From ed9a33a32c3f89403a1f64732e2b5e620d95bbca Mon Sep 17 00:00:00 2001 From: charan Date: Tue, 8 Nov 2016 14:07:31 -0800 Subject: [PATCH] Auth (#208) * login/logout workflow * login-workflow fix * auth * comments fix --- app/app.css | 2 +- app/app.module.ts | 8 +- app/app.routes.ts | 8 +- app/components/directives/authdirective.ts | 30 + .../directives/directives.module.ts | 7 +- app/components/models/collection.ts | 4 +- app/components/utils/authMatrix.ts | 29 + app/components/utils/authguard.ts | 80 + app/components/utils/authservice.ts | 161 + app/login/login.html | 23 +- app/login/login.module.ts | 8 +- app/login/loginctrl.ts | 29 +- app/login/logout.css | 5 + app/login/logout.html | 18 + app/login/logoutctrl.ts | 27 + app/login/unauthorized.html | 18 + app/login/unauthorized.ts | 18 + app/main.bundle.js | 1926 +- app/menu/menu.html | 2 +- app/menu/menu.module.ts | 7 +- app/menu/menuCtrl.ts | 12 +- app/networks/networklist.html | 2 +- app/organizations/organizationlist.html | 2 +- app/polyfills.bundle.js | 1408 +- app/service_lbs/servicelblist.html | 4 +- app/vendor.bundle.js | 40080 ++++++++-------- 26 files changed, 22421 insertions(+), 21497 deletions(-) create mode 100644 app/components/directives/authdirective.ts create mode 100644 app/components/utils/authMatrix.ts create mode 100644 app/components/utils/authguard.ts create mode 100644 app/components/utils/authservice.ts create mode 100644 app/login/logout.css create mode 100644 app/login/logout.html create mode 100644 app/login/logoutctrl.ts create mode 100644 app/login/unauthorized.html create mode 100644 app/login/unauthorized.ts diff --git a/app/app.css b/app/app.css index ade9823d..3a0ee476 100644 --- a/app/app.css +++ b/app/app.css @@ -19,7 +19,7 @@ table thead th[sortfield] span{ color: #b6b6b8 } -.login{ +.background{ /* This image will be displayed fullscreen */ background: url('images/loginbackground.jpeg') no-repeat center center; diff --git a/app/app.module.ts b/app/app.module.ts index 8002f965..846c88a3 100644 --- a/app/app.module.ts +++ b/app/app.module.ts @@ -5,8 +5,6 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HttpModule } from "@angular/http"; import { APP_BASE_HREF, HashLocationStrategy, LocationStrategy } from '@angular/common'; -import { LoginModule } from "./login/login.module"; -import { MenuModule } from "./menu/menu.module"; import { DashboardModule } from "./dashboard/dashboard.module"; import { NetworkPoliciesModule } from "./network_policies/networkpolicies.module"; import { ApplicationGroupsModule } from "./applicationgroups/applicationgroups.module.ts"; @@ -25,8 +23,12 @@ import { CRUDHelperService } from "./components/utils/crudhelperservice"; import { InspectService } from "./components/utils/inspectservice"; import { NetworkService } from "./components/utils/networkservice"; import { NodesService } from "./components/utils/nodesservice"; +import { MenuModule } from "./menu/menu.module"; import { AppComponent } from "./app.component"; import appRoutes from "./app.routes.ts"; +import {LoginModule} from "./login/login.module"; +import {AuthService} from "./components/utils/authservice"; +import {AuthGuard} from "./components/utils/authguard"; @NgModule({ imports: [ @@ -58,6 +60,8 @@ import appRoutes from "./app.routes.ts"; InspectService, NetworkService, NodesService, + AuthService, + AuthGuard, { provide: APP_BASE_HREF, useValue: '' }, { provide: LocationStrategy, useClass: HashLocationStrategy } ], diff --git a/app/app.routes.ts b/app/app.routes.ts index 58221c3b..29f12f6b 100644 --- a/app/app.routes.ts +++ b/app/app.routes.ts @@ -25,13 +25,19 @@ import {ServicelbListComponent} from "./service_lbs/servicelblistctrl"; import {ServicelbCreateComponent} from "./service_lbs/servicelbcreatectrl"; import {ServicelbDetailsComponent} from "./service_lbs/servicelbdetailsctrl"; import {LoginComponent} from "./login/loginctrl"; +import {AuthGuard} from "./components/utils/authguard"; +import {UnauthorizedComponent} from "./login/unauthorized"; +import {LogoutComponent} from "./login/logoutctrl"; const routes = [ - {path: 'login', component: LoginComponent}, + {path: 'login', component: LoginComponent, canActivate: [AuthGuard]}, + {path: 'logout', component: LogoutComponent, canActivate: [AuthGuard]}, + {path: 'unauthorized', component: UnauthorizedComponent, canActivate: [AuthGuard]}, {path: '', redirectTo: 'login', pathMatch: 'full'}, { path: 'm', component: MenuComponent, + canActivateChild: [AuthGuard], children: [ {path: '', redirectTo: 'dashboard', pathMatch: 'full'}, {path: 'dashboard', component: DashboardComponent}, diff --git a/app/components/directives/authdirective.ts b/app/components/directives/authdirective.ts new file mode 100644 index 00000000..8beea865 --- /dev/null +++ b/app/components/directives/authdirective.ts @@ -0,0 +1,30 @@ +/** + * Created by cshampur on 11/7/16. + */ + +import {Directive, ElementRef, Renderer, Input, OnInit, TemplateRef, ViewContainerRef} from "@angular/core"; +import {AuthService} from "../utils/authservice"; + +@Directive({ + selector: '[auth]' +}) + +export class AuthDirective implements OnInit{ + + @Input('auth') auth: string; + constructor(private authService: AuthService, + private templateRef: TemplateRef, + private viewContainer: ViewContainerRef){ + this.auth = ''; + } + + ngOnInit(){ + if (this.auth == this.authService.authTokenPayload['role']){ + this.viewContainer.createEmbeddedView(this.templateRef); + } + else{ + this.viewContainer.clear(); + } + } +} + diff --git a/app/components/directives/directives.module.ts b/app/components/directives/directives.module.ts index 2e1d5cc6..5290c7da 100644 --- a/app/components/directives/directives.module.ts +++ b/app/components/directives/directives.module.ts @@ -9,6 +9,7 @@ import {FormsModule} from "@angular/forms"; import {CtvAccordionComponent} from "./accordiondirective"; import {CtvCollapsibleComponent} from "./collapsibledirective"; import {CtvNamevalueComponent} from "./namevaluedirective"; +import {AuthDirective} from "./authdirective"; @NgModule({ imports: [ CommonModule, FormsModule @@ -21,7 +22,8 @@ import {CtvNamevalueComponent} from "./namevaluedirective"; CtvTpaginationComponent, CtvAccordionComponent, CtvCollapsibleComponent, - CtvNamevalueComponent + CtvNamevalueComponent, + AuthDirective ], exports: [ ErrorMessageComponent, @@ -31,7 +33,8 @@ import {CtvNamevalueComponent} from "./namevaluedirective"; CtvTpaginationComponent, CtvAccordionComponent, CtvCollapsibleComponent, - CtvNamevalueComponent + CtvNamevalueComponent, + AuthDirective ] }) export class DirectivesModule {} \ No newline at end of file diff --git a/app/components/models/collection.ts b/app/components/models/collection.ts index d4f48761..342ff18d 100644 --- a/app/components/models/collection.ts +++ b/app/components/models/collection.ts @@ -59,7 +59,7 @@ export class Collection extends BaseCollection { collection.http.put(url, model).map((res: Response) => res.json()).toPromise() .then(function successCallback(response) { _.remove(collection.models, function (n) { - return n.key == model.key; + return n['key'] == model['key']; }); collection.models.push(response); resolve(response); @@ -83,7 +83,7 @@ export class Collection extends BaseCollection { collection.http.delete(url).map((res: Response) => res.json()).toPromise() .then(function successCallback(response) { _.remove(collection.models, function (n) { - return n.key == model.key; + return n['key'] == model['key']; }); resolve(response); }, function errorCallback(response) { diff --git a/app/components/utils/authMatrix.ts b/app/components/utils/authMatrix.ts new file mode 100644 index 00000000..0154cabc --- /dev/null +++ b/app/components/utils/authMatrix.ts @@ -0,0 +1,29 @@ +/** + * Created by cshampur on 11/4/16. + */ + +export const AuthMatrix = { + 'dashboard': {'DevOps':'y', 'SysAdmin':'y'}, + 'networkpolicies/list': {'DevOps':'y', 'SysAdmin':'y'}, + 'networkpolicies/isolation/create': {'DevOps':'n', 'SysAdmin':'y'}, + 'networkpolicies/isolation/details': {'DevOps':'y', 'SysAdmin':'y'}, + 'networkpolicies/isolation/edit': {'DevOps':'n', 'SysAdmin':'y'}, + 'networkpolicies/bandwidth/create': {'DevOps':'y', 'SysAdmin':'y'}, + 'networkpolicies/bandwidth/details': {'DevOps':'y', 'SysAdmin':'y'}, + 'networkpolicies/bandwidth/edit': {'DevOps':'n', 'SysAdmin':'y'}, + 'applicationgroups/list': {'DevOps':'y', 'SysAdmin':'y'}, + 'applicationgroups/create': {'DevOps':'n', 'SysAdmin':'y'}, + 'applicationgroups/details': {'DevOps':'y', 'SysAdmin':'y'}, + 'applicationgroups/edit': {'DevOps':'n', 'SysAdmin':'y'}, + 'settings/cluster': {'DevOps':'n', 'SysAdmin':'y'}, + 'settings/networks': {'DevOps':'n', 'SysAdmin':'y'}, + 'organizations/list': {'DevOps':'y', 'SysAdmin':'y'}, + 'organizations/create': {'DevOps':'n', 'SysAdmin':'y'}, + 'organizations/details': {'DevOps':'y', 'SysAdmin':'y'}, + 'networks/list': {'DevOps':'y', 'SysAdmin':'y'}, + 'networks/create': {'DevOps':'n', 'SysAdmin':'y'}, + 'networks/details': {'DevOps':'y', 'SysAdmin':'y'}, + 'servicelbs/list': {'DevOps':'y', 'SysAdmin':'y'}, + 'servicelbs/create': {'DevOps':'n', 'SysAdmin':'y'}, + 'servicelbs/details': {'DevOps':'y', 'SysAdmin':'y'} +} \ No newline at end of file diff --git a/app/components/utils/authguard.ts b/app/components/utils/authguard.ts new file mode 100644 index 00000000..816aeb56 --- /dev/null +++ b/app/components/utils/authguard.ts @@ -0,0 +1,80 @@ +/** + * Created by cshampur on 11/4/16. + */ +import { Injectable } from '@angular/core'; +import {CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild} from '@angular/router'; +import {AuthService} from "./authservice"; +import {AuthMatrix} from "./authMatrix"; +import {isNull} from "util"; + +@Injectable() +export class AuthGuard implements CanActivate, CanActivateChild { + + accessMatrix:any; + unguardedUrls: string[]; + + constructor(private authService: AuthService, private router: Router) { + this.accessMatrix = AuthMatrix; + this.unguardedUrls = ['/unauthorized', '/login', '/logout']; + } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + let url: string = state.url; + if (this.unguardedUrls.indexOf(url) > -1) + return true; + return this.checkLogin(url); + } + + canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + return this.canActivate(route, state); + } + + checkLogin(url: string): boolean { + + if (this.authService.isLoggedIn) { + if (this.checkAccess(url)) + if (this.authService.validateExpiry()) + return true; + else{ + this.loadLogin(url); + return false; + } + else{ + this.router.navigate(['/unauthorized']); + return false; + } + + } + // Validate Token Expiration + if (!isNull(localStorage.getItem("authToken"))){ + this.authService.extractBody(); + if(this.authService.validateExpiry()){ + this.authService.isLoggedIn = true; + if(this.checkAccess(url)) + return true; + else{ + this.router.navigate(['/unauthorized']); + } + } + } + + this.loadLogin(url); + return false; + } + + loadLogin(url: string): void{ + // Clean the local storage + this.authService.cleanuplocalstorage(); + // Store the attempted URL for redirecting + this.authService.redirectUrl = url; + // Navigate to the login page + this.router.navigate(['/login']); + } + + checkAccess(url:string): boolean{ + return this.authService.checkAccess(url); + } + + + +} diff --git a/app/components/utils/authservice.ts b/app/components/utils/authservice.ts new file mode 100644 index 00000000..930bd419 --- /dev/null +++ b/app/components/utils/authservice.ts @@ -0,0 +1,161 @@ +/** + * Created by cshampur on 11/4/16. + */ + +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/of'; +import 'rxjs/add/operator/do'; +import 'rxjs/add/operator/delay'; +import {Http, Headers, RequestOptions, Response} from "@angular/http"; +import {AuthMatrix} from "./authMatrix"; +import {isNull} from "util"; + +interface User{ + username: string; + password: string; +} + +@Injectable() +export class AuthService { + isLoggedIn: boolean = false; + + // store the URL so we can redirect after logging in + redirectUrl: string; + headers: Headers; + authTokenPayload: any; + accessMatrix:any; + + constructor(private http: Http){ + this.isLoggedIn = false; + this.redirectUrl = ''; + this.accessMatrix = AuthMatrix; + this.authTokenPayload = {}; + } + + checkAccess(url: string): boolean{ + var searchUrl = url.replace('/m/', ''); + if(searchUrl.indexOf('details') > -1 || searchUrl.indexOf('edit') > -1) + searchUrl = searchUrl.replace(/\/[^\/]*$/,''); + if(searchUrl.indexOf('policyTab') > -1) + searchUrl = searchUrl.replace(/;[^\/]*$/,'') + var role = this.authTokenPayload['role']; + if (this.accessMatrix[searchUrl][role]=='y') + return true; + else + return false; + } + + login(user: User): Observable { + var data = this.encodeUrlData(user); + this.headers = new Headers(); + this.headers.append('Content-Type', 'application/x-www-form-urlencoded'); + var options = new RequestOptions({headers: this.headers}); + + // This is just a mock + return new Observable((observer) => { + if (user.username != "devops" && user.username != "admin") + observer.next(false); + else{ + var res = ''; + + if (user.username == "devops" && user.password == "devops") + var res = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBTExfQ0xVU1RFUlNfQVVUSCI6dHJ1ZSwiZXhwIjoxNDk4NjQ3NjIxLCJyb2xlIjoiRGV2T3BzIn0=.WXE_VtvyE_pg8paoVDwVIavZNHB-LmBLGJgY4REgvYk"; + + if (user.username == "admin" && user.password == "admin") + var res = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBTExfQ0xVU1RFUlNfQVVUSCI6dHJ1ZSwiZXhwIjoxNDk4NjQ3NjIxLCJyb2xlIjoiU3lzQWRtaW4ifQ==.WXE_VtvyE_pg8paoVDwVIavZNHB-LmBLGJgY4REgvYk"; + + if (res == ''){ + observer.next(false); + } + else{ + this.isLoggedIn = true; + localStorage.setItem("authToken", res); + localStorage.setItem("loginTime", new Date().toLocaleString()); + localStorage.setItem("lastAccessTime", new Date().toLocaleString()); + this.extractBody(); + observer.next(true); + } + } + + + + }); + + // This Code will be active after CCN Proxy is live... + + /* + return this.http.post("/1/system/login", data, options) + .map((res) => { + var s = this.extractToken(res); + if (s){ + this.isLoggedIn = true; + return true; + } + else{ + this.isLoggedIn = false; + return false; + } + }) + .catch((error:any) => Observable.throw(error)); + */ + } + + logout(): void { + this.cleanuplocalstorage(); + } + + cleanuplocalstorage(): void{ + localStorage.removeItem("authToken"); + localStorage.removeItem("loginTime"); + localStorage.removeItem("lastAccessTime"); + this.isLoggedIn = false; + } + + encodeUrlData(body: any) :string{ + var str = Object.keys(body).map(function(key){ + return encodeURIComponent(key) + '=' + encodeURIComponent(body[key]); + }).join('&'); + return str; + } + + extractToken(res: Response): boolean { + var xAuthToken = res.headers.get("x-auth-token"); + if (xAuthToken.length > 0) { + localStorage.setItem("authToken", xAuthToken); + localStorage.setItem("loginTime", new Date().toLocaleString()); + localStorage.setItem("lastAccessTime", new Date().toLocaleString()); + this.extractBody(); + return true; + } + else{ + return false; + } + } + + extractBody(): void{ + var token = localStorage.getItem("authToken"); + var bodyEncoded = token.split('.')[1]; + var bodyString = atob(bodyEncoded); + this.authTokenPayload = JSON.parse(bodyString); + } + + validateExpiry(): boolean{ + var lastAcessTime: any; + var currentDate = new Date(); + lastAcessTime = localStorage.getItem("lastAccessTime"); + if(isNull(lastAcessTime)){ + return false; + } + lastAcessTime = new Date(lastAcessTime); + var durationEloped = (currentDate.getTime() - lastAcessTime.getTime()) / 60000; + if((durationEloped > 0) && (durationEloped < 10)){ + if(currentDate.getTime() > (this.authTokenPayload['exp'] * 1000)) + return false; + localStorage.setItem("lastAccessTime", currentDate.toLocaleString()); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/app/login/login.html b/app/login/login.html index ff042f47..b2195301 100644 --- a/app/login/login.html +++ b/app/login/login.html @@ -1,12 +1,27 @@