Skip to content

Commit 2a04181

Browse files
authored
Merge pull request #307 from ioet/297-add-extra-features-to-projects-table
fix: #205 adding checks to restrict access for admin and non-admin users
2 parents 6078572 + 8557dcc commit 2a04181

File tree

9 files changed

+168
-23
lines changed

9 files changed

+168
-23
lines changed

src/app/app-routing.module.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { AdminGuard } from './guards/admin-guard/admin-guard';
12
import { NgModule } from '@angular/core';
23
import { Routes, RouterModule } from '@angular/router';
34

4-
import { AzureGuardService } from './guards/azure-guard.service';
5+
import { LoginGuard } from './guards/login-guard/login.guard';
56
import { ReportsComponent } from './modules/reports/pages/reports.component';
67
import { TimeClockComponent } from './modules/time-clock/pages/time-clock.component';
78
import { TimeEntriesComponent } from './modules/time-entries/pages/time-entries.component';
@@ -14,13 +15,13 @@ const routes: Routes = [
1415
{
1516
path: '',
1617
component: HomeComponent,
17-
canActivate: [AzureGuardService],
18+
canActivate: [LoginGuard],
1819
children: [
19-
{ path: 'reports', component: ReportsComponent },
20+
{ path: 'reports', canActivate: [AdminGuard], component: ReportsComponent },
2021
{ path: 'time-clock', component: TimeClockComponent },
2122
{ path: 'time-entries', component: TimeEntriesComponent },
2223
{ path: 'activities-management', component: ActivitiesManagementComponent },
23-
{ path: 'customers-management', component: CustomerComponent },
24+
{ path: 'customers-management', canActivate: [AdminGuard], component: CustomerComponent },
2425
{ path: '', pathMatch: 'full', redirectTo: 'time-clock' },
2526
],
2627
},
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Injectable } from '@angular/core';
2+
import { Router, CanActivate } from '@angular/router';
3+
import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service';
4+
5+
@Injectable({
6+
providedIn: 'root'
7+
})
8+
export class AdminGuard implements CanActivate {
9+
10+
constructor(private azureAdB2CService: AzureAdB2CService, private router: Router) { }
11+
12+
canActivate() {
13+
if (this.azureAdB2CService.isAdmin()) {
14+
return true;
15+
} else {
16+
this.router.navigate(['login']);
17+
return false;
18+
}
19+
}
20+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { AdminGuard } from './admin-guard';
2+
import { TestBed, inject } from '@angular/core/testing';
3+
import { RouterTestingModule } from '@angular/router/testing';
4+
import { Router } from '@angular/router';
5+
6+
import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service';
7+
8+
9+
describe('AdminGuard', () => {
10+
11+
let adminGuard: AdminGuard;
12+
let azureAdB2CService: AzureAdB2CService;
13+
14+
const azureAdB2CServiceStub = {
15+
isLogin() {
16+
return true;
17+
},
18+
isAdmin() {
19+
return true;
20+
}
21+
};
22+
23+
beforeEach(() => {
24+
TestBed.configureTestingModule({
25+
imports: [ RouterTestingModule ],
26+
providers: [
27+
{ providers: AzureAdB2CService, useValue: azureAdB2CServiceStub},
28+
]
29+
});
30+
adminGuard = TestBed.inject(AdminGuard);
31+
azureAdB2CService = TestBed.inject(AzureAdB2CService);
32+
});
33+
34+
it('should be created', () => {
35+
expect(adminGuard).toBeTruthy();
36+
});
37+
38+
it('can activate the route when user is logged-in', () => {
39+
spyOn(azureAdB2CService, 'isAdmin').and.returnValue(true);
40+
41+
const canActivate = adminGuard.canActivate();
42+
43+
expect(azureAdB2CService.isAdmin).toHaveBeenCalled();
44+
expect(canActivate).toEqual(true);
45+
});
46+
47+
it('can not active the route and is redirected to login if user is not logged-in', inject([Router], (router: Router) => {
48+
spyOn(azureAdB2CService, 'isAdmin').and.returnValue(false);
49+
spyOn(router, 'navigate').and.stub();
50+
51+
const canActivate = adminGuard.canActivate();
52+
53+
expect(azureAdB2CService.isAdmin).toHaveBeenCalled();
54+
expect(canActivate).toEqual(false);
55+
expect(router.navigate).toHaveBeenCalledWith(['login']);
56+
}));
57+
58+
});

src/app/guards/azure-guard.service.spec.ts renamed to src/app/guards/login-guard/login.guard.spec.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { TestBed, inject } from '@angular/core/testing';
22
import { RouterTestingModule } from '@angular/router/testing';
33
import { Router } from '@angular/router';
44

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

88

9-
describe('AzureGuardService', () => {
10-
let service: AzureGuardService;
9+
describe('LoginGuard', () => {
10+
11+
let loginGuard: LoginGuard;
1112
let azureAdB2CService: AzureAdB2CService;
1213
const azureAdB2CServiceStub = {
1314
isLogin() {
@@ -21,25 +22,25 @@ describe('AzureGuardService', () => {
2122
{ providers: AzureAdB2CService, useValue: azureAdB2CServiceStub},
2223
]
2324
});
24-
service = TestBed.inject(AzureGuardService);
25+
loginGuard = TestBed.inject(LoginGuard);
2526
azureAdB2CService = TestBed.inject(AzureAdB2CService);
2627
});
2728

2829
it('should be created', () => {
29-
expect(service).toBeTruthy();
30+
expect(loginGuard).toBeTruthy();
3031
});
3132

3233
it('can activate the route when user is logged-in', () => {
3334
spyOn(azureAdB2CService, 'isLogin').and.returnValue(true);
34-
const canActivate = service.canActivate();
35+
const canActivate = loginGuard.canActivate();
3536
expect(azureAdB2CService.isLogin).toHaveBeenCalled();
3637
expect(canActivate).toEqual(true);
3738
});
3839

3940
it('can not active the route and is redirected to login if user is not logged-in', inject([Router], (router: Router) => {
4041
spyOn(azureAdB2CService, 'isLogin').and.returnValue(false);
4142
spyOn(router, 'navigate').and.stub();
42-
const canActivate = service.canActivate();
43+
const canActivate = loginGuard.canActivate();
4344
expect(azureAdB2CService.isLogin).toHaveBeenCalled();
4445
expect(canActivate).toEqual(false);
4546
expect(router.navigate).toHaveBeenCalledWith(['login']);

src/app/guards/azure-guard.service.ts renamed to src/app/guards/login-guard/login.guard.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Injectable } from '@angular/core';
22
import { Router, CanActivate } from '@angular/router';
3-
import { AzureAdB2CService } from '../modules/login/services/azure.ad.b2c.service';
3+
import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service';
44

55
@Injectable({
66
providedIn: 'root'
77
})
8-
export class AzureGuardService implements CanActivate {
8+
export class LoginGuard implements CanActivate {
99

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

src/app/modules/login/services/azure.ad.b2c.service.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ describe('AzureAdB2CService', () => {
5454
expect(name).toEqual(account.name);
5555
});
5656

57+
it('isAdmin false when extension_role !== time-tracker-admin', () => {
58+
spyOn(UserAgentApplication.prototype, 'getAccount').and.returnValue(account);
59+
60+
const isAdmin = service.isAdmin();
61+
62+
expect(isAdmin).toEqual(false);
63+
});
64+
65+
it('isAdmin when extension_role === time-tracker-admin', () => {
66+
const adminAccount = account;
67+
adminAccount.idToken.extension_role = 'time-tracker-admin';
68+
spyOn(UserAgentApplication.prototype, 'getAccount').and.returnValue(adminAccount);
69+
70+
const isAdmin = service.isAdmin();
71+
72+
expect(isAdmin).toBeTruthy();
73+
});
74+
5775
it('isLogin returns true if UserAgentApplication has a defined Account', () => {
5876
spyOn(UserAgentApplication.prototype, 'getAccount').and.returnValue(account);
5977

src/app/modules/login/services/azure.ad.b2c.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export class AzureAdB2CService {
3333
return this.msal.getAccount().name;
3434
}
3535

36+
isAdmin() {
37+
return this.msal.getAccount()?.idToken?.extension_role === 'time-tracker-admin';
38+
}
39+
3640
isLogin() {
3741
return this.msal.getAccount() ? true : false;
3842
}
Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,60 @@
1+
import { AzureAdB2CService } from 'src/app/modules/login/services/azure.ad.b2c.service';
12
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
23

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

56
describe('SidebarComponent', () => {
67
let component: SidebarComponent;
78
let fixture: ComponentFixture<SidebarComponent>;
9+
let azureAdB2CServiceStubInjected;
10+
11+
const azureAdB2CServiceStub = {
12+
isLogin() {
13+
return true;
14+
},
15+
isAdmin() {
16+
return true;
17+
}
18+
};
819

920
beforeEach(async(() => {
1021
TestBed.configureTestingModule({
11-
declarations: [ SidebarComponent ]
22+
declarations: [ SidebarComponent ],
23+
providers: [
24+
{ providers: AzureAdB2CService, useValue: azureAdB2CServiceStub},
25+
]
1226
})
1327
.compileComponents();
1428
}));
1529

1630
beforeEach(() => {
1731
fixture = TestBed.createComponent(SidebarComponent);
32+
azureAdB2CServiceStubInjected = TestBed.inject(AzureAdB2CService);
1833
component = fixture.componentInstance;
1934
fixture.detectChanges();
2035
});
2136

2237
it('should be created', () => {
38+
spyOn(azureAdB2CServiceStubInjected, 'isAdmin').and.returnValue(false);
2339
expect(component).toBeTruthy();
2440
});
41+
42+
it('admin users have five menu items', () => {
43+
spyOn(azureAdB2CServiceStubInjected, 'isAdmin').and.returnValue(true);
44+
45+
component.getItemsSidebar();
46+
const menuItems = component.itemsSidebar;
47+
48+
expect(menuItems.length).toBe(5);
49+
});
50+
51+
it('non admin users have two menu items', () => {
52+
spyOn(azureAdB2CServiceStubInjected, 'isAdmin').and.returnValue(false);
53+
54+
component.getItemsSidebar();
55+
const menuItems = component.itemsSidebar;
56+
57+
expect(menuItems.length).toBe(2);
58+
});
59+
2560
});
Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AzureAdB2CService } from 'src/app/modules/login/services/azure.ad.b2c.service';
12
import { Component, OnInit } from '@angular/core';
23
import { ItemSidebar } from './models/item-sidebar.model';
34
@Component({
@@ -8,19 +9,26 @@ import { ItemSidebar } from './models/item-sidebar.model';
89
export class SidebarComponent implements OnInit {
910
itemsSidebar: ItemSidebar[] = [];
1011

11-
constructor() {}
12+
constructor(private azureAdB2CService: AzureAdB2CService) {}
1213

1314
ngOnInit(): void {
1415
this.getItemsSidebar();
1516
}
1617

1718
getItemsSidebar() {
18-
this.itemsSidebar = [
19-
{ route: '/time-clock', icon: 'fas fa-clock', text: 'Time Clock' },
20-
{ route: '/time-entries', icon: 'fas fa-list-alt', text: 'Time Entries' },
21-
{ route: '/reports', icon: 'fas fa-chart-pie', text: 'Reports' },
22-
{ route: '/activities-management', icon: 'fas fa-file-alt', text: 'Activities' },
23-
{ route: '/customers-management', icon: 'fas fa-user', text: 'Customers' },
24-
];
19+
if (this.azureAdB2CService.isAdmin()) {
20+
this.itemsSidebar = [
21+
{ route: '/time-clock', icon: 'fas fa-clock', text: 'Time Clock' },
22+
{ route: '/time-entries', icon: 'fas fa-list-alt', text: 'Time Entries' },
23+
{ route: '/reports', icon: 'fas fa-chart-pie', text: 'Reports' },
24+
{ route: '/activities-management', icon: 'fas fa-file-alt', text: 'Activities' },
25+
{ route: '/customers-management', icon: 'fas fa-user', text: 'Customers' },
26+
];
27+
} else {
28+
this.itemsSidebar = [
29+
{ route: '/time-clock', icon: 'fas fa-clock', text: 'Time Clock' },
30+
{ route: '/time-entries', icon: 'fas fa-list-alt', text: 'Time Entries' },
31+
];
32+
}
2533
}
2634
}

0 commit comments

Comments
 (0)