forked from hazza/dspace-angular
Refactored components' name and added missing tests
This commit is contained in:
@@ -7,7 +7,7 @@ import { type } from '../../shared/ngrx/type';
|
||||
// import models
|
||||
import { EPerson } from '../eperson/models/eperson.model';
|
||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||
import { AuthMethodModel } from './models/auth-method.model';
|
||||
import { AuthMethod } from './models/auth.method';
|
||||
import { AuthStatus } from './models/auth-status.model';
|
||||
|
||||
export const AuthActionTypes = {
|
||||
@@ -340,9 +340,9 @@ export class RetrieveAuthMethodsAction implements Action {
|
||||
*/
|
||||
export class RetrieveAuthMethodsSuccessAction implements Action {
|
||||
public type: string = AuthActionTypes.RETRIEVE_AUTH_METHODS_SUCCESS;
|
||||
payload: AuthMethodModel[];
|
||||
payload: AuthMethod[];
|
||||
|
||||
constructor(authMethods: AuthMethodModel[] ) {
|
||||
constructor(authMethods: AuthMethod[] ) {
|
||||
this.payload = authMethods;
|
||||
}
|
||||
}
|
||||
|
@@ -18,13 +18,14 @@ import {
|
||||
LogOutErrorAction,
|
||||
LogOutSuccessAction,
|
||||
RefreshTokenErrorAction,
|
||||
RefreshTokenSuccessAction
|
||||
RefreshTokenSuccessAction, RetrieveAuthMethodsAction, RetrieveAuthMethodsErrorAction, RetrieveAuthMethodsSuccessAction
|
||||
} from './auth.actions';
|
||||
import { AuthServiceStub } from '../../shared/testing/auth-service-stub';
|
||||
import { authMethodsMock, AuthServiceStub } from '../../shared/testing/auth-service-stub';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthState } from './auth.reducer';
|
||||
|
||||
import { EPersonMock } from '../../shared/testing/eperson-mock';
|
||||
import { AuthStatus } from './models/auth-status.model';
|
||||
|
||||
describe('AuthEffects', () => {
|
||||
let authEffects: AuthEffects;
|
||||
@@ -153,6 +154,49 @@ describe('AuthEffects', () => {
|
||||
})
|
||||
});
|
||||
|
||||
describe('checkTokenCookie$', () => {
|
||||
|
||||
describe('when check token succeeded', () => {
|
||||
it('should return a AUTHENTICATED action in response to a CHECK_AUTHENTICATION_TOKEN_COOKIE action when authenticated is true', () => {
|
||||
spyOn((authEffects as any).authService, 'checkAuthenticationCookie').and.returnValue(
|
||||
observableOf(
|
||||
{ authenticated: true,
|
||||
token
|
||||
})
|
||||
);
|
||||
actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE}});
|
||||
|
||||
const expected = cold('--b-', {b: new AuthenticatedAction(token)});
|
||||
|
||||
expect(authEffects.checkTokenCookie$).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should return a RETRIEVE_AUTH_METHODS action in response to a CHECK_AUTHENTICATION_TOKEN_COOKIE action when authenticated is false', () => {
|
||||
spyOn((authEffects as any).authService, 'checkAuthenticationCookie').and.returnValue(
|
||||
observableOf(
|
||||
{ authenticated: false })
|
||||
);
|
||||
actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE}});
|
||||
|
||||
const expected = cold('--b-', {b: new RetrieveAuthMethodsAction({ authenticated: false } as AuthStatus)});
|
||||
|
||||
expect(authEffects.checkTokenCookie$).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when check token failed', () => {
|
||||
it('should return a AUTHENTICATED_ERROR action in response to a CHECK_AUTHENTICATION_TOKEN_COOKIE action', () => {
|
||||
spyOn((authEffects as any).authService, 'checkAuthenticationCookie').and.returnValue(observableThrow(new Error('Message Error test')));
|
||||
|
||||
actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE, payload: token}});
|
||||
|
||||
const expected = cold('--b-', {b: new AuthenticatedErrorAction(new Error('Message Error test'))});
|
||||
|
||||
expect(authEffects.checkTokenCookie$).toBeObservable(expected);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('refreshToken$', () => {
|
||||
|
||||
describe('when refresh token succeeded', () => {
|
||||
@@ -204,4 +248,30 @@ describe('AuthEffects', () => {
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('retrieveMethods$', () => {
|
||||
|
||||
describe('when retrieve authentication methods succeeded', () => {
|
||||
it('should return a RETRIEVE_AUTH_METHODS_SUCCESS action in response to a RETRIEVE_AUTH_METHODS action', () => {
|
||||
|
||||
actions = hot('--a-', {a: {type: AuthActionTypes.RETRIEVE_AUTH_METHODS}});
|
||||
|
||||
const expected = cold('--b-', {b: new RetrieveAuthMethodsSuccessAction(authMethodsMock)});
|
||||
|
||||
expect(authEffects.retrieveMethods$).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when retrieve authentication methods failed', () => {
|
||||
it('should return a RETRIEVE_AUTH_METHODS_ERROR action in response to a RETRIEVE_AUTH_METHODS action', () => {
|
||||
spyOn((authEffects as any).authService, 'retrieveAuthMethods').and.returnValue(observableThrow(''));
|
||||
|
||||
actions = hot('--a-', {a: {type: AuthActionTypes.RETRIEVE_AUTH_METHODS}});
|
||||
|
||||
const expected = cold('--b-', {b: new RetrieveAuthMethodsErrorAction()});
|
||||
|
||||
expect(authEffects.retrieveMethods$).toBeObservable(expected);
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
@@ -2,9 +2,11 @@ import { Observable, of as observableOf } from 'rxjs';
|
||||
|
||||
import { catchError, debounceTime, filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
// import @ngrx
|
||||
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||
import { Action, select, Store } from '@ngrx/store';
|
||||
|
||||
// import services
|
||||
import { AuthService } from './auth.service';
|
||||
// import actions
|
||||
@@ -35,7 +37,7 @@ import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import { isAuthenticated } from './selectors';
|
||||
import { StoreActionTypes } from '../../store.actions';
|
||||
import { AuthMethodModel } from './models/auth-method.model';
|
||||
import { AuthMethod } from './models/auth.method';
|
||||
|
||||
@Injectable()
|
||||
export class AuthEffects {
|
||||
@@ -46,39 +48,39 @@ export class AuthEffects {
|
||||
*/
|
||||
@Effect()
|
||||
public authenticate$: Observable<Action> = this.actions$.pipe(
|
||||
ofType(AuthActionTypes.AUTHENTICATE),
|
||||
switchMap((action: AuthenticateAction) => {
|
||||
return this.authService.authenticate(action.payload.email, action.payload.password).pipe(
|
||||
take(1),
|
||||
map((response: AuthStatus) => new AuthenticationSuccessAction(response.token)),
|
||||
catchError((error) => observableOf(new AuthenticationErrorAction(error)))
|
||||
);
|
||||
})
|
||||
);
|
||||
ofType(AuthActionTypes.AUTHENTICATE),
|
||||
switchMap((action: AuthenticateAction) => {
|
||||
return this.authService.authenticate(action.payload.email, action.payload.password).pipe(
|
||||
take(1),
|
||||
map((response: AuthStatus) => new AuthenticationSuccessAction(response.token)),
|
||||
catchError((error) => observableOf(new AuthenticationErrorAction(error)))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@Effect()
|
||||
public authenticateSuccess$: Observable<Action> = this.actions$.pipe(
|
||||
ofType(AuthActionTypes.AUTHENTICATE_SUCCESS),
|
||||
tap((action: AuthenticationSuccessAction) => this.authService.storeToken(action.payload)),
|
||||
map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload))
|
||||
);
|
||||
ofType(AuthActionTypes.AUTHENTICATE_SUCCESS),
|
||||
tap((action: AuthenticationSuccessAction) => this.authService.storeToken(action.payload)),
|
||||
map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload))
|
||||
);
|
||||
|
||||
@Effect()
|
||||
public authenticated$: Observable<Action> = this.actions$.pipe(
|
||||
ofType(AuthActionTypes.AUTHENTICATED),
|
||||
switchMap((action: AuthenticatedAction) => {
|
||||
return this.authService.authenticatedUser(action.payload).pipe(
|
||||
map((user: EPerson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)),
|
||||
catchError((error) => observableOf(new AuthenticatedErrorAction(error))),);
|
||||
})
|
||||
);
|
||||
ofType(AuthActionTypes.AUTHENTICATED),
|
||||
switchMap((action: AuthenticatedAction) => {
|
||||
return this.authService.authenticatedUser(action.payload).pipe(
|
||||
map((user: EPerson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)),
|
||||
catchError((error) => observableOf(new AuthenticatedErrorAction(error))),);
|
||||
})
|
||||
);
|
||||
|
||||
// It means "reacts to this action but don't send another"
|
||||
@Effect({ dispatch: false })
|
||||
public authenticatedError$: Observable<Action> = this.actions$.pipe(
|
||||
ofType(AuthActionTypes.AUTHENTICATED_ERROR),
|
||||
tap((action: LogOutSuccessAction) => this.authService.removeToken())
|
||||
);
|
||||
ofType(AuthActionTypes.AUTHENTICATED_ERROR),
|
||||
tap((action: LogOutSuccessAction) => this.authService.removeToken())
|
||||
);
|
||||
|
||||
@Effect()
|
||||
public checkToken$: Observable<Action> = this.actions$.pipe(ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN),
|
||||
@@ -91,7 +93,7 @@ export class AuthEffects {
|
||||
);
|
||||
|
||||
@Effect()
|
||||
public checkTokenError$: Observable<Action> = this.actions$.pipe(
|
||||
public checkTokenCookie$: Observable<Action> = this.actions$.pipe(
|
||||
ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE),
|
||||
switchMap(() => {
|
||||
return this.authService.checkAuthenticationCookie().pipe(
|
||||
@@ -109,32 +111,32 @@ export class AuthEffects {
|
||||
|
||||
@Effect()
|
||||
public createUser$: Observable<Action> = this.actions$.pipe(
|
||||
ofType(AuthActionTypes.REGISTRATION),
|
||||
debounceTime(500), // to remove when functionality is implemented
|
||||
switchMap((action: RegistrationAction) => {
|
||||
return this.authService.create(action.payload).pipe(
|
||||
map((user: EPerson) => new RegistrationSuccessAction(user)),
|
||||
catchError((error) => observableOf(new RegistrationErrorAction(error)))
|
||||
);
|
||||
})
|
||||
);
|
||||
ofType(AuthActionTypes.REGISTRATION),
|
||||
debounceTime(500), // to remove when functionality is implemented
|
||||
switchMap((action: RegistrationAction) => {
|
||||
return this.authService.create(action.payload).pipe(
|
||||
map((user: EPerson) => new RegistrationSuccessAction(user)),
|
||||
catchError((error) => observableOf(new RegistrationErrorAction(error)))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@Effect()
|
||||
public refreshToken$: Observable<Action> = this.actions$.pipe(ofType(AuthActionTypes.REFRESH_TOKEN),
|
||||
switchMap((action: RefreshTokenAction) => {
|
||||
return this.authService.refreshAuthenticationToken(action.payload).pipe(
|
||||
map((token: AuthTokenInfo) => new RefreshTokenSuccessAction(token)),
|
||||
catchError((error) => observableOf(new RefreshTokenErrorAction()))
|
||||
);
|
||||
})
|
||||
);
|
||||
switchMap((action: RefreshTokenAction) => {
|
||||
return this.authService.refreshAuthenticationToken(action.payload).pipe(
|
||||
map((token: AuthTokenInfo) => new RefreshTokenSuccessAction(token)),
|
||||
catchError((error) => observableOf(new RefreshTokenErrorAction()))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
// It means "reacts to this action but don't send another"
|
||||
@Effect({ dispatch: false })
|
||||
public refreshTokenSuccess$: Observable<Action> = this.actions$.pipe(
|
||||
ofType(AuthActionTypes.REFRESH_TOKEN_SUCCESS),
|
||||
tap((action: RefreshTokenSuccessAction) => this.authService.replaceToken(action.payload))
|
||||
);
|
||||
ofType(AuthActionTypes.REFRESH_TOKEN_SUCCESS),
|
||||
tap((action: RefreshTokenSuccessAction) => this.authService.replaceToken(action.payload))
|
||||
);
|
||||
|
||||
/**
|
||||
* When the store is rehydrated in the browser,
|
||||
@@ -195,7 +197,7 @@ export class AuthEffects {
|
||||
switchMap((action: RetrieveAuthMethodsAction) => {
|
||||
return this.authService.retrieveAuthMethods(action.payload)
|
||||
.pipe(
|
||||
map((authMethodModels: AuthMethodModel[]) => new RetrieveAuthMethodsSuccessAction(authMethodModels)),
|
||||
map((authMethodModels: AuthMethod[]) => new RetrieveAuthMethodsSuccessAction(authMethodModels)),
|
||||
catchError((error) => observableOf(new RetrieveAuthMethodsErrorAction()))
|
||||
)
|
||||
})
|
||||
|
@@ -22,8 +22,8 @@ import { isNotEmpty, isNotNull, isUndefined } from '../../shared/empty.util';
|
||||
import { RedirectWhenTokenExpiredAction, RefreshTokenAction } from './auth.actions';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthMethodModel } from './models/auth-method.model';
|
||||
import { AuthMethodType } from '../../shared/log-in/methods/authMethods-type';
|
||||
import { AuthMethod } from './models/auth.method';
|
||||
import { AuthMethodType } from './models/auth.method-type';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
@@ -36,15 +36,30 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
constructor(private inj: Injector, private router: Router, private store: Store<AppState>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response status code is 401
|
||||
*
|
||||
* @param response
|
||||
*/
|
||||
private isUnauthorized(response: HttpResponseBase): boolean {
|
||||
// invalid_token The access token provided is expired, revoked, malformed, or invalid for other reasons
|
||||
return response.status === 401;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response status code is 200 or 204
|
||||
*
|
||||
* @param response
|
||||
*/
|
||||
private isSuccess(response: HttpResponseBase): boolean {
|
||||
return (response.status === 200 || response.status === 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if http request is to authn endpoint
|
||||
*
|
||||
* @param http
|
||||
*/
|
||||
private isAuthRequest(http: HttpRequest<any> | HttpResponseBase): boolean {
|
||||
return http && http.url
|
||||
&& (http.url.endsWith('/authn/login')
|
||||
@@ -52,29 +67,47 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
|| http.url.endsWith('/authn/status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response is from a login request
|
||||
*
|
||||
* @param http
|
||||
*/
|
||||
private isLoginResponse(http: HttpRequest<any> | HttpResponseBase): boolean {
|
||||
return http.url && http.url.endsWith('/authn/login')
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response is from a logout request
|
||||
*
|
||||
* @param http
|
||||
*/
|
||||
private isLogoutResponse(http: HttpRequest<any> | HttpResponseBase): boolean {
|
||||
return http.url && http.url.endsWith('/authn/logout');
|
||||
}
|
||||
|
||||
private parseLocation(unparsedLocation: string): string {
|
||||
unparsedLocation = unparsedLocation.trim();
|
||||
unparsedLocation = unparsedLocation.replace('location="', '');
|
||||
unparsedLocation = unparsedLocation.replace('"', '');
|
||||
/**
|
||||
* Extract location url from the WWW-Authenticate header
|
||||
*
|
||||
* @param header
|
||||
*/
|
||||
private parseLocation(header: string): string {
|
||||
let location = header.trim();
|
||||
location = location.replace('location="', '');
|
||||
location = location.replace('"', '');
|
||||
let re = /%3A%2F%2F/g;
|
||||
unparsedLocation = unparsedLocation.replace(re, '://');
|
||||
re = /%3A/g
|
||||
unparsedLocation = unparsedLocation.replace(re, ':')
|
||||
const parsedLocation = unparsedLocation.trim(); // + '/shibboleth';
|
||||
|
||||
return parsedLocation;
|
||||
location = location.replace(re, '://');
|
||||
re = /%3A/g;
|
||||
location = location.replace(re, ':');
|
||||
return location.trim();
|
||||
}
|
||||
|
||||
private sortAuthMethods(authMethodModels: AuthMethodModel[]): AuthMethodModel[] {
|
||||
const sortedAuthMethodModels: AuthMethodModel[] = new Array<AuthMethodModel>();
|
||||
/**
|
||||
* Sort authentication methods list
|
||||
*
|
||||
* @param authMethodModels
|
||||
*/
|
||||
private sortAuthMethods(authMethodModels: AuthMethod[]): AuthMethod[] {
|
||||
const sortedAuthMethodModels: AuthMethod[] = [];
|
||||
authMethodModels.forEach((method) => {
|
||||
if (method.authMethodType === AuthMethodType.Password) {
|
||||
sortedAuthMethodModels.push(method);
|
||||
@@ -90,10 +123,14 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
return sortedAuthMethodModels;
|
||||
}
|
||||
|
||||
private parseAuthMethodsfromHeaders(headers: HttpHeaders): AuthMethodModel[] {
|
||||
let authMethodModels: AuthMethodModel[] = [];
|
||||
/**
|
||||
* Extract authentication methods list from the WWW-Authenticate headers
|
||||
*
|
||||
* @param headers
|
||||
*/
|
||||
private parseAuthMethodsFromHeaders(headers: HttpHeaders): AuthMethod[] {
|
||||
let authMethodModels: AuthMethod[] = [];
|
||||
if (isNotEmpty(headers.get('www-authenticate'))) {
|
||||
const parts: string[] = headers.get('www-authenticate').split(',');
|
||||
// get the realms from the header - a realm is a single auth method
|
||||
const completeWWWauthenticateHeader = headers.get('www-authenticate');
|
||||
const regex = /(\w+ (\w+=((".*?")|[^,]*)(, )?)*)/g;
|
||||
@@ -105,15 +142,14 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
const splittedRealm = realms[j].split(', ');
|
||||
const methodName = splittedRealm[0].split(' ')[0].trim();
|
||||
|
||||
let authMethodModel: AuthMethodModel;
|
||||
let authMethodModel: AuthMethod;
|
||||
if (splittedRealm.length === 1) {
|
||||
authMethodModel = new AuthMethodModel(methodName);
|
||||
authMethodModel = new AuthMethod(methodName);
|
||||
authMethodModels.push(authMethodModel);
|
||||
} else if (splittedRealm.length > 1) {
|
||||
let location = splittedRealm[1];
|
||||
location = this.parseLocation(location);
|
||||
authMethodModel = new AuthMethodModel(methodName, location);
|
||||
// console.log('location: ', location);
|
||||
authMethodModel = new AuthMethod(methodName, location);
|
||||
authMethodModels.push(authMethodModel);
|
||||
}
|
||||
}
|
||||
@@ -121,17 +157,25 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
// make sure the email + password login component gets rendered first
|
||||
authMethodModels = this.sortAuthMethods(authMethodModels);
|
||||
} else {
|
||||
authMethodModels.push(new AuthMethodModel(AuthMethodType.Password));
|
||||
authMethodModels.push(new AuthMethod(AuthMethodType.Password));
|
||||
}
|
||||
|
||||
return authMethodModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an AuthStatus object
|
||||
*
|
||||
* @param authenticated
|
||||
* @param accessToken
|
||||
* @param error
|
||||
* @param httpHeaders
|
||||
*/
|
||||
private makeAuthStatusObject(authenticated: boolean, accessToken ?: string, error ?: string, httpHeaders ?: HttpHeaders): AuthStatus {
|
||||
const authStatus = new AuthStatus();
|
||||
// let authMethods: AuthMethodModel[];
|
||||
if (httpHeaders) {
|
||||
authStatus.authMethods = this.parseAuthMethodsfromHeaders(httpHeaders);
|
||||
authStatus.authMethods = this.parseAuthMethodsFromHeaders(httpHeaders);
|
||||
}
|
||||
|
||||
authStatus.id = null;
|
||||
@@ -149,6 +193,11 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
return authStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept method
|
||||
* @param req
|
||||
* @param next
|
||||
*/
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
|
||||
const authService = this.inj.get(AuthService);
|
||||
@@ -182,7 +231,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
newReq = req.clone({withCredentials: true});
|
||||
}
|
||||
|
||||
// Pass on the new request instead of the original request.
|
||||
// Pass on the new request instead of the original request.
|
||||
return next.handle(newReq).pipe(
|
||||
// tap((response) => console.log('next.handle: ', response)),
|
||||
map((response) => {
|
||||
|
@@ -26,8 +26,8 @@ import {
|
||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||
import { EPersonMock } from '../../shared/testing/eperson-mock';
|
||||
import { AuthStatus } from './models/auth-status.model';
|
||||
import { AuthMethodModel } from './models/auth-method.model';
|
||||
import { AuthMethodType } from '../../shared/log-in/methods/authMethods-type';
|
||||
import { AuthMethod } from './models/auth.method';
|
||||
import { AuthMethodType } from './models/auth.method-type';
|
||||
|
||||
describe('authReducer', () => {
|
||||
|
||||
@@ -441,8 +441,8 @@ describe('authReducer', () => {
|
||||
authMethods: []
|
||||
};
|
||||
const authMethods = [
|
||||
new AuthMethodModel(AuthMethodType.Password),
|
||||
new AuthMethodModel(AuthMethodType.Shibboleth, 'location')
|
||||
new AuthMethod(AuthMethodType.Password),
|
||||
new AuthMethod(AuthMethodType.Shibboleth, 'location')
|
||||
];
|
||||
const action = new RetrieveAuthMethodsSuccessAction(authMethods);
|
||||
const newState = authReducer(initialState, action);
|
||||
|
@@ -8,14 +8,14 @@ import {
|
||||
LogOutErrorAction,
|
||||
RedirectWhenAuthenticationIsRequiredAction,
|
||||
RedirectWhenTokenExpiredAction,
|
||||
RefreshTokenSuccessAction, RetrieveAuthMethodsSuccessAction,
|
||||
RefreshTokenSuccessAction,
|
||||
RetrieveAuthMethodsSuccessAction,
|
||||
SetRedirectUrlAction
|
||||
} from './auth.actions';
|
||||
// import models
|
||||
import { EPerson } from '../eperson/models/eperson.model';
|
||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||
import { AuthMethodModel } from './models/auth-method.model';
|
||||
import { AuthMethodType } from '../../shared/log-in/methods/authMethods-type';
|
||||
import { AuthMethod } from './models/auth.method';
|
||||
|
||||
/**
|
||||
* The auth state.
|
||||
@@ -50,8 +50,8 @@ export interface AuthState {
|
||||
// the authenticated user
|
||||
user?: EPerson;
|
||||
|
||||
// all authenticationMethods enabled at the backend
|
||||
authMethods?: AuthMethodModel[];
|
||||
// all authentication Methods enabled at the backend
|
||||
authMethods?: AuthMethod[];
|
||||
|
||||
}
|
||||
|
||||
|
@@ -25,6 +25,8 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
|
||||
import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service';
|
||||
import { routeServiceStub } from '../../shared/testing/route-service-stub';
|
||||
import { RouteService } from '../services/route.service';
|
||||
import { authMethodsMock } from '../../shared/testing/auth-service-stub';
|
||||
import { AuthMethod } from './models/auth.method';
|
||||
|
||||
describe('AuthService test', () => {
|
||||
|
||||
@@ -128,6 +130,26 @@ describe('AuthService test', () => {
|
||||
expect(authService.logout.bind(null)).toThrow();
|
||||
});
|
||||
|
||||
it('should return the authentication status object to check an Authentication Cookie', () => {
|
||||
authService.checkAuthenticationCookie().subscribe((status: AuthStatus) => {
|
||||
expect(status).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the authentication methods available', () => {
|
||||
const authStatus = new AuthStatus();
|
||||
|
||||
authService.retrieveAuthMethods(authStatus).subscribe((authMethods: AuthMethod[]) => {
|
||||
expect(authMethods).toBeDefined();
|
||||
expect(authMethods.length).toBe(0);
|
||||
});
|
||||
|
||||
authStatus.authMethods = authMethodsMock;
|
||||
authService.retrieveAuthMethods(authStatus).subscribe((authMethods: AuthMethod[]) => {
|
||||
expect(authMethods).toBeDefined();
|
||||
expect(authMethods.length).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('', () => {
|
||||
|
@@ -27,9 +27,7 @@ import { NativeWindowRef, NativeWindowService } from '../services/window.service
|
||||
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { RouteService } from '../services/route.service';
|
||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { GLOBAL_CONFIG } from '../../../config';
|
||||
import { AuthMethodModel } from './models/auth-method.model';
|
||||
import { AuthMethod } from './models/auth.method';
|
||||
|
||||
export const LOGIN_ROUTE = '/login';
|
||||
export const LOGOUT_ROUTE = '/logout';
|
||||
@@ -213,8 +211,8 @@ export class AuthService {
|
||||
* Retrieve authentication methods available
|
||||
* @returns {User}
|
||||
*/
|
||||
public retrieveAuthMethods(status: AuthStatus): Observable<AuthMethodModel[]> {
|
||||
let authMethods: AuthMethodModel[] = [];
|
||||
public retrieveAuthMethods(status: AuthStatus): Observable<AuthMethod[]> {
|
||||
let authMethods: AuthMethod[] = [];
|
||||
if (isNotEmpty(status.authMethods)) {
|
||||
authMethods = status.authMethods;
|
||||
}
|
||||
@@ -241,7 +239,7 @@ export class AuthService {
|
||||
// Send a request that sign end the session
|
||||
let headers = new HttpHeaders();
|
||||
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
||||
const options: HttpOptions = Object.create({headers, responseType: 'text'});
|
||||
const options: HttpOptions = Object.create({ headers, responseType: 'text' });
|
||||
return this.authRequestService.getRequest('logout', options).pipe(
|
||||
map((status: AuthStatus) => {
|
||||
if (!status.authenticated) {
|
||||
@@ -317,7 +315,7 @@ export class AuthService {
|
||||
|
||||
// Set the cookie expire date
|
||||
const expires = new Date(expireDate);
|
||||
const options: CookieAttributes = {expires: expires};
|
||||
const options: CookieAttributes = { expires: expires };
|
||||
|
||||
// Save cookie with the token
|
||||
return this.storage.set(TOKENITEM, token, options);
|
||||
@@ -384,7 +382,6 @@ export class AuthService {
|
||||
// For standalone login pages, use the previous route.
|
||||
redirUrl = history[history.length - 2] || '';
|
||||
} else {
|
||||
// console.log('isStandAlonePage: ', isStandalonePage);
|
||||
redirUrl = history[history.length - 1] || '';
|
||||
}
|
||||
this.navigateToRedirectUrl(redirUrl);
|
||||
@@ -438,7 +435,7 @@ export class AuthService {
|
||||
|
||||
// Set the cookie expire date
|
||||
const expires = new Date(expireDate);
|
||||
const options: CookieAttributes = {expires: expires};
|
||||
const options: CookieAttributes = { expires: expires };
|
||||
this.storage.set(REDIRECT_COOKIE, url, options);
|
||||
this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(url) ? url : ''));
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import { RemoteData } from '../../data/remote-data';
|
||||
import { Observable } from 'rxjs';
|
||||
import { CacheableObject } from '../../cache/object-cache.reducer';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import {AuthMethodModel} from './auth-method.model';
|
||||
import { AuthMethod } from './auth.method';
|
||||
|
||||
/**
|
||||
* Object that represents the authenticated status of a user
|
||||
@@ -56,6 +56,6 @@ export class AuthStatus implements CacheableObject {
|
||||
/**
|
||||
* All authentication methods enabled at the backend
|
||||
*/
|
||||
authMethods: AuthMethodModel[];
|
||||
authMethods: AuthMethod[];
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { AuthMethodType } from '../../../shared/log-in/methods/authMethods-type';
|
||||
import { AuthMethodType } from './auth.method-type';
|
||||
|
||||
export class AuthMethodModel {
|
||||
export class AuthMethod {
|
||||
authMethodType: AuthMethodType;
|
||||
location?: string;
|
||||
|
@@ -4,7 +4,6 @@ import { mapsTo, relationship } from '../../cache/builders/build-decorators';
|
||||
import { NormalizedObject } from '../../cache/models/normalized-object.model';
|
||||
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
|
||||
import { EPerson } from '../../eperson/models/eperson.model';
|
||||
import {AuthMethodModel} from './auth-method.model';
|
||||
|
||||
@mapsTo(AuthStatus)
|
||||
@inheritSerialization(NormalizedObject)
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import { filter, map, switchMap, take } from 'rxjs/operators';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
|
||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { AuthStatus } from './models/auth-status.model';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||
import { CheckAuthenticationTokenAction } from './auth.actions';
|
||||
import { EPerson } from '../eperson/models/eperson.model';
|
||||
import { AuthMethodModel } from './models/auth-method.model';
|
||||
import { AuthMethod } from './models/auth.method';
|
||||
|
||||
/**
|
||||
* The auth service.
|
||||
@@ -76,7 +76,7 @@ export class ServerAuthService extends AuthService {
|
||||
* Retrieve authentication methods available
|
||||
* @returns {User}
|
||||
*/
|
||||
public retrieveAuthMethods(): Observable<AuthMethodModel[]> {
|
||||
public retrieveAuthMethods(): Observable<AuthMethod[]> {
|
||||
const options: HttpOptions = Object.create({});
|
||||
if (isNotEmpty(this.req.headers) && isNotEmpty(this.req.headers.referer)) {
|
||||
let headers = new HttpHeaders();
|
||||
@@ -86,7 +86,7 @@ export class ServerAuthService extends AuthService {
|
||||
|
||||
return this.authRequestService.postToEndpoint('login', {}, options).pipe(
|
||||
map((status: AuthStatus) => {
|
||||
let authMethods: AuthMethodModel[];
|
||||
let authMethods: AuthMethod[];
|
||||
if (isNotEmpty(status.authMethods)) {
|
||||
authMethods = status.authMethods;
|
||||
}
|
||||
|
@@ -0,0 +1,108 @@
|
||||
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { LogInContainerComponent } from './log-in-container.component';
|
||||
import { authReducer } from '../../../core/auth/auth.reducer';
|
||||
import { SharedModule } from '../../shared.module';
|
||||
import { createTestComponent } from '../../testing/utils';
|
||||
import { AuthService } from '../../../core/auth/auth.service';
|
||||
import { AuthMethod } from '../../../core/auth/models/auth.method';
|
||||
import { AuthServiceStub } from '../../testing/auth-service-stub';
|
||||
|
||||
describe('LogInContainerComponent', () => {
|
||||
|
||||
let component: LogInContainerComponent;
|
||||
let fixture: ComponentFixture<LogInContainerComponent>;
|
||||
|
||||
const authMethod = new AuthMethod('password');
|
||||
|
||||
beforeEach(async(() => {
|
||||
// refine the test module by declaring the test component
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
StoreModule.forRoot(authReducer),
|
||||
SharedModule,
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
TestComponent
|
||||
],
|
||||
providers: [
|
||||
{provide: AuthService, useClass: AuthServiceStub},
|
||||
LogInContainerComponent
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
}));
|
||||
|
||||
describe('', () => {
|
||||
let testComp: TestComponent;
|
||||
let testFixture: ComponentFixture<TestComponent>;
|
||||
|
||||
// synchronous beforeEach
|
||||
beforeEach(() => {
|
||||
const html = `<ds-log-in-container [authMethod]="authMethod"> </ds-log-in-container>`;
|
||||
|
||||
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||
testComp = testFixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testFixture.destroy();
|
||||
});
|
||||
|
||||
it('should create LogInContainerComponent', inject([LogInContainerComponent], (app: LogInContainerComponent) => {
|
||||
|
||||
expect(app).toBeDefined();
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LogInContainerComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
spyOn(component, 'getAuthMethodContent').and.callThrough();
|
||||
component.authMethod = authMethod;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
component = null;
|
||||
});
|
||||
|
||||
it('should inject component properly', () => {
|
||||
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.getAuthMethodContent).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// declare a test component
|
||||
@Component({
|
||||
selector: 'ds-test-cmp',
|
||||
template: ``
|
||||
})
|
||||
class TestComponent {
|
||||
|
||||
isStandalonePage = true;
|
||||
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
import { Component, Injector, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { rendersAuthMethodType } from '../methods/log-in.methods-decorator';
|
||||
import { AuthMethod } from '../../../core/auth/models/auth.method';
|
||||
|
||||
/**
|
||||
* This component represents a component container for log-in methods available.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-log-in-container',
|
||||
templateUrl: './log-in-container.component.html',
|
||||
styleUrls: ['./log-in-container.component.scss']
|
||||
})
|
||||
export class LogInContainerComponent implements OnInit {
|
||||
|
||||
@Input() authMethod: AuthMethod;
|
||||
|
||||
/**
|
||||
* Injector to inject a section component with the @Input parameters
|
||||
* @type {Injector}
|
||||
*/
|
||||
public objectInjector: Injector;
|
||||
|
||||
/**
|
||||
* Initialize instance variables
|
||||
*
|
||||
* @param {Injector} injector
|
||||
*/
|
||||
constructor(private injector: Injector) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all instance variables
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.objectInjector = Injector.create({
|
||||
providers: [
|
||||
{ provide: 'authMethodProvider', useFactory: () => (this.authMethod), deps: [] },
|
||||
],
|
||||
parent: this.injector
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the correct component based on the AuthMethod's type
|
||||
*/
|
||||
getAuthMethodContent(): string {
|
||||
return rendersAuthMethodType(this.authMethod.authMethodType)
|
||||
}
|
||||
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
import { Component, Injector, Input, OnInit} from '@angular/core';
|
||||
import { rendersAuthMethodType } from '../methods/authMethods-decorator';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CoreState } from '../../../core/core.reducers';
|
||||
import { AuthMethodModel } from '../../../core/auth/models/auth-method.model';
|
||||
|
||||
/**
|
||||
* This component represents a section that contains the submission license form.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-login-container',
|
||||
templateUrl: './login-container.component.html',
|
||||
styleUrls: ['./login-container.component.scss']
|
||||
})
|
||||
export class LoginContainerComponent implements OnInit {
|
||||
|
||||
@Input() authMethodModel: AuthMethodModel;
|
||||
|
||||
/**
|
||||
* Injector to inject a section component with the @Input parameters
|
||||
* @type {Injector}
|
||||
*/
|
||||
public objectInjector: Injector;
|
||||
|
||||
/**
|
||||
* Initialize instance variables
|
||||
*
|
||||
* @param {Injector} injector
|
||||
*/
|
||||
constructor(private injector: Injector, private store: Store<CoreState>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all instance variables
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.objectInjector = Injector.create({
|
||||
providers: [
|
||||
{provide: 'authMethodModelProvider', useFactory: () => (this.authMethodModel), deps: []},
|
||||
],
|
||||
parent: this.injector
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the correct component based on the AuthMethod's type
|
||||
*/
|
||||
getAuthMethodContent(): string {
|
||||
return rendersAuthMethodType(this.authMethodModel.authMethodType)
|
||||
}
|
||||
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
<ds-loading *ngIf="(loading | async) || (isAuthenticated | async)" class="m-5"></ds-loading>
|
||||
<div *ngIf="!(loading | async) && !(isAuthenticated | async)" class="form-login px-4 py-3">
|
||||
<ng-container *ngFor="let authMethodModel of (authMethodModels | async); let i = index">
|
||||
<div *ngIf="!(loading | async) && !(isAuthenticated | async)" class="px-4 py-3">
|
||||
<ng-container *ngFor="let authMethod of (authMethods | async); let i = index">
|
||||
<div *ngIf="i === 1" class="text-center mt-2">
|
||||
<span class="align-middle">{{"login.form.or-divider" | translate}}</span>
|
||||
</div>
|
||||
<ds-login-container [authMethodModel]="authMethodModel"></ds-login-container>
|
||||
<ds-log-in-container [authMethod]="authMethod"></ds-log-in-container>
|
||||
</ng-container>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
@@ -1,35 +1,23 @@
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { LogInComponent } from './log-in.component';
|
||||
import { authReducer } from '../../core/auth/auth.reducer';
|
||||
import { EPersonMock } from '../testing/eperson-mock';
|
||||
import { EPerson } from '../../core/eperson/models/eperson.model';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { AuthServiceStub } from '../testing/auth-service-stub';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import { authMethodsMock, AuthServiceStub } from '../testing/auth-service-stub';
|
||||
import { createTestComponent } from '../testing/utils';
|
||||
import { SharedModule } from '../shared.module';
|
||||
|
||||
describe('LogInComponent', () => {
|
||||
|
||||
let component: LogInComponent;
|
||||
let fixture: ComponentFixture<LogInComponent>;
|
||||
let page: Page;
|
||||
let user: EPerson;
|
||||
|
||||
const authState = {
|
||||
authenticated: false,
|
||||
loaded: false,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
user = EPersonMock;
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
// refine the test module by declaring the test component
|
||||
@@ -38,13 +26,15 @@ describe('LogInComponent', () => {
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
StoreModule.forRoot(authReducer),
|
||||
SharedModule,
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
LogInComponent
|
||||
TestComponent
|
||||
],
|
||||
providers: [
|
||||
{provide: AuthService, useClass: AuthServiceStub}
|
||||
{provide: AuthService, useClass: AuthServiceStub},
|
||||
LogInComponent
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
@@ -54,75 +44,64 @@ describe('LogInComponent', () => {
|
||||
|
||||
}));
|
||||
|
||||
beforeEach(inject([Store], (store: Store<AppState>) => {
|
||||
store
|
||||
.subscribe((state) => {
|
||||
(state as any).core = Object.create({});
|
||||
(state as any).core.auth = authState;
|
||||
});
|
||||
describe('', () => {
|
||||
let testComp: TestComponent;
|
||||
let testFixture: ComponentFixture<TestComponent>;
|
||||
|
||||
// create component and test fixture
|
||||
fixture = TestBed.createComponent(LogInComponent);
|
||||
// synchronous beforeEach
|
||||
beforeEach(() => {
|
||||
const html = `<ds-log-in [isStandalonePage]="isStandalonePage"> </ds-log-in>`;
|
||||
|
||||
// get test component from the fixture
|
||||
component = fixture.componentInstance;
|
||||
|
||||
// create page
|
||||
page = new Page(component, fixture);
|
||||
|
||||
// verify the fixture is stable (no pending tasks)
|
||||
fixture.whenStable().then(() => {
|
||||
page.addPageElements();
|
||||
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||
testComp = testFixture.componentInstance;
|
||||
});
|
||||
|
||||
}));
|
||||
afterEach(() => {
|
||||
testFixture.destroy();
|
||||
});
|
||||
|
||||
/* it('should create a FormGroup comprised of FormControls', () => {
|
||||
fixture.detectChanges();
|
||||
expect(component.form instanceof FormGroup).toBe(true);
|
||||
});*/
|
||||
it('should create LogInComponent', inject([LogInComponent], (app: LogInComponent) => {
|
||||
|
||||
/* it('should authenticate', () => {
|
||||
fixture.detectChanges();
|
||||
expect(app).toBeDefined();
|
||||
|
||||
// set FormControl values
|
||||
component.form.controls.email.setValue('user');
|
||||
component.form.controls.password.setValue('password');
|
||||
}));
|
||||
});
|
||||
|
||||
// submit form
|
||||
component.submit();
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LogInComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.isAuthenticated = observableOf(false);
|
||||
component.loading = observableOf(false);
|
||||
|
||||
// verify Store.dispatch() is invoked
|
||||
expect(page.navigateSpy.calls.any()).toBe(true, 'Store.dispatch not invoked');
|
||||
});*/
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
component = null;
|
||||
});
|
||||
|
||||
it('should render a log-in container component foe each auth method available', () => {
|
||||
component.authMethods = observableOf(authMethodsMock);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const loginContainers = fixture.debugElement.queryAll(By.css('ds-log-in-container'));
|
||||
expect(loginContainers.length).toBe(2);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* I represent the DOM elements and attach spies.
|
||||
*
|
||||
* @class Page
|
||||
*/
|
||||
class Page {
|
||||
// declare a test component
|
||||
@Component({
|
||||
selector: 'ds-test-cmp',
|
||||
template: ``
|
||||
})
|
||||
class TestComponent {
|
||||
|
||||
public emailInput: HTMLInputElement;
|
||||
public navigateSpy: jasmine.Spy;
|
||||
public passwordInput: HTMLInputElement;
|
||||
isStandalonePage = true;
|
||||
|
||||
constructor(private component: LogInComponent, private fixture: ComponentFixture<LogInComponent>) {
|
||||
// use injector to get services
|
||||
const injector = fixture.debugElement.injector;
|
||||
const store = injector.get(Store);
|
||||
|
||||
// add spies
|
||||
this.navigateSpy = spyOn(store, 'dispatch');
|
||||
}
|
||||
|
||||
public addPageElements() {
|
||||
const emailInputSelector = 'input[formcontrolname=\'email\']';
|
||||
this.emailInput = this.fixture.debugElement.query(By.css(emailInputSelector)).nativeElement;
|
||||
|
||||
const passwordInputSelector = 'input[formcontrolname=\'password\']';
|
||||
this.passwordInput = this.fixture.debugElement.query(By.css(passwordInputSelector)).nativeElement;
|
||||
}
|
||||
}
|
||||
|
@@ -1,26 +1,37 @@
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthMethodModel } from '../../core/auth/models/auth-method.model';
|
||||
import { filter, takeWhile, } from 'rxjs/operators';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
|
||||
import { AuthMethod } from '../../core/auth/models/auth.method';
|
||||
import { getAuthenticationMethods, isAuthenticated, isAuthenticationLoading } from '../../core/auth/selectors';
|
||||
import { CoreState } from '../../core/core.reducers';
|
||||
import { filter, takeWhile, } from 'rxjs/operators';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
|
||||
/**
|
||||
* /users/sign-in
|
||||
* @class LogInComponent
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-log-in',
|
||||
templateUrl: './log-in.component.html',
|
||||
styleUrls: ['./log-in.component.scss']
|
||||
})
|
||||
export class LogInComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* The authentication methods data
|
||||
* @type {AuthMethodModel[]}
|
||||
*/
|
||||
@Input() authMethodModels: Observable<AuthMethodModel[]>;
|
||||
|
||||
/**
|
||||
* A boolean representing if LogInComponent is in a standalone page
|
||||
* @type {boolean}
|
||||
*/
|
||||
@Input() isStandalonePage: boolean;
|
||||
|
||||
/**
|
||||
* The list of authentication methods available
|
||||
* @type {AuthMethod[]}
|
||||
*/
|
||||
public authMethods: Observable<AuthMethod[]>;
|
||||
|
||||
/**
|
||||
* Whether user is authenticated.
|
||||
* @type {Observable<string>}
|
||||
@@ -45,9 +56,7 @@ export class LogInComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
// this.store.dispatch(new SetIsStandalonePageInAuthMethodsAction(this.isStandalonePage));
|
||||
|
||||
this.authMethodModels = this.store.pipe(
|
||||
this.authMethods = this.store.pipe(
|
||||
select(getAuthenticationMethods),
|
||||
);
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { AuthMethodType } from './authMethods-type';
|
||||
import { AuthMethodType } from '../../../core/auth/models/auth.method-type';
|
||||
|
||||
const authMethodsMap = new Map();
|
||||
|
@@ -4,16 +4,17 @@ import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { LogInPasswordComponent } from './log-in-password.component';
|
||||
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||
import { EPersonMock } from '../../../testing/eperson-mock';
|
||||
import { authReducer } from '../../../../core/auth/auth.reducer';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AuthService } from '../../../../core/auth/auth.service';
|
||||
import { AuthServiceStub } from '../../../testing/auth-service-stub';
|
||||
import { AppState } from '../../../../app.reducer';
|
||||
import { AuthMethodModel } from '../../../../core/auth/models/auth-method.model';
|
||||
import { AuthMethodType } from '../authMethods-type';
|
||||
import { AuthMethod } from '../../../../core/auth/models/auth.method';
|
||||
import { AuthMethodType } from '../../../../core/auth/models/auth.method-type';
|
||||
|
||||
describe('LogInPasswordComponent', () => {
|
||||
|
||||
@@ -46,7 +47,7 @@ describe('LogInPasswordComponent', () => {
|
||||
],
|
||||
providers: [
|
||||
{ provide: AuthService, useClass: AuthServiceStub },
|
||||
{ provide: 'authMethodModelProvider', useValue: new AuthMethodModel(AuthMethodType.Password) }
|
||||
{ provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Password) }
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Component, Inject, Input, OnInit } from '@angular/core';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
import { select, Store } from '@ngrx/store';
|
||||
@@ -8,12 +8,11 @@ import { AuthenticateAction, ResetAuthenticationMessagesAction } from '../../../
|
||||
|
||||
import { getAuthenticationError, getAuthenticationInfo, } from '../../../../core/auth/selectors';
|
||||
import { CoreState } from '../../../../core/core.reducers';
|
||||
|
||||
import { isNotEmpty } from '../../../empty.util';
|
||||
import { fadeOut } from '../../../animations/fade';
|
||||
import { AuthMethodType } from '../authMethods-type';
|
||||
import { renderAuthMethodFor } from '../authMethods-decorator';
|
||||
import { AuthMethodModel } from '../../../../core/auth/models/auth-method.model';
|
||||
import { AuthMethodType } from '../../../../core/auth/models/auth.method-type';
|
||||
import { renderAuthMethodFor } from '../log-in.methods-decorator';
|
||||
import { AuthMethod } from '../../../../core/auth/models/auth.method';
|
||||
|
||||
/**
|
||||
* /users/sign-in
|
||||
@@ -28,6 +27,12 @@ import { AuthMethodModel } from '../../../../core/auth/models/auth-method.model'
|
||||
@renderAuthMethodFor(AuthMethodType.Password)
|
||||
export class LogInPasswordComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* The authentication method data.
|
||||
* @type {AuthMethod}
|
||||
*/
|
||||
public authMethod: AuthMethod;
|
||||
|
||||
/**
|
||||
* The error if authentication fails.
|
||||
* @type {Observable<string>}
|
||||
@@ -58,21 +63,18 @@ export class LogInPasswordComponent implements OnInit {
|
||||
*/
|
||||
public form: FormGroup;
|
||||
|
||||
@Input() authMethodModel: AuthMethodModel;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {AuthMethodModel} injectedAuthMethodModel
|
||||
* @param {AuthMethod} injectedAuthMethodModel
|
||||
* @param {FormBuilder} formBuilder
|
||||
* @param {Store<State>} store
|
||||
*/
|
||||
constructor(
|
||||
@Inject('authMethodModelProvider') public injectedAuthMethodModel: AuthMethodModel,
|
||||
/* private authService: AuthService,*/
|
||||
@Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod,
|
||||
private formBuilder: FormBuilder,
|
||||
private store: Store<CoreState>
|
||||
) {
|
||||
this.authMethodModel = injectedAuthMethodModel;
|
||||
this.authMethod = injectedAuthMethodModel;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,15 +120,6 @@ export class LogInPasswordComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To the registration page.
|
||||
* @method register
|
||||
*/
|
||||
public register() {
|
||||
// TODO enable after registration process is done
|
||||
// this.router.navigate(['/register']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the authentication form.
|
||||
* @method submit
|
||||
|
@@ -0,0 +1,105 @@
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||
import { EPersonMock } from '../../../testing/eperson-mock';
|
||||
import { authReducer } from '../../../../core/auth/auth.reducer';
|
||||
import { AuthService } from '../../../../core/auth/auth.service';
|
||||
import { AuthServiceStub } from '../../../testing/auth-service-stub';
|
||||
import { AppState } from '../../../../app.reducer';
|
||||
import { AuthMethod } from '../../../../core/auth/models/auth.method';
|
||||
import { AuthMethodType } from '../../../../core/auth/models/auth.method-type';
|
||||
import { LogInShibbolethComponent } from './log-in-shibboleth.component';
|
||||
|
||||
describe('LogInShibbolethComponent', () => {
|
||||
|
||||
let component: LogInShibbolethComponent;
|
||||
let fixture: ComponentFixture<LogInShibbolethComponent>;
|
||||
let page: Page;
|
||||
let user: EPerson;
|
||||
|
||||
const authState = {
|
||||
authenticated: false,
|
||||
loaded: false,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
user = EPersonMock;
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
// refine the test module by declaring the test component
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
StoreModule.forRoot(authReducer),
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
LogInShibbolethComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: AuthService, useClass: AuthServiceStub },
|
||||
{ provide: 'authMethodProvider',
|
||||
useValue: new AuthMethod(AuthMethodType.Shibboleth, 'dspace.test/shibboleth')
|
||||
}
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
}));
|
||||
|
||||
beforeEach(inject([Store], (store: Store<AppState>) => {
|
||||
store
|
||||
.subscribe((state) => {
|
||||
(state as any).core = Object.create({});
|
||||
(state as any).core.auth = authState;
|
||||
});
|
||||
|
||||
// create component and test fixture
|
||||
fixture = TestBed.createComponent(LogInShibbolethComponent);
|
||||
|
||||
// get test component from the fixture
|
||||
component = fixture.componentInstance;
|
||||
|
||||
// create page
|
||||
page = new Page(component, fixture);
|
||||
|
||||
}));
|
||||
|
||||
it('should display a link with properly href', () => {
|
||||
fixture.detectChanges();
|
||||
const link = fixture.debugElement.query(By.css('a'));
|
||||
expect(link.nativeElement.getAttribute('href')).toBe('dspace.test/shibboleth');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* I represent the DOM elements and attach spies.
|
||||
*
|
||||
* @class Page
|
||||
*/
|
||||
class Page {
|
||||
|
||||
public emailInput: HTMLInputElement;
|
||||
public navigateSpy: jasmine.Spy;
|
||||
public passwordInput: HTMLInputElement;
|
||||
|
||||
constructor(private component: LogInShibbolethComponent, private fixture: ComponentFixture<LogInShibbolethComponent>) {
|
||||
// use injector to get services
|
||||
const injector = fixture.debugElement.injector;
|
||||
const store = injector.get(Store);
|
||||
|
||||
// add spies
|
||||
this.navigateSpy = spyOn(store, 'dispatch');
|
||||
}
|
||||
|
||||
}
|
@@ -1,11 +1,11 @@
|
||||
import { Component, Inject, Input, OnInit, } from '@angular/core';
|
||||
import { Component, Inject, OnInit, } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
|
||||
import { renderAuthMethodFor } from '../authMethods-decorator';
|
||||
import { AuthMethodType } from '../authMethods-type';
|
||||
import { AuthMethodModel } from '../../../../core/auth/models/auth-method.model';
|
||||
import { renderAuthMethodFor } from '../log-in.methods-decorator';
|
||||
import { AuthMethodType } from '../../../../core/auth/models/auth.method-type';
|
||||
import { AuthMethod } from '../../../../core/auth/models/auth.method';
|
||||
|
||||
import { CoreState } from '../../../../core/core.reducers';
|
||||
import { isAuthenticated, isAuthenticationLoading } from '../../../../core/auth/selectors';
|
||||
@@ -19,7 +19,11 @@ import { isAuthenticated, isAuthenticationLoading } from '../../../../core/auth/
|
||||
@renderAuthMethodFor(AuthMethodType.Shibboleth)
|
||||
export class LogInShibbolethComponent implements OnInit {
|
||||
|
||||
@Input() authMethodModel: AuthMethodModel;
|
||||
/**
|
||||
* The authentication method data.
|
||||
* @type {AuthMethod}
|
||||
*/
|
||||
public authMethod: AuthMethod;
|
||||
|
||||
/**
|
||||
* True if the authentication is loading.
|
||||
@@ -41,12 +45,14 @@ export class LogInShibbolethComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {AuthMethod} injectedAuthMethodModel
|
||||
* @param {Store<State>} store
|
||||
*/
|
||||
constructor(
|
||||
@Inject('authMethodModelProvider') public injectedAuthMethodModel: AuthMethodModel,
|
||||
@Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod,
|
||||
private store: Store<CoreState>
|
||||
) {
|
||||
this.authMethodModel = injectedAuthMethodModel;
|
||||
this.authMethod = injectedAuthMethodModel;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@@ -171,9 +171,8 @@ import { PageWithSidebarComponent } from './sidebar/page-with-sidebar.component'
|
||||
import { SidebarDropdownComponent } from './sidebar/sidebar-dropdown.component';
|
||||
import { SidebarFilterComponent } from './sidebar/filter/sidebar-filter.component';
|
||||
import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component';
|
||||
import { MetadataRepresentationListComponent } from '../+item-page/simple/metadata-representation-list/metadata-representation-list.component';
|
||||
import { SelectableListItemControlComponent } from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component';
|
||||
import { LoginContainerComponent } from './log-in/container/login-container.component';
|
||||
import { LogInContainerComponent } from './log-in/container/log-in-container.component';
|
||||
import { LogInShibbolethComponent } from './log-in/methods/shibboleth/log-in-shibboleth.component';
|
||||
import { LogInPasswordComponent } from './log-in/methods/password/log-in-password.component';
|
||||
import { LogInComponent } from './log-in/log-in.component';
|
||||
@@ -336,7 +335,7 @@ const COMPONENTS = [
|
||||
SelectableListItemControlComponent,
|
||||
LogInShibbolethComponent,
|
||||
LogInPasswordComponent,
|
||||
LoginContainerComponent,
|
||||
LogInContainerComponent,
|
||||
ItemTypeBadgeComponent
|
||||
];
|
||||
|
||||
|
@@ -5,7 +5,6 @@ import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
|
||||
import { EPerson } from '../../core/eperson/models/eperson.model';
|
||||
import { isNotEmpty } from '../empty.util';
|
||||
import { EPersonMock } from './eperson-mock';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { createSuccessfulRemoteDataObject$ } from './utils';
|
||||
|
||||
export class AuthRequestServiceStub {
|
||||
@@ -23,7 +22,7 @@ export class AuthRequestServiceStub {
|
||||
} else {
|
||||
authStatusStub.authenticated = false;
|
||||
}
|
||||
} else {
|
||||
} else if (isNotEmpty(options)) {
|
||||
const token = (options.headers as any).lazyUpdate[1].value;
|
||||
if (this.validateToken(token)) {
|
||||
authStatusStub.authenticated = true;
|
||||
@@ -32,6 +31,8 @@ export class AuthRequestServiceStub {
|
||||
} else {
|
||||
authStatusStub.authenticated = false;
|
||||
}
|
||||
} else {
|
||||
authStatusStub.authenticated = false;
|
||||
}
|
||||
return observableOf(authStatusStub);
|
||||
}
|
||||
|
@@ -3,8 +3,13 @@ import { AuthStatus } from '../../core/auth/models/auth-status.model';
|
||||
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
|
||||
import { EPersonMock } from './eperson-mock';
|
||||
import { EPerson } from '../../core/eperson/models/eperson.model';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { createSuccessfulRemoteDataObject$ } from './utils';
|
||||
import { AuthMethod } from '../../core/auth/models/auth.method';
|
||||
|
||||
export const authMethodsMock = [
|
||||
new AuthMethod('password'),
|
||||
new AuthMethod('shibboleth', 'dspace.test/shibboleth')
|
||||
];
|
||||
|
||||
export class AuthServiceStub {
|
||||
|
||||
@@ -103,4 +108,12 @@ export class AuthServiceStub {
|
||||
isAuthenticated() {
|
||||
return observableOf(true);
|
||||
}
|
||||
|
||||
checkAuthenticationCookie() {
|
||||
return;
|
||||
}
|
||||
|
||||
retrieveAuthMethods(status: AuthStatus) {
|
||||
return observableOf(authMethodsMock);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user