Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
fix: #205 adding checks to restrict access for admin and non-admin users
  • Loading branch information
enriquezrene committed May 29, 2020
commit 8557dccb5ba7b7848e47bc03dd8d7983ac5d2389
9 changes: 5 additions & 4 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { AdminGuard } from './guards/admin-guard/admin-guard';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AzureGuardService } from './guards/azure-guard.service';
import { LoginGuard } from './guards/login-guard/login.guard';
import { ReportsComponent } from './modules/reports/pages/reports.component';
import { TimeClockComponent } from './modules/time-clock/pages/time-clock.component';
import { TimeEntriesComponent } from './modules/time-entries/pages/time-entries.component';
Expand All @@ -14,13 +15,13 @@ const routes: Routes = [
{
path: '',
component: HomeComponent,
canActivate: [AzureGuardService],
canActivate: [LoginGuard],
children: [
{ path: 'reports', component: ReportsComponent },
{ path: 'reports', canActivate: [AdminGuard], component: ReportsComponent },
{ path: 'time-clock', component: TimeClockComponent },
{ path: 'time-entries', component: TimeEntriesComponent },
{ path: 'activities-management', component: ActivitiesManagementComponent },
{ path: 'customers-management', component: CustomerComponent },
{ path: 'customers-management', canActivate: [AdminGuard], component: CustomerComponent },
{ path: '', pathMatch: 'full', redirectTo: 'time-clock' },
],
},
Expand Down
20 changes: 20 additions & 0 deletions src/app/guards/admin-guard/admin-guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service';

@Injectable({
providedIn: 'root'
})
export class AdminGuard implements CanActivate {

constructor(private azureAdB2CService: AzureAdB2CService, private router: Router) { }

canActivate() {
if (this.azureAdB2CService.isAdmin()) {
return true;
} else {
this.router.navigate(['login']);
return false;
}
}
}
58 changes: 58 additions & 0 deletions src/app/guards/admin-guard/admin.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { AdminGuard } from './admin-guard';
import { TestBed, inject } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Router } from '@angular/router';

import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service';


describe('AdminGuard', () => {

let adminGuard: AdminGuard;
let azureAdB2CService: AzureAdB2CService;

const azureAdB2CServiceStub = {
isLogin() {
return true;
},
isAdmin() {
return true;
}
};

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ RouterTestingModule ],
providers: [
{ providers: AzureAdB2CService, useValue: azureAdB2CServiceStub},
]
});
adminGuard = TestBed.inject(AdminGuard);
azureAdB2CService = TestBed.inject(AzureAdB2CService);
});

it('should be created', () => {
expect(adminGuard).toBeTruthy();
});

it('can activate the route when user is logged-in', () => {
spyOn(azureAdB2CService, 'isAdmin').and.returnValue(true);

const canActivate = adminGuard.canActivate();

expect(azureAdB2CService.isAdmin).toHaveBeenCalled();
expect(canActivate).toEqual(true);
});

it('can not active the route and is redirected to login if user is not logged-in', inject([Router], (router: Router) => {
spyOn(azureAdB2CService, 'isAdmin').and.returnValue(false);
spyOn(router, 'navigate').and.stub();

const canActivate = adminGuard.canActivate();

expect(azureAdB2CService.isAdmin).toHaveBeenCalled();
expect(canActivate).toEqual(false);
expect(router.navigate).toHaveBeenCalledWith(['login']);
}));

});
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { TestBed, inject } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Router } from '@angular/router';

import { AzureAdB2CService } from '../modules/login/services/azure.ad.b2c.service';
import { AzureGuardService } from './azure-guard.service';
import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service';
import { LoginGuard } from './login.guard';


describe('AzureGuardService', () => {
let service: AzureGuardService;
describe('LoginGuard', () => {

let loginGuard: LoginGuard;
let azureAdB2CService: AzureAdB2CService;
const azureAdB2CServiceStub = {
isLogin() {
Expand All @@ -21,25 +22,25 @@ describe('AzureGuardService', () => {
{ providers: AzureAdB2CService, useValue: azureAdB2CServiceStub},
]
});
service = TestBed.inject(AzureGuardService);
loginGuard = TestBed.inject(LoginGuard);
azureAdB2CService = TestBed.inject(AzureAdB2CService);
});

it('should be created', () => {
expect(service).toBeTruthy();
expect(loginGuard).toBeTruthy();
});

it('can activate the route when user is logged-in', () => {
spyOn(azureAdB2CService, 'isLogin').and.returnValue(true);
const canActivate = service.canActivate();
const canActivate = loginGuard.canActivate();
expect(azureAdB2CService.isLogin).toHaveBeenCalled();
expect(canActivate).toEqual(true);
});

it('can not active the route and is redirected to login if user is not logged-in', inject([Router], (router: Router) => {
spyOn(azureAdB2CService, 'isLogin').and.returnValue(false);
spyOn(router, 'navigate').and.stub();
const canActivate = service.canActivate();
const canActivate = loginGuard.canActivate();
expect(azureAdB2CService.isLogin).toHaveBeenCalled();
expect(canActivate).toEqual(false);
expect(router.navigate).toHaveBeenCalledWith(['login']);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { AzureAdB2CService } from '../modules/login/services/azure.ad.b2c.service';
import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service';

@Injectable({
providedIn: 'root'
})
export class AzureGuardService implements CanActivate {
export class LoginGuard implements CanActivate {

constructor(private azureAdB2CService: AzureAdB2CService, private router: Router) { }

Expand Down
18 changes: 18 additions & 0 deletions src/app/modules/login/services/azure.ad.b2c.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@ describe('AzureAdB2CService', () => {
expect(name).toEqual(account.name);
});

it('isAdmin false when extension_role !== time-tracker-admin', () => {
spyOn(UserAgentApplication.prototype, 'getAccount').and.returnValue(account);

const isAdmin = service.isAdmin();

expect(isAdmin).toEqual(false);
});

it('isAdmin when extension_role === time-tracker-admin', () => {
const adminAccount = account;
adminAccount.idToken.extension_role = 'time-tracker-admin';
spyOn(UserAgentApplication.prototype, 'getAccount').and.returnValue(adminAccount);

const isAdmin = service.isAdmin();

expect(isAdmin).toBeTruthy();
});

it('isLogin returns true if UserAgentApplication has a defined Account', () => {
spyOn(UserAgentApplication.prototype, 'getAccount').and.returnValue(account);

Expand Down
4 changes: 4 additions & 0 deletions src/app/modules/login/services/azure.ad.b2c.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export class AzureAdB2CService {
return this.msal.getAccount().name;
}

isAdmin() {
return this.msal.getAccount()?.idToken?.extension_role === 'time-tracker-admin';
}

isLogin() {
return this.msal.getAccount() ? true : false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,60 @@
import { AzureAdB2CService } from 'src/app/modules/login/services/azure.ad.b2c.service';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { SidebarComponent } from './sidebar.component';

describe('SidebarComponent', () => {
let component: SidebarComponent;
let fixture: ComponentFixture<SidebarComponent>;
let azureAdB2CServiceStubInjected;

const azureAdB2CServiceStub = {
isLogin() {
return true;
},
isAdmin() {
return true;
}
};

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SidebarComponent ]
declarations: [ SidebarComponent ],
providers: [
{ providers: AzureAdB2CService, useValue: azureAdB2CServiceStub},
]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(SidebarComponent);
azureAdB2CServiceStubInjected = TestBed.inject(AzureAdB2CService);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should be created', () => {
spyOn(azureAdB2CServiceStubInjected, 'isAdmin').and.returnValue(false);
expect(component).toBeTruthy();
});

it('admin users have five menu items', () => {
spyOn(azureAdB2CServiceStubInjected, 'isAdmin').and.returnValue(true);

component.getItemsSidebar();
const menuItems = component.itemsSidebar;

expect(menuItems.length).toBe(5);
});

it('non admin users have two menu items', () => {
spyOn(azureAdB2CServiceStubInjected, 'isAdmin').and.returnValue(false);

component.getItemsSidebar();
const menuItems = component.itemsSidebar;

expect(menuItems.length).toBe(2);
});

});
24 changes: 16 additions & 8 deletions src/app/modules/shared/components/sidebar/sidebar.component.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AzureAdB2CService } from 'src/app/modules/login/services/azure.ad.b2c.service';
import { Component, OnInit } from '@angular/core';
import { ItemSidebar } from './models/item-sidebar.model';
@Component({
Expand All @@ -8,19 +9,26 @@ import { ItemSidebar } from './models/item-sidebar.model';
export class SidebarComponent implements OnInit {
itemsSidebar: ItemSidebar[] = [];

constructor() {}
constructor(private azureAdB2CService: AzureAdB2CService) {}

ngOnInit(): void {
this.getItemsSidebar();
}

getItemsSidebar() {
this.itemsSidebar = [
{ route: '/time-clock', icon: 'fas fa-clock', text: 'Time Clock' },
{ route: '/time-entries', icon: 'fas fa-list-alt', text: 'Time Entries' },
{ route: '/reports', icon: 'fas fa-chart-pie', text: 'Reports' },
{ route: '/activities-management', icon: 'fas fa-file-alt', text: 'Activities' },
{ route: '/customers-management', icon: 'fas fa-user', text: 'Customers' },
];
if (this.azureAdB2CService.isAdmin()) {
this.itemsSidebar = [
{ route: '/time-clock', icon: 'fas fa-clock', text: 'Time Clock' },
{ route: '/time-entries', icon: 'fas fa-list-alt', text: 'Time Entries' },
{ route: '/reports', icon: 'fas fa-chart-pie', text: 'Reports' },
{ route: '/activities-management', icon: 'fas fa-file-alt', text: 'Activities' },
{ route: '/customers-management', icon: 'fas fa-user', text: 'Customers' },
];
} else {
this.itemsSidebar = [
{ route: '/time-clock', icon: 'fas fa-clock', text: 'Time Clock' },
{ route: '/time-entries', icon: 'fas fa-list-alt', text: 'Time Entries' },
];
}
}
}