Merge branch 'develop' of github.com:apache/incubator-dlab into DLAB-1157
diff --git a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
index d0b33b9..0473332 100644
--- a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
@@ -49,7 +49,7 @@
}, {
path: 'resources_list',
component: ResourcesComponent,
- canActivate: [CheckParamsGuard]
+ canActivate: [AuthorizationGuard]
}, {
path: 'billing_report',
component: ReportingComponent,
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
index b465665..f815c70 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
@@ -86,19 +86,20 @@
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: [] },
-
+ {
+ provide: HTTP_INTERCEPTORS,
+ useClass: ErrorInterceptor,
+ multi: true,
+ },
{
provide: HTTP_INTERCEPTORS,
useClass: HttpTokenInterceptor,
multi: true
- }, {
+ },
+ {
provide: HTTP_INTERCEPTORS,
useClass: NoCacheInterceptor,
multi: true,
- }, {
- provide: HTTP_INTERCEPTORS,
- useClass: ErrorInterceptor,
- multi: true,
}
]
};
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/interceptors/error.interceptor.ts b/services/self-service/src/main/resources/webapp/src/app/core/interceptors/error.interceptor.ts
index d492eda..d464d16 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/interceptors/error.interceptor.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/interceptors/error.interceptor.ts
@@ -18,41 +18,81 @@
*/
import { Injectable } from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
import {
- HttpInterceptor,
- HttpRequest,
- HttpHandler,
- HttpEvent
+ HttpInterceptor,
+ HttpRequest,
+ HttpHandler,
+ HttpEvent,
+ HttpErrorResponse
} from '@angular/common/http';
-import { Observable, throwError, of as observableOf } from 'rxjs';
-import { map, catchError } from 'rxjs/operators';
+import { Observable } from 'rxjs';
+import { _throw } from 'rxjs/observable/throw';
+import { switchMap, filter, take, catchError } from 'rxjs/operators';
-import { StorageService, AppRoutingService } from '../services';
+import { StorageService, AppRoutingService, ApplicationSecurityService } from '../services';
import { HTTP_STATUS_CODES } from '../util';
@Injectable() export class ErrorInterceptor implements HttpInterceptor {
+ private isRefreshing = false;
+ private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
+
constructor(
private jwtService: StorageService,
- private routingService: AppRoutingService
- ) {}
+ private routingService: AppRoutingService,
+ private auth: ApplicationSecurityService
+ ) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError(error => {
- let url = error.url;
- if (url.indexOf('?') > -1) {
- url = url.substr(0, url.indexOf('?'));
- }
-
- if ((error.status === HTTP_STATUS_CODES.UNAUTHORIZED) && !url.endsWith('login')) {
- this.jwtService.destroyToken();
- this.routingService.redirectToLoginPage();
- return observableOf(error);
+ if (error instanceof HttpErrorResponse) {
+ switch ((<HttpErrorResponse>error).status) {
+ case HTTP_STATUS_CODES.UNAUTHORIZED:
+ return this.handleUnauthorized(request, next);
+ case HTTP_STATUS_CODES.BAD_REQUEST:
+ return this.handleBadRequest(request, next);
+ default:
+ return _throw(error);
+ }
} else {
- return throwError(error);
+ this.routingService.redirectToLoginPage();
+ this.jwtService.destroyTokens();
+ return _throw(error);
}
}));
+ }
+
+ private addToken(request: HttpRequest<any>, token: string) {
+ return request.clone({ setHeaders: { 'Authorization': `Bearer ${token}` } });
+ }
+
+ private handleUnauthorized(request: HttpRequest<any>, next: HttpHandler) {
+
+ if (!this.isRefreshing) {
+ this.isRefreshing = true;
+ this.jwtService.destroyAccessToken();
+ this.refreshTokenSubject.next(null);
+
+ return this.auth.refreshToken().pipe(
+ switchMap((token: any) => {
+ this.isRefreshing = false;
+ this.refreshTokenSubject.next(this.addToken(request, token.access_token));
+ return next.handle(request);
+ }));
+
+ } else {
+ return this.refreshTokenSubject.pipe(
+ filter(token => token != null),
+ take(1),
+ switchMap(jwt => next.handle(this.addToken(request, jwt))));
}
+ }
+
+ private handleBadRequest(request: HttpRequest<any>, next: HttpHandler) {
+ this.routingService.redirectToLoginPage();
+ return next.handle(request);
+ }
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts b/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts
index ad33ef2..ce476b4 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts
@@ -19,17 +19,12 @@
import { Injectable } from '@angular/core';
import { StorageService } from '../services/storage.service';
-import {
- HttpInterceptor,
- HttpRequest,
- HttpHandler,
- HttpEvent
-} from '@angular/common/http';
+import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable() export class HttpTokenInterceptor implements HttpInterceptor {
- constructor(private jwtService: StorageService) {}
+ constructor(private jwtService: StorageService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = this.jwtService.getToken();
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationSecurity.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationSecurity.service.ts
index c6b161c..a55c667 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationSecurity.service.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationSecurity.service.ts
@@ -19,7 +19,7 @@
import { Injectable } from '@angular/core';
import { of as observableOf, Observable, BehaviorSubject } from 'rxjs';
-import { catchError, map } from 'rxjs/operators';
+import { catchError, map, tap } from 'rxjs/operators';
import { ApplicationServiceFacade } from './applicationServiceFacade.service';
import { AppRoutingService } from './appRouting.service';
@@ -59,13 +59,8 @@
.pipe(
map(response => {
if (response.status === HTTP_STATUS_CODES.OK) {
- if (!DICTIONARY.use_ldap) {
- this.storage.setAuthToken(response.body.access_token);
- this.storage.setUserName(response.body.username);
- } else {
- this.storage.setAuthToken(response.body);
- this.storage.setUserName(loginModel.username);
- }
+ this.storage.storeTokens(response.body);
+ this.storage.setUserName(response.body.username);
this._loggedInStatus.next(true);
return true;
}
@@ -75,6 +70,13 @@
catchError(ErrorUtils.handleServiceError));
}
+ public refreshToken() {
+ const refreshTocken = `/${this.storage.getRefreshToken()}`;
+ return this.serviceFacade.buildRefreshToken(refreshTocken)
+ .pipe(
+ tap((tokens) => this.storage.storeTokens(tokens)));
+ }
+
public logout(): Observable<boolean> {
const authToken = this.storage.getToken();
@@ -83,7 +85,7 @@
.buildLogoutRequest()
.pipe(
map(response => {
- this.storage.destroyToken();
+ this.storage.destroyTokens();
this.storage.setBillingQuoteUsed('');
this._loggedInStatus.next(false);
return response;
@@ -106,12 +108,12 @@
return true;
}
- this.storage.destroyToken();
+ this.storage.destroyTokens();
return false;
}),
catchError(error => {
this.emmitMessage(error.message);
- this.storage.destroyToken();
+ this.storage.destroyTokens();
return observableOf(false);
}));
@@ -129,7 +131,7 @@
map((response: any) => {
const data = response.body;
if (response.status === HTTP_STATUS_CODES.OK && data.access_token) {
- this.storage.setAuthToken(data.access_token);
+ this.storage.storeTokens(data);
this.storage.setUserName(data.username);
this.appRoutingService.redirectToHomePage();
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts
index 52bfb54..67c7010 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts
@@ -35,6 +35,7 @@
private static readonly LOGIN = 'login';
private static readonly LOGOUT = 'logout';
private static readonly AUTHORIZE = 'authorize';
+ private static readonly REFRESH_TOKEN = 'refresh_token';
private static readonly OAUTH = 'oauth';
private static readonly ACCESS_KEY = 'access_key';
private static readonly ACTIVE_LIST = 'active_list';
@@ -75,7 +76,7 @@
private static readonly PROJECT = 'project';
private static readonly USER_PROJECT = 'project/me';
private static readonly ENDPOINT = 'endpoint';
- private accessTokenKey: string = 'access_token';
+
private requestRegistry: Dictionary<string>;
constructor(private http: HttpClient) {
@@ -107,6 +108,12 @@
});
}
+ public buildRefreshToken(param: any): Observable<any> {
+ return this.buildRequest(HTTPMethod.POST,
+ this.requestRegistry.Item(ApplicationServiceFacade.REFRESH_TOKEN) + param,
+ null);
+ }
+
public buildLocationCheck(): Observable<any> {
return this.buildRequest(HTTPMethod.GET,
this.requestRegistry.Item(ApplicationServiceFacade.OAUTH),
@@ -588,6 +595,7 @@
this.requestRegistry.Add(ApplicationServiceFacade.LOGIN, '/api/user/login');
this.requestRegistry.Add(ApplicationServiceFacade.LOGOUT, '/api/oauth/logout');
this.requestRegistry.Add(ApplicationServiceFacade.AUTHORIZE, '/api/oauth/authorize');
+ this.requestRegistry.Add(ApplicationServiceFacade.REFRESH_TOKEN, '/api/oauth/refresh');
this.requestRegistry.Add(ApplicationServiceFacade.ACTIVE_LIST, '/api/environment/user');
this.requestRegistry.Add(ApplicationServiceFacade.FULL_ACTIVE_LIST, '/api/environment/all');
this.requestRegistry.Add(ApplicationServiceFacade.ENV, '/api/environment');
@@ -626,7 +634,6 @@
// Environment Health Status
this.requestRegistry.Add(ApplicationServiceFacade.ENVIRONMENT_HEALTH_STATUS, '/api/infrastructure/status');
- this.requestRegistry.Add(ApplicationServiceFacade.ENVIRONMENT_HEALTH_STATUS, '/api/infrastructure/status');
this.requestRegistry.Add(ApplicationServiceFacade.META_DATA, '/api/infrastructure/meta');
this.requestRegistry.Add(ApplicationServiceFacade.EDGE_NODE_START, '/api/infrastructure/edge/start');
this.requestRegistry.Add(ApplicationServiceFacade.EDGE_NODE_STOP, '/api/infrastructure/edge/stop');
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/storage.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/storage.service.ts
index 3a2e44d..71de459 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/services/storage.service.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/storage.service.ts
@@ -25,16 +25,19 @@
private userNameKey: string = 'user_name';
private quoteUsedKey: string = 'billing_quote';
+ private readonly JWT_TOKEN = 'JWT_TOKEN';
+ private readonly REFRESH_TOKEN = 'REFRESH_TOKEN';
+
getToken(): string {
- return window.localStorage.getItem(this.accessTokenKey);
+ return window.localStorage.getItem(this.JWT_TOKEN);
+ }
+
+ getRefreshToken() {
+ return localStorage.getItem(this.REFRESH_TOKEN);
}
setAuthToken(token: string) {
- window.localStorage.setItem(this.accessTokenKey, token);
- }
-
- destroyToken(): void {
- window.localStorage.removeItem(this.accessTokenKey);
+ window.localStorage.setItem(this.JWT_TOKEN, token);
}
getUserName(): string {
@@ -52,4 +55,18 @@
setBillingQuoteUsed(quote): void {
window.localStorage.setItem(this.quoteUsedKey, quote);
}
+
+ storeTokens(tokens) {
+ window.localStorage.setItem(this.JWT_TOKEN, tokens.access_token);
+ window.localStorage.setItem(this.REFRESH_TOKEN, tokens.refresh_token);
+ }
+
+ destroyAccessToken(): void {
+ window.localStorage.removeItem(this.JWT_TOKEN);
+ }
+
+ destroyTokens(): void {
+ window.localStorage.removeItem(this.JWT_TOKEN);
+ window.localStorage.removeItem(this.REFRESH_TOKEN);
+ }
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/login/login.model.ts b/services/self-service/src/main/resources/webapp/src/app/login/login.model.ts
index 52e7b18..d06ed0d 100644
--- a/services/self-service/src/main/resources/webapp/src/app/login/login.model.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/login/login.model.ts
@@ -18,7 +18,7 @@
*/
export class LoginModel {
- constructor(public username: string, public password: string) {}
+ constructor(public username: string, public password: string) { }
toJsonString(): string {
return JSON.stringify({
diff --git a/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.html b/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.html
index ac576ca..c6f6020 100644
--- a/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.html
@@ -99,7 +99,8 @@
<span class="label"> {{ DICTIONARY.instance_size}} </span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
- <span *ngIf="filteredReportData.shape.length > 0; else shape_filtered">filter_list</span>
+ <span
+ *ngIf="filteredReportData[DICTIONARY.billing.instance_size].length > 0; else shape_filtered">filter_list</span>
<ng-template #shape_filtered>more_vert</ng-template>
</i>
</button>
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.scss
index 64004d3..40902a6 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.scss
@@ -52,6 +52,7 @@
&.name-col {
padding-right: 5px;
padding-left: 24px;
+ cursor: pointer;
}
}
}