Skip to content

Commit cefe1bf

Browse files
authored
Merge pull request DSpace#2187 from mspalti/shibboleth-refresh
Shibboleth page update after authentication
2 parents d9f6386 + b1f3b78 commit cefe1bf

9 files changed

Lines changed: 130 additions & 4 deletions

File tree

src/app/core/auth/auth.actions.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const AuthActionTypes = {
1717
AUTHENTICATED_SUCCESS: type('dspace/auth/AUTHENTICATED_SUCCESS'),
1818
CHECK_AUTHENTICATION_TOKEN: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN'),
1919
CHECK_AUTHENTICATION_TOKEN_COOKIE: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN_COOKIE'),
20+
SET_AUTH_COOKIE_STATUS: type('dspace/auth/SET_AUTH_COOKIE_STATUS'),
2021
RETRIEVE_AUTH_METHODS: type('dspace/auth/RETRIEVE_AUTH_METHODS'),
2122
RETRIEVE_AUTH_METHODS_SUCCESS: type('dspace/auth/RETRIEVE_AUTH_METHODS_SUCCESS'),
2223
RETRIEVE_AUTH_METHODS_ERROR: type('dspace/auth/RETRIEVE_AUTH_METHODS_ERROR'),
@@ -150,6 +151,19 @@ export class CheckAuthenticationTokenCookieAction implements Action {
150151
public type: string = AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE;
151152
}
152153

154+
/**
155+
* Sets the authentication cookie status to flag an external authentication response.
156+
*/
157+
export class SetAuthCookieStatus implements Action {
158+
public type: string = AuthActionTypes.SET_AUTH_COOKIE_STATUS;
159+
160+
payload = false;
161+
162+
constructor(exists: boolean) {
163+
this.payload = exists;
164+
}
165+
}
166+
153167
/**
154168
* Sign out.
155169
* @class LogOutAction
@@ -425,6 +439,7 @@ export type AuthActions
425439
| AuthenticationSuccessAction
426440
| CheckAuthenticationTokenAction
427441
| CheckAuthenticationTokenCookieAction
442+
| SetAuthCookieStatus
428443
| RedirectWhenAuthenticationIsRequiredAction
429444
| RedirectWhenTokenExpiredAction
430445
| AddAuthenticationMessageAction

src/app/core/auth/auth.effects.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,15 @@ describe('AuthEffects', () => {
214214
authenticated: true
215215
})
216216
);
217+
spyOn((authEffects as any).authService, 'setExternalAuthStatus');
217218
actions = hot('--a-', { a: { type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE } });
218219

219220
const expected = cold('--b-', { b: new RetrieveTokenAction() });
220221

221222
expect(authEffects.checkTokenCookie$).toBeObservable(expected);
222223
authEffects.checkTokenCookie$.subscribe(() => {
224+
expect(authServiceStub.setExternalAuthStatus).toHaveBeenCalled();
225+
expect(authServiceStub.isExternalAuthentication).toBeTrue();
223226
expect((authEffects as any).authorizationsService.invalidateAuthorizationsRequestCache).toHaveBeenCalled();
224227
});
225228
});

src/app/core/auth/auth.effects.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export class AuthEffects {
153153
return this.authService.checkAuthenticationCookie().pipe(
154154
map((response: AuthStatus) => {
155155
if (response.authenticated) {
156+
this.authService.setExternalAuthStatus(true);
156157
this.authorizationsService.invalidateAuthorizationsRequestCache();
157158
return new RetrieveTokenAction();
158159
} else {

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
AuthenticationErrorAction,
99
AuthenticationSuccessAction,
1010
CheckAuthenticationTokenAction,
11+
SetAuthCookieStatus,
1112
CheckAuthenticationTokenCookieAction,
1213
LogOutAction,
1314
LogOutErrorAction,
@@ -219,6 +220,28 @@ describe('authReducer', () => {
219220
expect(newState).toEqual(state);
220221
});
221222

223+
it('should set the authentication cookie status in response to a SET_AUTH_COOKIE_STATUS action', () => {
224+
initialState = {
225+
authenticated: true,
226+
loaded: false,
227+
blocking: false,
228+
loading: true,
229+
externalAuth: false,
230+
idle: false
231+
};
232+
const action = new SetAuthCookieStatus(true);
233+
const newState = authReducer(initialState, action);
234+
state = {
235+
authenticated: true,
236+
loaded: false,
237+
blocking: false,
238+
loading: true,
239+
externalAuth: true,
240+
idle: false
241+
};
242+
expect(newState).toEqual(state);
243+
});
244+
222245
it('should properly set the state, in response to a LOG_OUT action', () => {
223246
initialState = {
224247
authenticated: true,

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
RedirectWhenTokenExpiredAction,
1111
RefreshTokenSuccessAction,
1212
RetrieveAuthenticatedEpersonSuccessAction,
13-
RetrieveAuthMethodsSuccessAction,
13+
RetrieveAuthMethodsSuccessAction, SetAuthCookieStatus,
1414
SetRedirectUrlAction
1515
} from './auth.actions';
1616
// import models
@@ -59,6 +59,8 @@ export interface AuthState {
5959
// all authentication Methods enabled at the backend
6060
authMethods?: AuthMethod[];
6161

62+
externalAuth?: boolean,
63+
6264
// true when the current user is idle
6365
idle: boolean;
6466

@@ -73,6 +75,7 @@ const initialState: AuthState = {
7375
blocking: true,
7476
loading: false,
7577
authMethods: [],
78+
externalAuth: false,
7679
idle: false
7780
};
7881

@@ -104,6 +107,11 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
104107
loading: true,
105108
});
106109

110+
case AuthActionTypes.SET_AUTH_COOKIE_STATUS:
111+
return Object.assign({}, state, {
112+
externalAuth: (action as SetAuthCookieStatus).payload
113+
});
114+
107115
case AuthActionTypes.AUTHENTICATED_ERROR:
108116
case AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_ERROR:
109117
return Object.assign({}, state, {

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
import { CookieService } from '../services/cookie.service';
2626
import {
2727
getAuthenticatedUserId,
28-
getAuthenticationToken,
28+
getAuthenticationToken, getExternalAuthCookieStatus,
2929
getRedirectUrl,
3030
isAuthenticated,
3131
isAuthenticatedLoaded,
@@ -36,7 +36,7 @@ import { AppState } from '../../app.reducer';
3636
import {
3737
CheckAuthenticationTokenAction,
3838
RefreshTokenAction,
39-
ResetAuthenticationMessagesAction,
39+
ResetAuthenticationMessagesAction, SetAuthCookieStatus,
4040
SetRedirectUrlAction,
4141
SetUserAsIdleAction,
4242
UnsetUserAsIdleAction
@@ -156,6 +156,24 @@ export class AuthService {
156156
return this.store.pipe(select(isAuthenticatedLoaded));
157157
}
158158

159+
/**
160+
* Used to set the external authentication status when authenticating via an
161+
* external authentication system (e.g. Shibboleth).
162+
* @param external
163+
*/
164+
public setExternalAuthStatus(external: boolean) {
165+
this.store.dispatch(new SetAuthCookieStatus(external));
166+
}
167+
168+
/**
169+
* Returns true if an external authentication system (e.g. Shibboleth) is being used
170+
* for authentication. Returns false otherwise.
171+
*/
172+
public isExternalAuthentication(): Observable<boolean> {
173+
return this.store.pipe(
174+
select(getExternalAuthCookieStatus));
175+
}
176+
159177
/**
160178
* Returns the href link to authenticated user
161179
* @returns {string}

src/app/core/auth/selectors.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ const _getRedirectUrl = (state: AuthState) => state.redirectUrl;
116116

117117
const _getAuthenticationMethods = (state: AuthState) => state.authMethods;
118118

119+
const _getExternalAuthCookieStatus = (state: AuthState) => state.externalAuth;
120+
119121
/**
120122
* Returns true if the user is idle.
121123
* @function _isIdle
@@ -178,6 +180,16 @@ export const isAuthenticated = createSelector(getAuthState, _isAuthenticated);
178180
*/
179181
export const isAuthenticatedLoaded = createSelector(getAuthState, _isAuthenticatedLoaded);
180182

183+
/**
184+
* Returns the authentication cookie status. Expect to be true when external authentication
185+
* is used.
186+
* @function getExternalAuthCookieStatus
187+
* @param {AuthState} state
188+
* @param {any} props
189+
* @return {boolean}
190+
*/
191+
export const getExternalAuthCookieStatus = createSelector(getAuthState, _getExternalAuthCookieStatus);
192+
181193
/**
182194
* Returns true if the authentication request is loading.
183195
* @function isAuthenticationLoading

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class AuthServiceStub {
1717
token: AuthTokenInfo = new AuthTokenInfo('token_test');
1818
impersonating: string;
1919
private _tokenExpired = false;
20+
private _isExternalAuth = false;
2021
private redirectUrl;
2122

2223
constructor() {
@@ -122,6 +123,13 @@ export class AuthServiceStub {
122123
checkAuthenticationCookie() {
123124
return;
124125
}
126+
setExternalAuthStatus(externalCookie: boolean) {
127+
this._isExternalAuth = externalCookie;
128+
}
129+
130+
isExternalAuthentication(): Observable<boolean> {
131+
return observableOf(this._isExternalAuth);
132+
}
125133

126134
retrieveAuthMethodsFromAuthStatus(status: AuthStatus) {
127135
return observableOf(authMethodsMock);

src/modules/app/browser-init.service.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,21 @@ import { AuthService } from '../../app/core/auth/auth.service';
2626
import { ThemeService } from '../../app/shared/theme-support/theme.service';
2727
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
2828
import { coreSelector } from '../../app/core/core.selectors';
29-
import { find, map } from 'rxjs/operators';
29+
import { filter, find, map } from 'rxjs/operators';
3030
import { isNotEmpty } from '../../app/shared/empty.util';
3131
import { logStartupMessage } from '../../../startup-message';
3232
import { MenuService } from '../../app/shared/menu/menu.service';
33+
import { RootDataService } from '../../app/core/data/root-data.service';
34+
import { firstValueFrom, Subscription } from 'rxjs';
3335

3436
/**
3537
* Performs client-side initialization.
3638
*/
3739
@Injectable()
3840
export class BrowserInitService extends InitService {
41+
42+
sub: Subscription;
43+
3944
constructor(
4045
protected store: Store<AppState>,
4146
protected correlationIdService: CorrelationIdService,
@@ -51,6 +56,7 @@ export class BrowserInitService extends InitService {
5156
protected authService: AuthService,
5257
protected themeService: ThemeService,
5358
protected menuService: MenuService,
59+
private rootDataService: RootDataService
5460
) {
5561
super(
5662
store,
@@ -80,6 +86,7 @@ export class BrowserInitService extends InitService {
8086
return async () => {
8187
await this.loadAppState();
8288
this.checkAuthenticationToken();
89+
this.externalAuthCheck();
8390
this.initCorrelationId();
8491

8592
this.checkEnvironment();
@@ -134,4 +141,35 @@ export class BrowserInitService extends InitService {
134141
protected initGoogleAnalytics() {
135142
this.googleAnalyticsService.addTrackingIdToPage();
136143
}
144+
145+
/**
146+
* During an external authentication flow invalidate the SSR transferState
147+
* data in the cache. This allows the app to fetch fresh content.
148+
* @private
149+
*/
150+
private externalAuthCheck() {
151+
152+
this.sub = this.authService.isExternalAuthentication().pipe(
153+
filter((externalAuth: boolean) => externalAuth)
154+
).subscribe(() => {
155+
// Clear the transferState data.
156+
this.rootDataService.invalidateRootCache();
157+
this.authService.setExternalAuthStatus(false);
158+
}
159+
);
160+
161+
this.closeAuthCheckSubscription();
162+
}
163+
164+
/**
165+
* Unsubscribe the external authentication subscription
166+
* when authentication is no longer blocking.
167+
* @private
168+
*/
169+
private closeAuthCheckSubscription() {
170+
firstValueFrom(this.authenticationReady$()).then(() => {
171+
this.sub.unsubscribe();
172+
});
173+
}
174+
137175
}

0 commit comments

Comments
 (0)