Skip to content

Commit 916d7fc

Browse files
authored
Merge pull request DSpace#1805 from atmire/w2p-92900_Admin_options_dont_appear_after_Shibboleth_authentication_PR
Admin options don't appear after Shibboleth authentication
2 parents e7dc5f8 + 2ac0bcd commit 916d7fc

13 files changed

Lines changed: 229 additions & 224 deletions

scripts/serve.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ const appConfig: AppConfig = buildAppConfig();
77

88
/**
99
* Calls `ng serve` with the following arguments configured for the UI in the app config: host, port, nameSpace, ssl
10+
* Any CLI arguments given to this script are patched through to `ng serve` as well.
1011
*/
1112
child.spawn(
12-
`ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl}`,
13+
`ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl} ${process.argv.slice(2).join(' ')}`,
1314
{ stdio: 'inherit', shell: true }
1415
);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<nav @slideHorizontal class="navbar navbar-dark p-0"
1+
<nav class="navbar navbar-dark p-0"
22
[ngClass]="{'active': sidebarOpen, 'inactive': sidebarClosed}"
33
[@slideSidebar]="{
44
value: (!(sidebarExpanded | async) ? 'collapsed' : 'expanded'),

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Component, HostListener, Injector, OnInit } from '@angular/core';
22
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
33
import { debounceTime, distinctUntilChanged, first, map, withLatestFrom } from 'rxjs/operators';
44
import { AuthService } from '../../core/auth/auth.service';
5-
import { slideHorizontal, slideSidebar } from '../../shared/animations/slide';
5+
import { slideSidebar } from '../../shared/animations/slide';
66
import { MenuComponent } from '../../shared/menu/menu.component';
77
import { MenuService } from '../../shared/menu/menu.service';
88
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
@@ -18,7 +18,7 @@ import { ThemeService } from '../../shared/theme-support/theme.service';
1818
selector: 'ds-admin-sidebar',
1919
templateUrl: './admin-sidebar.component.html',
2020
styleUrls: ['./admin-sidebar.component.scss'],
21-
animations: [slideHorizontal, slideSidebar]
21+
animations: [slideSidebar]
2222
})
2323
export class AdminSidebarComponent extends MenuComponent implements OnInit {
2424
/**

src/app/core/auth/auth-request.service.spec.ts

Lines changed: 129 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,155 @@ import { TestScheduler } from 'rxjs/testing';
77
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
88
import { ShortLivedToken } from './models/short-lived-token.model';
99
import { RemoteData } from '../data/remote-data';
10+
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
11+
import objectContaining = jasmine.objectContaining;
12+
import { AuthStatus } from './models/auth-status.model';
13+
import { RestRequestMethod } from '../data/rest-request-method';
1014

1115
describe(`AuthRequestService`, () => {
1216
let halService: HALEndpointService;
1317
let endpointURL: string;
18+
let requestID: string;
1419
let shortLivedToken: ShortLivedToken;
1520
let shortLivedTokenRD: RemoteData<ShortLivedToken>;
1621
let requestService: RequestService;
1722
let rdbService: RemoteDataBuildService;
18-
let service: AuthRequestService;
23+
let service;
1924
let testScheduler;
2025

21-
class TestAuthRequestService extends AuthRequestService {
22-
constructor(
23-
hes: HALEndpointService,
24-
rs: RequestService,
25-
rdbs: RemoteDataBuildService
26-
) {
27-
super(hes, rs, rdbs);
28-
}
29-
30-
protected createShortLivedTokenRequest(href: string): PostRequest {
31-
return new PostRequest(this.requestService.generateRequestId(), href);
32-
}
26+
const status = new AuthStatus();
27+
28+
class TestAuthRequestService extends AuthRequestService {
29+
constructor(
30+
hes: HALEndpointService,
31+
rs: RequestService,
32+
rdbs: RemoteDataBuildService
33+
) {
34+
super(hes, rs, rdbs);
3335
}
3436

35-
const init = (cold: typeof TestScheduler.prototype.createColdObservable) => {
36-
endpointURL = 'https://rest.api/auth';
37-
shortLivedToken = Object.assign(new ShortLivedToken(), {
38-
value: 'some-token'
39-
});
40-
shortLivedTokenRD = createSuccessfulRemoteDataObject(shortLivedToken);
37+
protected createShortLivedTokenRequest(href: string): PostRequest {
38+
return new PostRequest(this.requestService.generateRequestId(), href);
39+
}
40+
}
41+
42+
const init = (cold: typeof TestScheduler.prototype.createColdObservable) => {
43+
endpointURL = 'https://rest.api/auth';
44+
requestID = 'requestID';
45+
shortLivedToken = Object.assign(new ShortLivedToken(), {
46+
value: 'some-token'
47+
});
48+
shortLivedTokenRD = createSuccessfulRemoteDataObject(shortLivedToken);
49+
50+
halService = jasmine.createSpyObj('halService', {
51+
'getEndpoint': cold('a', { a: endpointURL })
52+
});
53+
requestService = jasmine.createSpyObj('requestService', {
54+
'generateRequestId': requestID,
55+
'send': null,
56+
});
57+
rdbService = jasmine.createSpyObj('rdbService', {
58+
'buildFromRequestUUID': cold('a', { a: shortLivedTokenRD })
59+
});
60+
61+
service = new TestAuthRequestService(halService, requestService, rdbService);
62+
63+
spyOn(service as any, 'fetchRequest').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject(status) }));
64+
};
65+
66+
beforeEach(() => {
67+
testScheduler = new TestScheduler((actual, expected) => {
68+
expect(actual).toEqual(expected);
69+
});
70+
});
71+
72+
describe('REST request methods', () => {
73+
let options: HttpOptions;
4174

42-
halService = jasmine.createSpyObj('halService', {
43-
'getEndpoint': cold('a', { a: endpointURL })
75+
beforeEach(() => {
76+
options = Object.create({});
77+
});
78+
79+
describe('GET', () => {
80+
it('should send a GET request to the right endpoint and return the auth status', () => {
81+
testScheduler.run(({ cold, expectObservable, flush }) => {
82+
init(cold);
83+
84+
expectObservable(service.getRequest('method', options)).toBe('a', {
85+
a: objectContaining({ payload: status }),
86+
});
87+
flush();
88+
89+
expect(requestService.send).toHaveBeenCalledWith(objectContaining({
90+
uuid: requestID,
91+
href: endpointURL + '/method',
92+
method: RestRequestMethod.GET,
93+
body: undefined,
94+
options,
95+
}));
96+
expect((service as any).fetchRequest).toHaveBeenCalledWith(requestID);
97+
});
4498
});
45-
requestService = jasmine.createSpyObj('requestService', {
46-
'send': null
99+
100+
it('should send the request even if caller doesn\'t subscribe to the response', () => {
101+
testScheduler.run(({ cold, flush }) => {
102+
init(cold);
103+
104+
service.getRequest('method', options);
105+
flush();
106+
107+
expect(requestService.send).toHaveBeenCalledWith(objectContaining({
108+
uuid: requestID,
109+
href: endpointURL + '/method',
110+
method: RestRequestMethod.GET,
111+
body: undefined,
112+
options,
113+
}));
114+
expect((service as any).fetchRequest).toHaveBeenCalledWith(requestID);
115+
});
47116
});
48-
rdbService = jasmine.createSpyObj('rdbService', {
49-
'buildFromRequestUUID': cold('a', { a: shortLivedTokenRD })
117+
});
118+
119+
describe('POST', () => {
120+
it('should send a POST request to the right endpoint and return the auth status', () => {
121+
testScheduler.run(({ cold, expectObservable, flush }) => {
122+
init(cold);
123+
124+
expectObservable(service.postToEndpoint('method', { content: 'something' }, options)).toBe('a', {
125+
a: objectContaining({ payload: status }),
126+
});
127+
flush();
128+
129+
expect(requestService.send).toHaveBeenCalledWith(objectContaining({
130+
uuid: requestID,
131+
href: endpointURL + '/method',
132+
method: RestRequestMethod.POST,
133+
body: { content: 'something' },
134+
options,
135+
}));
136+
expect((service as any).fetchRequest).toHaveBeenCalledWith(requestID);
137+
});
50138
});
51139

52-
service = new TestAuthRequestService(halService, requestService, rdbService);
53-
};
140+
it('should send the request even if caller doesn\'t subscribe to the response', () => {
141+
testScheduler.run(({ cold, flush }) => {
142+
init(cold);
54143

55-
beforeEach(() => {
56-
testScheduler = new TestScheduler((actual, expected) => {
57-
expect(actual).toEqual(expected);
144+
service.postToEndpoint('method', { content: 'something' }, options);
145+
flush();
146+
147+
expect(requestService.send).toHaveBeenCalledWith(objectContaining({
148+
uuid: requestID,
149+
href: endpointURL + '/method',
150+
method: RestRequestMethod.POST,
151+
body: { content: 'something' },
152+
options,
153+
}));
154+
expect((service as any).fetchRequest).toHaveBeenCalledWith(requestID);
155+
});
58156
});
59157
});
158+
});
60159

61160
describe(`getShortlivedToken`, () => {
62161
it(`should call createShortLivedTokenRequest with the url for the endpoint`, () => {

src/app/core/auth/auth-request.service.ts

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Observable } from 'rxjs';
2-
import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators';
2+
import { distinctUntilChanged, filter, map, switchMap, tap, take } from 'rxjs/operators';
33
import { HALEndpointService } from '../shared/hal-endpoint.service';
44
import { RequestService } from '../data/request.service';
55
import { isNotEmpty } from '../../shared/empty.util';
@@ -27,8 +27,13 @@ export abstract class AuthRequestService {
2727
) {
2828
}
2929

30-
protected fetchRequest(request: RestRequest, ...linksToFollow: FollowLinkConfig<AuthStatus>[]): Observable<RemoteData<AuthStatus>> {
31-
return this.rdbService.buildFromRequestUUID<AuthStatus>(request.uuid, ...linksToFollow).pipe(
30+
/**
31+
* Fetch the response to a request from the cache, once it's completed.
32+
* @param requestId the UUID of the request for which to retrieve the response
33+
* @protected
34+
*/
35+
protected fetchRequest(requestId: string, ...linksToFollow: FollowLinkConfig<AuthStatus>[]): Observable<RemoteData<AuthStatus>> {
36+
return this.rdbService.buildFromRequestUUID<AuthStatus>(requestId, ...linksToFollow).pipe(
3237
getFirstCompletedRemoteData(),
3338
);
3439
}
@@ -44,28 +49,48 @@ export abstract class AuthRequestService {
4449
return url;
4550
}
4651

52+
/**
53+
* Send a POST request to an authentication endpoint
54+
* @param method the method to send to (e.g. 'status')
55+
* @param body the data to send (optional)
56+
* @param options the HTTP options for the request
57+
*/
4758
public postToEndpoint(method: string, body?: any, options?: HttpOptions): Observable<RemoteData<AuthStatus>> {
48-
return this.halService.getEndpoint(this.linkName).pipe(
59+
const requestId = this.requestService.generateRequestId();
60+
61+
this.halService.getEndpoint(this.linkName).pipe(
4962
filter((href: string) => isNotEmpty(href)),
5063
map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
5164
distinctUntilChanged(),
52-
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL, body, options)),
53-
tap((request: PostRequest) => this.requestService.send(request)),
54-
mergeMap((request: PostRequest) => this.fetchRequest(request)),
55-
distinctUntilChanged());
65+
map((endpointURL: string) => new PostRequest(requestId, endpointURL, body, options)),
66+
take(1)
67+
).subscribe((request: PostRequest) => {
68+
this.requestService.send(request);
69+
});
70+
71+
return this.fetchRequest(requestId);
5672
}
5773

74+
/**
75+
* Send a GET request to an authentication endpoint
76+
* @param method the method to send to (e.g. 'status')
77+
* @param options the HTTP options for the request
78+
*/
5879
public getRequest(method: string, options?: HttpOptions, ...linksToFollow: FollowLinkConfig<any>[]): Observable<RemoteData<AuthStatus>> {
59-
return this.halService.getEndpoint(this.linkName).pipe(
80+
const requestId = this.requestService.generateRequestId();
81+
82+
this.halService.getEndpoint(this.linkName).pipe(
6083
filter((href: string) => isNotEmpty(href)),
6184
map((endpointURL) => this.getEndpointByMethod(endpointURL, method, ...linksToFollow)),
6285
distinctUntilChanged(),
63-
map((endpointURL: string) => new GetRequest(this.requestService.generateRequestId(), endpointURL, undefined, options)),
64-
tap((request: GetRequest) => this.requestService.send(request)),
65-
mergeMap((request: GetRequest) => this.fetchRequest(request, ...linksToFollow)),
66-
distinctUntilChanged());
67-
}
86+
map((endpointURL: string) => new GetRequest(requestId, endpointURL, undefined, options)),
87+
take(1)
88+
).subscribe((request: GetRequest) => {
89+
this.requestService.send(request);
90+
});
6891

92+
return this.fetchRequest(requestId, ...linksToFollow);
93+
}
6994
/**
7095
* Factory function to create the request object to send. This needs to be a POST client side and
7196
* a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow

src/app/core/auth/auth.reducer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
import { AuthTokenInfo } from './models/auth-token-info.model';
1818
import { AuthMethod } from './models/auth.method';
1919
import { AuthMethodType } from './models/auth.method-type';
20+
import { StoreActionTypes } from '../../store.actions';
2021

2122
/**
2223
* The auth state.
@@ -251,6 +252,11 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
251252
idle: false,
252253
});
253254

255+
case StoreActionTypes.REHYDRATE:
256+
return Object.assign({}, state, {
257+
blocking: true,
258+
});
259+
254260
default:
255261
return state;
256262
}

src/app/init.service.spec.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { inject, TestBed, waitForAsync } from '@angular/core/testing';
55
import { MetadataService } from './core/metadata/metadata.service';
66
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
77
import { CommonModule } from '@angular/common';
8-
import { StoreModule } from '@ngrx/store';
8+
import { Store, StoreModule } from '@ngrx/store';
99
import { authReducer } from './core/auth/auth.reducer';
1010
import { storeModuleConfig } from './app.reducer';
1111
import { AngularticsProviderMock } from './shared/mocks/angulartics-provider.service.mock';
@@ -31,6 +31,7 @@ import { getMockThemeService } from './shared/mocks/theme-service.mock';
3131
import objectContaining = jasmine.objectContaining;
3232
import createSpyObj = jasmine.createSpyObj;
3333
import SpyObj = jasmine.SpyObj;
34+
import { getTestScheduler } from 'jasmine-marbles';
3435

3536
let spy: SpyObj<any>;
3637

@@ -124,6 +125,15 @@ describe('InitService', () => {
124125
let metadataServiceSpy;
125126
let breadcrumbsServiceSpy;
126127

128+
const BLOCKING = {
129+
t: { core: { auth: { blocking: true } } },
130+
f: { core: { auth: { blocking: false } } },
131+
};
132+
const BOOLEAN = {
133+
t: true,
134+
f: false,
135+
};
136+
127137
beforeEach(waitForAsync(() => {
128138
correlationIdServiceSpy = jasmine.createSpyObj('correlationIdServiceSpy', [
129139
'initCorrelationId',
@@ -182,6 +192,18 @@ describe('InitService', () => {
182192
expect(breadcrumbsServiceSpy.listenForRouteChanges).toHaveBeenCalledTimes(1);
183193
}));
184194
});
195+
196+
describe('authenticationReady', () => {
197+
it('should emit & complete the first time auth is unblocked', () => {
198+
getTestScheduler().run(({ cold, expectObservable }) => {
199+
TestBed.overrideProvider(Store, { useValue: cold('t--t--f--t--f--', BLOCKING) });
200+
const service = TestBed.inject(InitService);
201+
202+
// @ts-ignore
203+
expectObservable(service.authenticationReady$()).toBe('------(f|)', BOOLEAN);
204+
});
205+
});
206+
});
185207
});
186208
});
187209

0 commit comments

Comments
 (0)