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
6 changes: 5 additions & 1 deletion admin/src/app/header/header.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<header class="app-header app-header--solid" id="header">
<nav class="navbar navbar-expand-md justify-content-between">
<a class="navbar-brand" title="Forest Operations Map - FOM" routerLink="/home">
<a class="navbar-brand"
title="Forest Operations Map - FOM"
[routerLink]="!isAdminRoleOnly() ? '/home' : null"
[style.pointerEvents]="!isAdminRoleOnly() ? 'auto' : 'none'"
[attr.tabindex]="!isAdminRoleOnly() ? 0 : -1">
<span class="navbar-brand__title">
Forest Operations Map - Admin
</span>
Expand Down
12 changes: 12 additions & 0 deletions admin/src/app/header/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,25 @@ export class HeaderComponent implements OnInit {
this.user = this.cognitoService.getUser();
}

isAdminRoleOnly(): boolean {
return this.user && this.user.isAdmin && !this.user.isMinistry && !this.user.isForestClient;
}

ngOnInit() {
// user is not authorized.
if (!this.user || !this.user.isAuthorizedForAdminSite()) {
// If on not-authorized page, or if just logged out, don't redirect to not-authorized page as would cause an infinite loop.
if (window.location.href.indexOf('/not-authorized') == -1 && window.location.href.indexOf("loggedout=true") == -1) {
this.router.navigate(['/not-authorized']);
}
}

// user has admin role only
if (this.user && this.isAdminRoleOnly()) {
this.router.navigate(['/analytics-dashboard']);
return;
}

}

async navigateToLogout() {
Expand Down
14 changes: 13 additions & 1 deletion admin/src/core/services/mock-user.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { User } from "@utility/security/user";

export function getFakeUser():User {
const userType:string = 'AllAccess'; // NoAccess, ForestClient, Ministry, AllAccess
const userType:string = 'AllAccess'; // NoAccess, ForestClient, Ministry, AdminOnly, AllAccess
switch (userType) {
case 'NoAccess':
return getFakeNoAccessUser();
case 'ForestClient':
return getFakeForestClientUser();
case 'Ministry':
return getFakeMinistryUser();
case 'AdminOnly':
return getFakeAdminOnlyUser();
case 'AllAccess':
return getFakeAllAccessUser();
default:
Expand Down Expand Up @@ -57,3 +59,13 @@ export function getFakeAllAccessUser(): User {
user.clientIds.push('00132188');
return user;
}

export function getFakeAdminOnlyUser(): User {
const user = new User();
user.userName = 'fakeAdminOnlyUser';
user.displayName = 'Admin Only User';
user.isAdmin = true;
user.isMinistry = false;
user.isForestClient = false;
return user;
}
8 changes: 4 additions & 4 deletions api/src/core/security/admin.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ describe('AdminOperationGuard', () => {

beforeEach(() => {
guard = new AdminOperationGuard();
mockRequest = { headers: { user: { isAuthorizedForAdminOperation: jest.fn() } } };
mockRequest = { headers: { user: { isAdmin: false } } };
mockContext = {
switchToHttp: () => ({ getRequest: () => mockRequest })
} as Partial<ExecutionContext>;
});

it('should allow access if user is authorized', () => {
mockRequest.headers.user.isAuthorizedForAdminOperation.mockReturnValue(true);
mockRequest.headers.user.isAdmin = true;
expect(guard.canActivate(mockContext as ExecutionContext)).toBe(true);
});

it('should throw ForbiddenException if user is not authorized', () => {
mockRequest.headers.user.isAuthorizedForAdminOperation.mockReturnValue(false);
mockRequest.headers.user.isAdmin = false;
expect(() => guard.canActivate(mockContext as ExecutionContext)).toThrow(ForbiddenException);
});

Expand All @@ -29,7 +29,7 @@ describe('AdminOperationGuard', () => {
expect(() => guard.canActivate(mockContext as ExecutionContext)).toThrow(ForbiddenException);
});

it('should throw ForbiddenException if user object cannot determine isAuthorizedForAdminOperation', () => {
it('should throw ForbiddenException if user object is missing isAdmin', () => {
mockRequest.headers.user = {};
expect(() => guard.canActivate(mockContext as ExecutionContext)).toThrow(ForbiddenException);
});
Expand Down
5 changes: 3 additions & 2 deletions api/src/core/security/admin.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import {
Injectable,
ForbiddenException,
} from '@nestjs/common';
import { User } from '@utility/security/user';

@Injectable()
export class AdminOperationGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = request.headers['user'];
const user: User = request.headers['user'];

if (!user?.isAuthorizedForAdminOperation?.()) {
if (!user?.isAdmin) {
throw new ForbiddenException();
}
return true;
Expand Down
8 changes: 3 additions & 5 deletions libs/utility/src/security/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ export class User {
isForestClient: boolean = false;
isAdmin: boolean = false;
clientIds: string[] = [];

isAuthorizedForAdminOperation():boolean {
return this.isAdmin;
}

isAuthorizedForAdminSite():boolean {
return this.isMinistry || this.isForestClient;
// A user is authorized for FOM admin site if it has any of the roles (ministry, forest client, or admin).
// However, curently for admin role the user can only access Analytics Dashboard.
return this.isMinistry || this.isForestClient || this.isAdmin;
}

isAuthorizedForClientId(clientId:string):boolean {
Expand Down
Loading