# HG changeset patch # User Andrew Azores # Date 1508530576 14400 # Node ID f13e9da113507ca30eff400c0d3d20ff2625bb8f # Parent 288f0c458eb683c4c8dba437a04dac6f07180831 Port authServices to TypeScript, upgrade for Angular 4 Reviewed-by: jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-October/025463.html diff -r 288f0c458eb6 -r f13e9da11350 src/app/app.module.ts --- a/src/app/app.module.ts Thu Oct 19 11:36:12 2017 -0400 +++ b/src/app/app.module.ts Fri Oct 20 16:16:16 2017 -0400 @@ -39,6 +39,7 @@ import { UpgradeAdapter } from "@angular/upgrade"; import { UpgradeModule } from "@angular/upgrade/static"; +import { AuthModule } from "components/auth/auth.module"; import { FiltersModule } from "shared/filters/filters.module"; import { ServicesModule } from "shared/services/services.module"; @@ -51,6 +52,7 @@ HttpClientModule, UpgradeModule, + AuthModule, ServicesModule, FiltersModule, ], diff -r 288f0c458eb6 -r f13e9da11350 src/app/components/auth/auth-service.interface.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/auth/auth-service.interface.ts Fri Oct 20 16:16:16 2017 -0400 @@ -0,0 +1,53 @@ +/** + * Copyright 2012-2017 Red Hat, Inc. + * + * Thermostat is distributed under the GNU General Public License, + * version 2 or any later version (with a special exception described + * below, commonly known as the "Classpath Exception"). + * + * A copy of GNU General Public License (GPL) is included in this + * distribution, in the file COPYING. + * + * Linking Thermostat code with other modules is making a combined work + * based on Thermostat. Thus, the terms and conditions of the GPL + * cover the whole combination. + * + * As a special exception, the copyright holders of Thermostat give you + * permission to link this code with independent modules to produce an + * executable, regardless of the license terms of these independent + * modules, and to copy and distribute the resulting executable under + * terms of your choice, provided that you also meet, for each linked + * independent module, the terms and conditions of the license of that + * module. An independent module is a module which is not derived from + * or based on Thermostat code. If you modify Thermostat, you may + * extend this exception to your version of the software, but you are + * not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +import { InjectionToken } from "@angular/core"; + +export interface IAuthService { + + readonly authHeader: string; + + readonly username: string; + + rootScope: ng.IRootScopeService; + + status(): boolean; + + login(user: string, pass: string, onsuccess?: () => void): void; + + goToLogin(promise: angular.IDeferred<{}>): void; + + logout(onsuccess?: () => void): void; + + refresh(): ng.IPromise<{}>; + + getCommandChannelUrl(baseUrl: string): string; + +} + +const AuthServiceToken: InjectionToken = new InjectionToken("AuthService"); +export { AuthServiceToken }; diff -r 288f0c458eb6 -r f13e9da11350 src/app/components/auth/auth.module.js --- a/src/app/components/auth/auth.module.js Thu Oct 19 11:36:12 2017 -0400 +++ b/src/app/components/auth/auth.module.js Fri Oct 20 16:16:16 2017 -0400 @@ -27,8 +27,8 @@ import Keycloak from 'keycloak-js/dist/keycloak.js'; -import KeycloakAuthService from './keycloak-auth.service.js'; -import BasicAuthService from './basic-auth.service.js'; +import { KeycloakAuthService } from './keycloak-auth.service'; +import { BasicAuthService } from './basic-auth.service'; let MOD_NAME = 'authModule'; export default MOD_NAME; @@ -62,7 +62,7 @@ .success(done) .error(() => window.location.reload()); } else { - mod.service('authService', BasicAuthService); + mod.service('authService', ['$injector', BasicAuthService]); done(); } } diff -r 288f0c458eb6 -r f13e9da11350 src/app/components/auth/auth.module.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/auth/auth.module.ts Fri Oct 20 16:16:16 2017 -0400 @@ -0,0 +1,49 @@ +/** + * Copyright 2012-2017 Red Hat, Inc. + * + * Thermostat is distributed under the GNU General Public License, + * version 2 or any later version (with a special exception described + * below, commonly known as the "Classpath Exception"). + * + * A copy of GNU General Public License (GPL) is included in this + * distribution, in the file COPYING. + * + * Linking Thermostat code with other modules is making a combined work + * based on Thermostat. Thus, the terms and conditions of the GPL + * cover the whole combination. + * + * As a special exception, the copyright holders of Thermostat give you + * permission to link this code with independent modules to produce an + * executable, regardless of the license terms of these independent + * modules, and to copy and distribute the resulting executable under + * terms of your choice, provided that you also meet, for each linked + * independent module, the terms and conditions of the license of that + * module. An independent module is a module which is not derived from + * or based on Thermostat code. If you modify Thermostat, you may + * extend this exception to your version of the software, but you are + * not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +import { + InjectionToken, + NgModule, +} from "@angular/core"; + +import { + AuthServiceToken, + IAuthService, +} from "./auth-service.interface"; +import { BasicAuthService } from "./basic-auth.service"; +import { KeycloakAuthService } from "./keycloak-auth.service"; + +const KEYCLOAK_ENABLED = process.env.NODE_ENV === "production"; +/* istanbul ignore next */ +const AUTH_SVC_CLASS = KEYCLOAK_ENABLED ? KeycloakAuthService : BasicAuthService; + +@NgModule({ + providers: [ + { deps: ["$injector"], provide: AuthServiceToken, useClass: AUTH_SVC_CLASS as any }, + ], +}) +export class AuthModule {} diff -r 288f0c458eb6 -r f13e9da11350 src/app/components/auth/basic-auth.service.js --- a/src/app/components/auth/basic-auth.service.js Thu Oct 19 11:36:12 2017 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +0,0 @@ -/** - * Copyright 2012-2017 Red Hat, Inc. - * - * Thermostat is distributed under the GNU General Public License, - * version 2 or any later version (with a special exception described - * below, commonly known as the "Classpath Exception"). - * - * A copy of GNU General Public License (GPL) is included in this - * distribution, in the file COPYING. - * - * Linking Thermostat code with other modules is making a combined work - * based on Thermostat. Thus, the terms and conditions of the GPL - * cover the whole combination. - * - * As a special exception, the copyright holders of Thermostat give you - * permission to link this code with independent modules to produce an - * executable, regardless of the license terms of these independent - * modules, and to copy and distribute the resulting executable under - * terms of your choice, provided that you also meet, for each linked - * independent module, the terms and conditions of the license of that - * module. An independent module is a module which is not derived from - * or based on Thermostat code. If you modify Thermostat, you may - * extend this exception to your version of the software, but you are - * not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - */ - -import * as url from 'url'; - -const SESSION_EXPIRY_MINUTES = 15; - -export default class BasicAuthService { - - constructor ($q, $state, localStorage) { - 'ngInject'; - this.q = $q; - this.$state = $state; - this._localStorage = localStorage; - - this._pass = null; - } - - set rootScope (rootScope) { - this._rootScope = rootScope; - } - - status () { - return this._sessionIsValid(); - } - - login (user, pass, success = angular.noop) { - this._pass = pass; - if (this._rememberUser) { - this._localStorage.setItem('username', user); - } - - this._localStorage.setItem('loggedInUser', user); - this._refreshSession(); - - this._rootScope.$broadcast('userLoginChanged'); - success(); - } - - goToLogin (promise) { - promise.resolve(this.$state.target('login')); - } - - logout (callback = angular.noop) { - this._pass = null; - this.$state.go('login'); - - this._localStorage.removeItem('session'); - this._localStorage.removeItem('loggedInUser'); - - this._rootScope.$broadcast('userLoginChanged'); - callback(); - } - - _refreshSession () { - let now = new Date(); - let expiry = new Date(now); - expiry.setMinutes(now.getMinutes() + SESSION_EXPIRY_MINUTES); - this._localStorage.setItem('session', expiry); - } - - _sessionIsValid () { - if (!this._localStorage.hasItem('session')) { - return false; - } - let session = new Date(this._localStorage.getItem('session')); - let now = new Date(); - - return session.getTime() >= now.getTime(); - } - - refresh () { - let defer = this.q.defer(); - if (this._sessionIsValid()) { - this._refreshSession(); - defer.resolve(); - } else { - this._localStorage.removeItem('session'); - this._localStorage.removeItem('loggedInUser'); - defer.reject(); - } - return defer.promise; - } - - get authHeader () { - return 'Basic ' + btoa(this.username + ':' + this._pass); - } - - get username () { - return this._localStorage.getItem('loggedInUser'); - } - - get rememberedUsername () { - return this._localStorage.getItem('username'); - } - - getCommandChannelUrl (baseUrl) { - let parsed = url.parse(baseUrl); - if (this.username == null && this._pass == null) { - // no-op - } - if (this.username != null && this._pass == null) { - parsed.auth = this.username; - } - if (this.username != null && this._pass != null) { - parsed.auth = this.username + ':' + this._pass; - } - return url.format(parsed); - } - - rememberUser (remember) { - this._rememberUser = remember; - if (!remember) { - this._localStorage.removeItem('username'); - } - } - -} diff -r 288f0c458eb6 -r f13e9da11350 src/app/components/auth/basic-auth.service.spec.js --- a/src/app/components/auth/basic-auth.service.spec.js Thu Oct 19 11:36:12 2017 -0400 +++ b/src/app/components/auth/basic-auth.service.spec.js Fri Oct 20 16:16:16 2017 -0400 @@ -27,7 +27,7 @@ // AuthServices are set up before Angular is bootstrapped, so we manually import rather than // using Angular DI -import BasicAuthService from './basic-auth.service.js'; +import { BasicAuthService } from './basic-auth.service'; describe('BasicAuthService', () => { @@ -61,7 +61,12 @@ clear: sinon.spy() }; rootScope = { $broadcast: sinon.spy() }; - basicAuthService = new BasicAuthService(q, state, localStorage); + + let injector = sinon.stub(); + injector.withArgs('$q').returns(q); + injector.withArgs('$state').returns(state); + injector.withArgs('localStorage').returns(localStorage); + basicAuthService = new BasicAuthService({ get: injector }); basicAuthService.rootScope = rootScope; }); diff -r 288f0c458eb6 -r f13e9da11350 src/app/components/auth/basic-auth.service.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/auth/basic-auth.service.ts Fri Oct 20 16:16:16 2017 -0400 @@ -0,0 +1,154 @@ +/** + * Copyright 2012-2017 Red Hat, Inc. + * + * Thermostat is distributed under the GNU General Public License, + * version 2 or any later version (with a special exception described + * below, commonly known as the "Classpath Exception"). + * + * A copy of GNU General Public License (GPL) is included in this + * distribution, in the file COPYING. + * + * Linking Thermostat code with other modules is making a combined work + * based on Thermostat. Thus, the terms and conditions of the GPL + * cover the whole combination. + * + * As a special exception, the copyright holders of Thermostat give you + * permission to link this code with independent modules to produce an + * executable, regardless of the license terms of these independent + * modules, and to copy and distribute the resulting executable under + * terms of your choice, provided that you also meet, for each linked + * independent module, the terms and conditions of the license of that + * module. An independent module is a module which is not derived from + * or based on Thermostat code. If you modify Thermostat, you may + * extend this exception to your version of the software, but you are + * not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +import * as angular from "angular"; +import * as url from "url"; +import { IAuthService } from "./auth-service.interface"; + +import { StateService } from "@uirouter/angularjs"; + +import { LocalStorageService } from "../../shared/services/local-storage.service.js"; + +const SESSION_EXPIRY_MINUTES = 15; + +export class BasicAuthService implements IAuthService { + + private q: ng.IQService; + private $state: StateService; + private localStorage: LocalStorageService; + private pass: string = null; + private scope: ng.IRootScopeService; + private remember: boolean; + + public constructor($injector: ng.auto.IInjectorService) { + this.q = $injector.get("$q"); + this.$state = $injector.get("$state"); + this.localStorage = $injector.get("localStorage"); + + this.pass = null; + } + + public set rootScope(rootScope: ng.IRootScopeService) { + this.scope = rootScope; + } + + public status(): boolean { + return this._sessionIsValid(); + } + + public login(user: string, pass: string, success = angular.noop): void { + this.pass = pass; + if (this.remember) { + this.localStorage.setItem("username", user); + } + + this.localStorage.setItem("loggedInUser", user); + this._refreshSession(); + + this.scope.$broadcast("userLoginChanged"); + success(); + } + + public goToLogin(promise: ng.IDeferred<{}>): void { + promise.resolve(this.$state.target("login")); + } + + public logout(callback = angular.noop): void { + this.pass = null; + this.$state.go("login"); + + this.localStorage.removeItem("session"); + this.localStorage.removeItem("loggedInUser"); + + this.scope.$broadcast("userLoginChanged"); + callback(); + } + + public refresh(): ng.IPromise<{}> { + const defer = this.q.defer(); + if (this._sessionIsValid()) { + this._refreshSession(); + defer.resolve(); + } else { + this.localStorage.removeItem("session"); + this.localStorage.removeItem("loggedInUser"); + defer.reject(); + } + return defer.promise; + } + + public get authHeader(): string { + return "Basic " + btoa(this.username + ":" + this.pass); + } + + public get username(): string { + return this.localStorage.getItem("loggedInUser"); + } + + public get rememberedUsername(): string { + return this.localStorage.getItem("username"); + } + + public getCommandChannelUrl(baseUrl: string): string { + const parsed = url.parse(baseUrl); + if (this.username == null && this.pass == null) { + // no-op + } + if (this.username != null && this.pass == null) { + parsed.auth = this.username; + } + if (this.username != null && this.pass != null) { + parsed.auth = this.username + ":" + this.pass; + } + return url.format(parsed); + } + + public rememberUser(rememberUser: boolean): void { + this.remember = rememberUser; + if (!this.remember) { + this.localStorage.removeItem("username"); + } + } + + private _refreshSession(): void { + const now = new Date(); + const expiry = new Date(now); + expiry.setMinutes(now.getMinutes() + SESSION_EXPIRY_MINUTES); + this.localStorage.setItem("session", expiry); + } + + private _sessionIsValid(): boolean { + if (!this.localStorage.hasItem("session")) { + return false; + } + const session = new Date(this.localStorage.getItem("session")); + const now = new Date(); + + return session.getTime() >= now.getTime(); + } + +} diff -r 288f0c458eb6 -r f13e9da11350 src/app/components/auth/keycloak-auth.service.js --- a/src/app/components/auth/keycloak-auth.service.js Thu Oct 19 11:36:12 2017 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -/** - * Copyright 2012-2017 Red Hat, Inc. - * - * Thermostat is distributed under the GNU General Public License, - * version 2 or any later version (with a special exception described - * below, commonly known as the "Classpath Exception"). - * - * A copy of GNU General Public License (GPL) is included in this - * distribution, in the file COPYING. - * - * Linking Thermostat code with other modules is making a combined work - * based on Thermostat. Thus, the terms and conditions of the GPL - * cover the whole combination. - * - * As a special exception, the copyright holders of Thermostat give you - * permission to link this code with independent modules to produce an - * executable, regardless of the license terms of these independent - * modules, and to copy and distribute the resulting executable under - * terms of your choice, provided that you also meet, for each linked - * independent module, the terms and conditions of the license of that - * module. An independent module is a module which is not derived from - * or based on Thermostat code. If you modify Thermostat, you may - * extend this exception to your version of the software, but you are - * not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - */ - -import * as url from 'url'; - -export default class KeycloakAuthService { - - constructor (keycloak) { - this.keycloak = keycloak; - } - - set rootScope (rootScope) { - this._rootScope = rootScope; - } - - login () { - // no-op - } - - goToLogin (promise) { - this.keycloak.login(); - this._rootScope.$broadcast('userLoginChanged'); - promise.resolve(); - } - - logout (callback = angular.noop) { - this._rootScope.$broadcast('userLoginChanged'); - this.keycloak.logout(); - callback(); - } - - status () { - return this.keycloak.authenticated; - } - - refresh () { - // refresh the token if it expires within 300 seconds - return this.keycloak.updateToken(300); - } - - get authHeader () { - return 'Bearer ' + this.keycloak.token; - } - - get username () { - return this.keycloak.idTokenParsed.preferred_username; - } - - getCommandChannelUrl (baseUrl) { - let parsed = url.parse(baseUrl); - parsed.query = { access_token: this.keycloak.token }; - return url.format(parsed); - } - -} diff -r 288f0c458eb6 -r f13e9da11350 src/app/components/auth/keycloak-auth.service.spec.js --- a/src/app/components/auth/keycloak-auth.service.spec.js Thu Oct 19 11:36:12 2017 -0400 +++ b/src/app/components/auth/keycloak-auth.service.spec.js Fri Oct 20 16:16:16 2017 -0400 @@ -27,7 +27,7 @@ // AuthServices are set up before Angular is bootstrapped, so we manually import rather than // using Angular DI -import KeycloakAuthService from './keycloak-auth.service.js'; +import { KeycloakAuthService } from './keycloak-auth.service'; describe('KeycloakAuthService', () => { diff -r 288f0c458eb6 -r f13e9da11350 src/app/components/auth/keycloak-auth.service.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/auth/keycloak-auth.service.ts Fri Oct 20 16:16:16 2017 -0400 @@ -0,0 +1,83 @@ +/** + * Copyright 2012-2017 Red Hat, Inc. + * + * Thermostat is distributed under the GNU General Public License, + * version 2 or any later version (with a special exception described + * below, commonly known as the "Classpath Exception"). + * + * A copy of GNU General Public License (GPL) is included in this + * distribution, in the file COPYING. + * + * Linking Thermostat code with other modules is making a combined work + * based on Thermostat. Thus, the terms and conditions of the GPL + * cover the whole combination. + * + * As a special exception, the copyright holders of Thermostat give you + * permission to link this code with independent modules to produce an + * executable, regardless of the license terms of these independent + * modules, and to copy and distribute the resulting executable under + * terms of your choice, provided that you also meet, for each linked + * independent module, the terms and conditions of the license of that + * module. An independent module is a module which is not derived from + * or based on Thermostat code. If you modify Thermostat, you may + * extend this exception to your version of the software, but you are + * not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +import * as angular from "angular"; +import * as url from "url"; + +import { IAuthService } from "./auth-service.interface"; + +export class KeycloakAuthService implements IAuthService { + + private scope: ng.IRootScopeService; + + public constructor(private keycloak: any) { + } + + public set rootScope(rootScope: ng.IRootScopeService) { + this.scope = rootScope; + } + + public login(): void { + // no-op + } + + public goToLogin(promise: ng.IDeferred<{}>): void { + this.keycloak.login(); + this.scope.$broadcast("userLoginChanged"); + promise.resolve(); + } + + public logout(callback = angular.noop): void { + this.scope.$broadcast("userLoginChanged"); + this.keycloak.logout(); + callback(); + } + + public status(): boolean { + return this.keycloak.authenticated; + } + + public refresh(): ng.IPromise<{}> { + // refresh the token if it expires within 300 seconds + return this.keycloak.updateToken(300); + } + + public get authHeader(): string { + return "Bearer " + this.keycloak.token; + } + + public get username(): string { + return this.keycloak.idTokenParsed.preferred_username; + } + + public getCommandChannelUrl(baseUrl: string): string { + const parsed = url.parse(baseUrl); + parsed.query = { access_token: this.keycloak.token }; + return url.format(parsed); + } + +}