Skip to content

Commit 3ed6646

Browse files
UoE/The admin panel should be displayed only to administrators and hidden from regular signed-in users
UoE/The admin panel should be displayed only to administrators and hidden from regular signed-in users
2 parents da74f6f + 1519028 commit 3ed6646

3 files changed

Lines changed: 85 additions & 9 deletions

File tree

src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ import {
1919
NgbModalRef,
2020
} from '@ng-bootstrap/ng-bootstrap';
2121
import { TranslateModule } from '@ngx-translate/core';
22-
import { of as observableOf } from 'rxjs';
22+
import {
23+
BehaviorSubject,
24+
of as observableOf,
25+
} from 'rxjs';
2326

2427
import { AuthService } from '../../core/auth/auth.service';
2528
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
29+
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
2630
import { ScriptDataService } from '../../core/data/processes/script-data.service';
2731
import { Item } from '../../core/shared/item.model';
2832
import { MenuService } from '../../shared/menu/menu.service';
@@ -105,6 +109,70 @@ describe('AdminSidebarComponent', () => {
105109
fixture.detectChanges();
106110
});
107111

112+
describe('authorization', () => {
113+
/**
114+
* Re-create the component so that its single ngOnInit runs under the stubs/spies configured by the
115+
* test, instead of the default detectChanges() from the outer beforeEach (which runs before them).
116+
*/
117+
const initFreshComponent = () => {
118+
fixture = TestBed.createComponent(AdminSidebarComponent);
119+
comp = fixture.componentInstance;
120+
comp.sections = observableOf([]);
121+
fixture.detectChanges();
122+
};
123+
124+
beforeEach(() => {
125+
spyOn(menuService, 'showMenu');
126+
spyOn(menuService, 'hideMenu');
127+
});
128+
129+
it('should show the admin menu for a user with an administrative role', () => {
130+
authorizationService.isAuthorized = jasmine.createSpy('isAuthorized').and.callFake((featureID: FeatureID) => {
131+
return observableOf(featureID === FeatureID.AdministratorOf);
132+
});
133+
134+
initFreshComponent();
135+
136+
expect(menuService.showMenu).toHaveBeenCalledWith(comp.menuID);
137+
expect(menuService.hideMenu).not.toHaveBeenCalled();
138+
});
139+
140+
it('should hide the admin menu for an authenticated non-admin user', () => {
141+
authorizationService.isAuthorized = jasmine.createSpy('isAuthorized').and.returnValue(observableOf(false));
142+
143+
initFreshComponent();
144+
145+
expect(menuService.showMenu).not.toHaveBeenCalled();
146+
expect(menuService.hideMenu).toHaveBeenCalledWith(comp.menuID);
147+
});
148+
149+
it('should hide the admin menu without requesting authorizations for an anonymous user', () => {
150+
spyOn(TestBed.inject(AuthService), 'isAuthenticated').and.returnValue(observableOf(false));
151+
authorizationService.isAuthorized = jasmine.createSpy('isAuthorized').and.returnValue(observableOf(false));
152+
153+
initFreshComponent();
154+
155+
expect(menuService.showMenu).not.toHaveBeenCalled();
156+
expect(menuService.hideMenu).toHaveBeenCalledWith(comp.menuID);
157+
expect(authorizationService.isAuthorized).not.toHaveBeenCalled();
158+
});
159+
160+
it('should reveal the admin menu once authentication resolves (guards against a one-shot take(1) regression)', () => {
161+
const authenticated$ = new BehaviorSubject<boolean>(false);
162+
spyOn(TestBed.inject(AuthService), 'isAuthenticated').and.returnValue(authenticated$);
163+
authorizationService.isAuthorized = jasmine.createSpy('isAuthorized').and.callFake((featureID: FeatureID) => {
164+
return observableOf(featureID === FeatureID.AdministratorOf);
165+
});
166+
167+
initFreshComponent();
168+
expect(menuService.hideMenu).toHaveBeenCalledWith(comp.menuID);
169+
170+
authenticated$.next(true);
171+
172+
expect(menuService.showMenu).toHaveBeenCalledWith(comp.menuID);
173+
});
174+
});
175+
108176
describe('startSlide', () => {
109177
describe('when expanding', () => {
110178
beforeEach(() => {

src/app/admin/admin-sidebar/admin-sidebar.component.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@ import {
1919
BehaviorSubject,
2020
combineLatest,
2121
Observable,
22+
of as observableOf,
2223
} from 'rxjs';
2324
import {
2425
debounceTime,
2526
distinctUntilChanged,
2627
first,
2728
map,
29+
switchMap,
2830
withLatestFrom,
2931
} from 'rxjs/operators';
3032

3133
import { AuthService } from '../../core/auth/auth.service';
3234
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
35+
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
3336
import { slideSidebar } from '../../shared/animations/slide';
3437
import { MenuComponent } from '../../shared/menu/menu.component';
3538
import { MenuService } from '../../shared/menu/menu.service';
@@ -108,12 +111,17 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
108111
*/
109112
ngOnInit(): void {
110113
super.ngOnInit();
111-
this.authService.isAuthenticated()
112-
.subscribe((loggedIn: boolean) => {
113-
if (loggedIn) {
114-
this.menuService.showMenu(this.menuID);
115-
}
116-
});
114+
this.authService.isAuthenticated().pipe(
115+
switchMap((isAuthenticated: boolean) => isAuthenticated
116+
? this.authorizationService.isAuthorized(FeatureID.AdministratorOf)
117+
: observableOf(false)),
118+
).subscribe((isSiteAdmin: boolean) => {
119+
if (isSiteAdmin) {
120+
this.menuService.showMenu(this.menuID);
121+
} else {
122+
this.menuService.hideMenu(this.menuID);
123+
}
124+
});
117125
this.menuCollapsed.pipe(first())
118126
.subscribe((collapsed: boolean) => {
119127
this.sidebarOpen = !collapsed;

src/app/shared/testing/menu-service.stub.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ export class MenuServiceStub {
3636
collapseMenu(): void { /***/
3737
}
3838

39-
showMenu(): void { /***/
39+
showMenu(menuID?: MenuID): void { /***/
4040
}
4141

42-
hideMenu(): void { /***/
42+
hideMenu(menuID?: MenuID): void { /***/
4343
}
4444

4545
expandMenuPreview(): void { /***/

0 commit comments

Comments
 (0)