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;
         }
       }
     }