Merge remote-tracking branch 'atmire-internal/w2p-55565_authorization-issue' into patch-support

Conflicts:
	package.json
	src/app/+search-page/search-filters/search-filters.component.ts
	src/app/core/auth/auth.effects.ts
	src/app/core/auth/auth.service.ts
	src/app/core/auth/server-auth.service.ts
	src/app/core/data/data.service.ts
	src/app/header/header.component.spec.ts
	src/app/shared/auth-nav-menu/auth-nav-menu.component.ts
	src/app/shared/testing/auth-service-stub.ts
	yarn.lock
This commit is contained in:
lotte
2018-09-27 16:54:59 +02:00
39 changed files with 241 additions and 133 deletions

View File

@@ -8,13 +8,13 @@ module.exports = {
nameSpace: '/' nameSpace: '/'
}, },
// The REST API server settings. // The REST API server settings.
rest: { rest: {
ssl: true, ssl: true,
host: 'dspace7.4science.it', host: 'dspace7.4science.it',
port: 443, port: 443,
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
nameSpace: '/dspace-spring-rest/api' nameSpace: '/dspace-spring-rest/api'
}, },
// Caching settings // Caching settings
cache: { cache: {
// NOTE: how long should objects be cached for by default // NOTE: how long should objects be cached for by default

View File

@@ -93,7 +93,7 @@
"cerialize": "0.1.18", "cerialize": "0.1.18",
"compression": "1.7.1", "compression": "1.7.1",
"cookie-parser": "1.4.3", "cookie-parser": "1.4.3",
"core-js": "2.5.3", "core-js": "^2.5.7",
"express": "4.16.2", "express": "4.16.2",
"express-session": "1.15.6", "express-session": "1.15.6",
"fast-json-patch": "^2.0.7", "fast-json-patch": "^2.0.7",

View File

@@ -35,7 +35,7 @@
</ds-comcol-page-content> </ds-comcol-page-content>
</div> </div>
</div> </div>
<ds-error *ngIf="collectionRD?.hasFailed"0 message="{{'error.collection' | translate}}"></ds-error> <ds-error *ngIf="collectionRD?.hasFailed" message="{{'error.collection' | translate}}"></ds-error>
<ds-loading *ngIf="collectionRD?.isLoading" message="{{'loading.collection' | translate}}"></ds-loading> <ds-loading *ngIf="collectionRD?.isLoading" message="{{'loading.collection' | translate}}"></ds-loading>
<br> <br>
<ng-container *ngVar="(itemRD$ | async) as itemRD"> <ng-container *ngVar="(itemRD$ | async) as itemRD">

View File

@@ -6,13 +6,21 @@ import { CollectionDataService } from '../core/data/collection-data.service';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { getSucceededRemoteData } from '../core/shared/operators'; import { getSucceededRemoteData } from '../core/shared/operators';
/**
* This class represents a resolver that requests a specific collection before the route is activated
*/
@Injectable() @Injectable()
export class CollectionPageResolver implements Resolve<RemoteData<Collection>> { export class CollectionPageResolver implements Resolve<RemoteData<Collection>> {
constructor(private collectionService: CollectionDataService) { constructor(private collectionService: CollectionDataService) {
} }
/**
* Method for resolving a collection based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Collection>> Emits the found collection based on the parameters in the current route
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
return this.collectionService.findById(route.params.id).pipe( return this.collectionService.findById(route.params.id).pipe(
getSucceededRemoteData() getSucceededRemoteData()
); );

View File

@@ -6,13 +6,21 @@ import { getSucceededRemoteData } from '../core/shared/operators';
import { Community } from '../core/shared/community.model'; import { Community } from '../core/shared/community.model';
import { CommunityDataService } from '../core/data/community-data.service'; import { CommunityDataService } from '../core/data/community-data.service';
/**
* This class represents a resolver that requests a specific community before the route is activated
*/
@Injectable() @Injectable()
export class CommunityPageResolver implements Resolve<RemoteData<Community>> { export class CommunityPageResolver implements Resolve<RemoteData<Community>> {
constructor(private communityService: CommunityDataService) { constructor(private communityService: CommunityDataService) {
} }
/**
* Method for resolving a community based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Community>> Emits the found community based on the parameters in the current route
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
return this.communityService.findById(route.params.id).pipe( return this.communityService.findById(route.params.id).pipe(
getSucceededRemoteData() getSucceededRemoteData()
); );

View File

@@ -6,11 +6,20 @@ import { getSucceededRemoteData } from '../core/shared/operators';
import { ItemDataService } from '../core/data/item-data.service'; import { ItemDataService } from '../core/data/item-data.service';
import { Item } from '../core/shared/item.model'; import { Item } from '../core/shared/item.model';
/**
* This class represents a resolver that requests a specific item before the route is activated
*/
@Injectable() @Injectable()
export class ItemPageResolver implements Resolve<RemoteData<Item>> { export class ItemPageResolver implements Resolve<RemoteData<Item>> {
constructor(private itemService: ItemDataService) { constructor(private itemService: ItemDataService) {
} }
/**
* Method for resolving an item based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
return this.itemService.findById(route.params.id).pipe( return this.itemService.findById(route.params.id).pipe(
getSucceededRemoteData() getSucceededRemoteData()

View File

@@ -58,7 +58,6 @@ export class SearchFiltersComponent {
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown * @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
*/ */
isActive(filterConfig: SearchFilterConfig): Observable<boolean> { isActive(filterConfig: SearchFilterConfig): Observable<boolean> {
// console.log(filter.name);
return this.filterService.getSelectedValuesForFilter(filterConfig).pipe( return this.filterService.getSelectedValuesForFilter(filterConfig).pipe(
mergeMap((isActive) => { mergeMap((isActive) => {
if (isNotEmpty(isActive)) { if (isNotEmpty(isActive)) {

View File

@@ -1,14 +1,15 @@
import { AuthType } from './auth-type'; import { AuthType } from './auth-type';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
import { NormalizedAuthStatus } from './models/normalized-auth-status.model'; import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model'; import { NormalizedEPerson } from '../eperson/models/normalized-eperson.model';
import { NormalizedEpersonModel } from '../eperson/models/NormalizedEperson.model'; import { NormalizedObject } from '../cache/models/normalized-object.model';
import { EPerson } from '../eperson/models/eperson.model';
export class AuthObjectFactory { export class AuthObjectFactory {
public static getConstructor(type): GenericConstructor<NormalizedDSpaceObject> { public static getConstructor(type): GenericConstructor<NormalizedObject> {
switch (type) { switch (type) {
case AuthType.Eperson: { case AuthType.EPerson: {
return NormalizedEpersonModel return NormalizedEPerson
} }
case AuthType.Status: { case AuthType.Status: {

View File

@@ -8,12 +8,13 @@ import { CoreState } from '../core.reducers';
import { AuthStatus } from './models/auth-status.model'; import { AuthStatus } from './models/auth-status.model';
import { AuthResponseParsingService } from './auth-response-parsing.service'; import { AuthResponseParsingService } from './auth-response-parsing.service';
import { AuthGetRequest, AuthPostRequest } from '../data/request.models'; import { AuthGetRequest, AuthPostRequest } from '../data/request.models';
import { getMockStore } from '../../shared/mocks/mock-store';
describe('ConfigResponseParsingService', () => { describe('AuthResponseParsingService', () => {
let service: AuthResponseParsingService; let service: AuthResponseParsingService;
const EnvConfig = {} as GlobalConfig; const EnvConfig = {cache: {msToLive: 1000}} as GlobalConfig;
const store = {} as Store<CoreState>; const store = getMockStore() as Store<CoreState>;
const objectCacheService = new ObjectCacheService(store); const objectCacheService = new ObjectCacheService(store);
beforeEach(() => { beforeEach(() => {
@@ -86,13 +87,19 @@ describe('ConfigResponseParsingService', () => {
type: 'eperson', type: 'eperson',
uuid: '4dc70ab5-cd73-492f-b007-3179d2d9296b', uuid: '4dc70ab5-cd73-492f-b007-3179d2d9296b',
_links: { _links: {
self: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b' self: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
}
} }
} }
}, },
_links: { _links: {
eperson: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b', eperson: {
self: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/authn/status' href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
},
self: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/authn/status'
}
} }
}, },
statusCode: '200' statusCode: '200'

View File

@@ -12,12 +12,13 @@ import { ResponseParsingService } from '../data/parsing.service';
import { RestRequest } from '../data/request.models'; import { RestRequest } from '../data/request.models';
import { AuthType } from './auth-type'; import { AuthType } from './auth-type';
import { AuthStatus } from './models/auth-status.model'; import { AuthStatus } from './models/auth-status.model';
import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
@Injectable() @Injectable()
export class AuthResponseParsingService extends BaseResponseParsingService implements ResponseParsingService { export class AuthResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected objectFactory = AuthObjectFactory; protected objectFactory = AuthObjectFactory;
protected toCache = false; protected toCache = true;
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected objectCache: ObjectCacheService) { protected objectCache: ObjectCacheService) {
@@ -26,8 +27,8 @@ export class AuthResponseParsingService extends BaseResponseParsingService imple
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && (data.statusCode === '200' || data.statusCode === 'OK')) { if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && (data.statusCode === '200' || data.statusCode === 'OK')) {
const response = this.process<AuthStatus, AuthType>(data.payload, request.href); const response = this.process<NormalizedAuthStatus, AuthType>(data.payload, request.href);
return new AuthStatusResponse(response[Object.keys(response)[0]][0], data.statusCode); return new AuthStatusResponse(response, data.statusCode);
} else { } else {
return new AuthStatusResponse(data.payload as AuthStatus, data.statusCode); return new AuthStatusResponse(data.payload as AuthStatus, data.statusCode);
} }

View File

@@ -1,4 +1,4 @@
export enum AuthType { export enum AuthType {
Eperson = 'eperson', EPerson = 'eperson',
Status = 'status' Status = 'status'
} }

View File

@@ -5,7 +5,7 @@ import { Action } from '@ngrx/store';
import { type } from '../../shared/ngrx/type'; import { type } from '../../shared/ngrx/type';
// import models // import models
import { Eperson } from '../eperson/models/eperson.model'; import { EPerson } from '../eperson/models/eperson.model';
import { AuthTokenInfo } from './models/auth-token-info.model'; import { AuthTokenInfo } from './models/auth-token-info.model';
export const AuthActionTypes = { export const AuthActionTypes = {
@@ -76,10 +76,10 @@ export class AuthenticatedSuccessAction implements Action {
payload: { payload: {
authenticated: boolean; authenticated: boolean;
authToken: AuthTokenInfo; authToken: AuthTokenInfo;
user: Eperson user: EPerson
}; };
constructor(authenticated: boolean, authToken: AuthTokenInfo, user: Eperson) { constructor(authenticated: boolean, authToken: AuthTokenInfo, user: EPerson) {
this.payload = { authenticated, authToken, user }; this.payload = { authenticated, authToken, user };
} }
} }
@@ -250,9 +250,9 @@ export class RefreshTokenErrorAction implements Action {
*/ */
export class RegistrationAction implements Action { export class RegistrationAction implements Action {
public type: string = AuthActionTypes.REGISTRATION; public type: string = AuthActionTypes.REGISTRATION;
payload: Eperson; payload: EPerson;
constructor(user: Eperson) { constructor(user: EPerson) {
this.payload = user; this.payload = user;
} }
} }
@@ -278,9 +278,9 @@ export class RegistrationErrorAction implements Action {
*/ */
export class RegistrationSuccessAction implements Action { export class RegistrationSuccessAction implements Action {
public type: string = AuthActionTypes.REGISTRATION_SUCCESS; public type: string = AuthActionTypes.REGISTRATION_SUCCESS;
payload: Eperson; payload: EPerson;
constructor(user: Eperson) { constructor(user: EPerson) {
this.payload = user; this.payload = user;
} }
} }

View File

@@ -25,12 +25,11 @@ import { AuthServiceStub } from '../../shared/testing/auth-service-stub';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { TruncatablesState } from '../../shared/truncatable/truncatable.reducer'; import { TruncatablesState } from '../../shared/truncatable/truncatable.reducer';
import { EpersonMock } from '../../shared/testing/eperson-mock'; import { EPersonMock } from '../../shared/testing/eperson-mock';
describe('AuthEffects', () => { describe('AuthEffects', () => {
let authEffects: AuthEffects; let authEffects: AuthEffects;
let actions: Observable<any>; let actions: Observable<any>;
const authServiceStub = new AuthServiceStub(); const authServiceStub = new AuthServiceStub();
const store: Store<TruncatablesState> = jasmine.createSpyObj('store', { const store: Store<TruncatablesState> = jasmine.createSpyObj('store', {
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
@@ -105,7 +104,7 @@ describe('AuthEffects', () => {
it('should return a AUTHENTICATED_SUCCESS action in response to a AUTHENTICATED action', () => { it('should return a AUTHENTICATED_SUCCESS action in response to a AUTHENTICATED action', () => {
actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}}); actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}});
const expected = cold('--b-', {b: new AuthenticatedSuccessAction(true, token, EpersonMock)}); const expected = cold('--b-', {b: new AuthenticatedSuccessAction(true, token, EPersonMock)});
expect(authEffects.authenticated$).toBeObservable(expected); expect(authEffects.authenticated$).toBeObservable(expected);
}); });

View File

@@ -28,7 +28,7 @@ import {
RegistrationErrorAction, RegistrationErrorAction,
RegistrationSuccessAction RegistrationSuccessAction
} from './auth.actions'; } from './auth.actions';
import { Eperson } from '../eperson/models/eperson.model'; import { EPerson } from '../eperson/models/eperson.model';
import { AuthStatus } from './models/auth-status.model'; import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo } from './models/auth-token-info.model'; import { AuthTokenInfo } from './models/auth-token-info.model';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
@@ -66,7 +66,7 @@ export class AuthEffects {
ofType(AuthActionTypes.AUTHENTICATED), ofType(AuthActionTypes.AUTHENTICATED),
switchMap((action: AuthenticatedAction) => { switchMap((action: AuthenticatedAction) => {
return this.authService.authenticatedUser(action.payload).pipe( return this.authService.authenticatedUser(action.payload).pipe(
map((user: Eperson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)), map((user: EPerson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)),
catchError((error) => observableOf(new AuthenticatedErrorAction(error))),); catchError((error) => observableOf(new AuthenticatedErrorAction(error))),);
}) })
); );
@@ -94,7 +94,7 @@ export class AuthEffects {
debounceTime(500), // to remove when functionality is implemented debounceTime(500), // to remove when functionality is implemented
switchMap((action: RegistrationAction) => { switchMap((action: RegistrationAction) => {
return this.authService.create(action.payload).pipe( return this.authService.create(action.payload).pipe(
map((user: Eperson) => new RegistrationSuccessAction(user)), map((user: EPerson) => new RegistrationSuccessAction(user)),
catchError((error) => observableOf(new RegistrationErrorAction(error))) catchError((error) => observableOf(new RegistrationErrorAction(error)))
); );
}) })

View File

@@ -42,7 +42,7 @@ export class AuthInterceptor implements HttpInterceptor {
} }
private isSuccess(response: HttpResponseBase): boolean { private isSuccess(response: HttpResponseBase): boolean {
return response.status === 200; return (response.status === 200 || response.status === 204);
} }
private isAuthRequest(http: HttpRequest<any> | HttpResponseBase): boolean { private isAuthRequest(http: HttpRequest<any> | HttpResponseBase): boolean {

View File

@@ -21,7 +21,7 @@ import {
SetRedirectUrlAction SetRedirectUrlAction
} from './auth.actions'; } from './auth.actions';
import { AuthTokenInfo } from './models/auth-token-info.model'; import { AuthTokenInfo } from './models/auth-token-info.model';
import { EpersonMock } from '../../shared/testing/eperson-mock'; import { EPersonMock } from '../../shared/testing/eperson-mock';
describe('authReducer', () => { describe('authReducer', () => {
@@ -107,7 +107,7 @@ describe('authReducer', () => {
loading: true, loading: true,
info: undefined info: undefined
}; };
const action = new AuthenticatedSuccessAction(true, mockTokenInfo, EpersonMock); const action = new AuthenticatedSuccessAction(true, mockTokenInfo, EPersonMock);
const newState = authReducer(initialState, action); const newState = authReducer(initialState, action);
state = { state = {
authenticated: true, authenticated: true,
@@ -116,7 +116,7 @@ describe('authReducer', () => {
error: undefined, error: undefined,
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock user: EPersonMock
}; };
expect(newState).toEqual(state); expect(newState).toEqual(state);
}); });
@@ -182,7 +182,7 @@ describe('authReducer', () => {
error: undefined, error: undefined,
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock user: EPersonMock
}; };
const action = new LogOutAction(); const action = new LogOutAction();
@@ -199,7 +199,7 @@ describe('authReducer', () => {
error: undefined, error: undefined,
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock user: EPersonMock
}; };
const action = new LogOutSuccessAction(); const action = new LogOutSuccessAction();
@@ -225,7 +225,7 @@ describe('authReducer', () => {
error: undefined, error: undefined,
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock user: EPersonMock
}; };
const action = new LogOutErrorAction(mockError); const action = new LogOutErrorAction(mockError);
@@ -237,7 +237,7 @@ describe('authReducer', () => {
error: 'Test error message', error: 'Test error message',
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock user: EPersonMock
}; };
expect(newState).toEqual(state); expect(newState).toEqual(state);
}); });
@@ -250,7 +250,7 @@ describe('authReducer', () => {
error: undefined, error: undefined,
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock user: EPersonMock
}; };
const newTokenInfo = new AuthTokenInfo('Refreshed token'); const newTokenInfo = new AuthTokenInfo('Refreshed token');
const action = new RefreshTokenAction(newTokenInfo); const action = new RefreshTokenAction(newTokenInfo);
@@ -262,7 +262,7 @@ describe('authReducer', () => {
error: undefined, error: undefined,
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock, user: EPersonMock,
refreshing: true refreshing: true
}; };
expect(newState).toEqual(state); expect(newState).toEqual(state);
@@ -276,7 +276,7 @@ describe('authReducer', () => {
error: undefined, error: undefined,
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock, user: EPersonMock,
refreshing: true refreshing: true
}; };
const newTokenInfo = new AuthTokenInfo('Refreshed token'); const newTokenInfo = new AuthTokenInfo('Refreshed token');
@@ -289,7 +289,7 @@ describe('authReducer', () => {
error: undefined, error: undefined,
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock, user: EPersonMock,
refreshing: false refreshing: false
}; };
expect(newState).toEqual(state); expect(newState).toEqual(state);
@@ -303,7 +303,7 @@ describe('authReducer', () => {
error: undefined, error: undefined,
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock, user: EPersonMock,
refreshing: true refreshing: true
}; };
const action = new RefreshTokenErrorAction(); const action = new RefreshTokenErrorAction();
@@ -329,7 +329,7 @@ describe('authReducer', () => {
error: undefined, error: undefined,
loading: false, loading: false,
info: undefined, info: undefined,
user: EpersonMock user: EPersonMock
}; };
state = { state = {

View File

@@ -12,7 +12,7 @@ import {
SetRedirectUrlAction SetRedirectUrlAction
} from './auth.actions'; } from './auth.actions';
// import models // import models
import { Eperson } from '../eperson/models/eperson.model'; import { EPerson } from '../eperson/models/eperson.model';
import { AuthTokenInfo } from './models/auth-token-info.model'; import { AuthTokenInfo } from './models/auth-token-info.model';
/** /**
@@ -46,7 +46,7 @@ export interface AuthState {
refreshing?: boolean; refreshing?: boolean;
// the authenticated user // the authenticated user
user?: Eperson; user?: EPerson;
} }
/** /**

View File

@@ -17,10 +17,12 @@ import { AuthRequestServiceStub } from '../../shared/testing/auth-request-servic
import { AuthRequestService } from './auth-request.service'; import { AuthRequestService } from './auth-request.service';
import { AuthStatus } from './models/auth-status.model'; import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo } from './models/auth-token-info.model'; import { AuthTokenInfo } from './models/auth-token-info.model';
import { Eperson } from '../eperson/models/eperson.model'; import { EPerson } from '../eperson/models/eperson.model';
import { EpersonMock } from '../../shared/testing/eperson-mock'; import { EPersonMock } from '../../shared/testing/eperson-mock';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import { ClientCookieService } from '../../shared/services/client-cookie.service'; import { ClientCookieService } from '../../shared/services/client-cookie.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service';
describe('AuthService test', () => { describe('AuthService test', () => {
@@ -41,9 +43,9 @@ describe('AuthService test', () => {
loaded: true, loaded: true,
loading: false, loading: false,
authToken: token, authToken: token,
user: EpersonMock user: EPersonMock
}; };
const rdbService = getMockRemoteDataBuildService();
describe('', () => { describe('', () => {
beforeEach(() => { beforeEach(() => {
@@ -60,6 +62,7 @@ describe('AuthService test', () => {
{provide: Router, useValue: routerStub}, {provide: Router, useValue: routerStub},
{provide: ActivatedRoute, useValue: routeStub}, {provide: ActivatedRoute, useValue: routeStub},
{provide: Store, useValue: mockStore}, {provide: Store, useValue: mockStore},
{provide: RemoteDataBuildService, useValue: rdbService},
CookieService, CookieService,
AuthService AuthService
], ],
@@ -78,7 +81,7 @@ describe('AuthService test', () => {
}); });
it('should return the authenticated user object when user token is valid', () => { it('should return the authenticated user object when user token is valid', () => {
authService.authenticatedUser(new AuthTokenInfo('test_token')).subscribe((user: Eperson) => { authService.authenticatedUser(new AuthTokenInfo('test_token')).subscribe((user: EPerson) => {
expect(user).toBeDefined(); expect(user).toBeDefined();
}); });
}); });
@@ -120,6 +123,7 @@ describe('AuthService test', () => {
{provide: AuthRequestService, useValue: authRequest}, {provide: AuthRequestService, useValue: authRequest},
{provide: REQUEST, useValue: {}}, {provide: REQUEST, useValue: {}},
{provide: Router, useValue: routerStub}, {provide: Router, useValue: routerStub},
{provide: RemoteDataBuildService, useValue: rdbService},
CookieService CookieService
] ]
}).compileComponents(); }).compileComponents();
@@ -131,7 +135,7 @@ describe('AuthService test', () => {
(state as any).core = Object.create({}); (state as any).core = Object.create({});
(state as any).core.auth = authenticatedState; (state as any).core.auth = authenticatedState;
}); });
authService = new AuthService({}, window, authReqService, router, cookieService, store); authService = new AuthService({}, window, authReqService, router, cookieService, store, rdbService);
})); }));
it('should return true when user is logged in', () => { it('should return true when user is logged in', () => {
@@ -183,14 +187,14 @@ describe('AuthService test', () => {
loaded: true, loaded: true,
loading: false, loading: false,
authToken: expiredToken, authToken: expiredToken,
user: EpersonMock user: EPersonMock
}; };
store store
.subscribe((state) => { .subscribe((state) => {
(state as any).core = Object.create({}); (state as any).core = Object.create({});
(state as any).core.auth = authenticatedState; (state as any).core.auth = authenticatedState;
}); });
authService = new AuthService({}, window, authReqService, router, cookieService, store); authService = new AuthService({}, window, authReqService, router, cookieService, store, rdbService);
storage = (authService as any).storage; storage = (authService as any).storage;
spyOn(storage, 'get'); spyOn(storage, 'get');
spyOn(storage, 'remove'); spyOn(storage, 'remove');

View File

@@ -1,11 +1,12 @@
import { of as observableOf, Observable } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { import {
take,
filter,
startWith,
first,
distinctUntilChanged, distinctUntilChanged,
filter,
first,
map, map,
startWith,
switchMap,
take,
withLatestFrom withLatestFrom
} from 'rxjs/operators'; } from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
@@ -16,8 +17,9 @@ import { REQUEST } from '@nguniversal/express-engine/tokens';
import { RouterReducerState } from '@ngrx/router-store'; import { RouterReducerState } from '@ngrx/router-store';
import { select, Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { CookieAttributes } from 'js-cookie'; import { CookieAttributes } from 'js-cookie';
import { Observable } from 'rxjs/Observable';
import { Eperson } from '../eperson/models/eperson.model'; import { EPerson } from '../eperson/models/eperson.model';
import { AuthRequestService } from './auth-request.service'; import { AuthRequestService } from './auth-request.service';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
@@ -35,6 +37,8 @@ import { AppState, routerStateSelector } from '../../app.reducer';
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions'; import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions';
import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service'; import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util'; import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedEPerson } from '../eperson/models/normalized-eperson.model';
export const LOGIN_ROUTE = '/login'; export const LOGIN_ROUTE = '/login';
export const LOGOUT_ROUTE = '/logout'; export const LOGOUT_ROUTE = '/logout';
@@ -58,7 +62,8 @@ export class AuthService {
protected authRequestService: AuthRequestService, protected authRequestService: AuthRequestService,
protected router: Router, protected router: Router,
protected storage: CookieService, protected storage: CookieService,
protected store: Store<AppState>) { protected store: Store<AppState>,
protected rdbService: RemoteDataBuildService) {
this.store.pipe( this.store.pipe(
select(isAuthenticated), select(isAuthenticated),
startWith(false) startWith(false)
@@ -132,7 +137,7 @@ export class AuthService {
* Returns the authenticated user * Returns the authenticated user
* @returns {User} * @returns {User}
*/ */
public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> { public authenticatedUser(token: AuthTokenInfo): Observable<EPerson> {
// Determine if the user has an existing auth session on the server // Determine if the user has an existing auth session on the server
const options: HttpOptions = Object.create({}); const options: HttpOptions = Object.create({});
let headers = new HttpHeaders(); let headers = new HttpHeaders();
@@ -140,9 +145,13 @@ export class AuthService {
headers = headers.append('Authorization', `Bearer ${token.accessToken}`); headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
options.headers = headers; options.headers = headers;
return this.authRequestService.getRequest('status', options).pipe( return this.authRequestService.getRequest('status', options).pipe(
map((status: AuthStatus) => { switchMap((status: AuthStatus) => {
if (status.authenticated) { if (status.authenticated) {
return status.eperson[0]; // TODO this should be cleaned up, AuthStatus could be parsed by the RemoteDataService as a whole...
// Review when https://jira.duraspace.org/browse/DS-4006 is fixed
// See https://github.com/DSpace/dspace-angular/issues/292
const person$ = this.rdbService.buildSingle<NormalizedEPerson, EPerson>(status.eperson.toString());
return person$.pipe(map((eperson) => eperson.payload));
} else { } else {
throw(new Error('Not authenticated')); throw(new Error('Not authenticated'));
} }
@@ -206,7 +215,7 @@ export class AuthService {
* Create a new user * Create a new user
* @returns {User} * @returns {User}
*/ */
public create(user: Eperson): Observable<Eperson> { public create(user: EPerson): Observable<EPerson> {
// Normally you would do an HTTP request to POST the user // Normally you would do an HTTP request to POST the user
// details and then return the new user object // details and then return the new user object
// but, let's just return the new user for this example. // but, let's just return the new user for this example.
@@ -357,8 +366,12 @@ export class AuthService {
this.router.navigated = false; this.router.navigated = false;
const url = decodeURIComponent(redirectUrl); const url = decodeURIComponent(redirectUrl);
this.router.navigateByUrl(url); this.router.navigateByUrl(url);
/* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
// this._window.nativeWindow.location.href = url;
} else { } else {
this.router.navigate(['/']); this.router.navigate(['/']);
/* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
// this._window.nativeWindow.location.href = '/';
} }
}) })

View File

@@ -1,7 +1,8 @@
import { AuthError } from './auth-error.model'; import { AuthError } from './auth-error.model';
import { AuthTokenInfo } from './auth-token-info.model'; import { AuthTokenInfo } from './auth-token-info.model';
import { DSpaceObject } from '../../shared/dspace-object.model'; import { EPerson } from '../../eperson/models/eperson.model';
import { Eperson } from '../../eperson/models/eperson.model'; import { RemoteData } from '../../data/remote-data';
import { Observable } from 'rxjs/Observable';
export class AuthStatus { export class AuthStatus {
@@ -13,7 +14,7 @@ export class AuthStatus {
error?: AuthError; error?: AuthError;
eperson: Eperson[]; eperson: Observable<RemoteData<EPerson>>;
token?: AuthTokenInfo; token?: AuthTokenInfo;

View File

@@ -1,12 +1,18 @@
import { AuthStatus } from './auth-status.model'; import { AuthStatus } from './auth-status.model';
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize'; import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { mapsTo } from '../../cache/builders/build-decorators'; import { mapsTo, relationship } from '../../cache/builders/build-decorators';
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model'; import { ResourceType } from '../../shared/resource-type';
import { Eperson } from '../../eperson/models/eperson.model'; import { NormalizedObject } from '../../cache/models/normalized-object.model';
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
@mapsTo(AuthStatus) @mapsTo(AuthStatus)
@inheritSerialization(NormalizedDSpaceObject) @inheritSerialization(NormalizedObject)
export class NormalizedAuthStatus extends NormalizedDSpaceObject { export class NormalizedAuthStatus extends NormalizedObject {
@autoserialize
id: string;
@autoserializeAs(new IDToUUIDSerializer('auth-status'), 'id')
uuid: string;
/** /**
* True if REST API is up and running, should never return false * True if REST API is up and running, should never return false
@@ -20,7 +26,7 @@ export class NormalizedAuthStatus extends NormalizedDSpaceObject {
@autoserialize @autoserialize
authenticated: boolean; authenticated: boolean;
@autoserializeAs(Eperson) @relationship(ResourceType.EPerson, false)
eperson: Eperson[]; @autoserialize
eperson: string;
} }

View File

@@ -1,5 +1,3 @@
import {first, map} from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -10,7 +8,9 @@ import { isNotEmpty } from '../../shared/empty.util';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { AuthTokenInfo } from './models/auth-token-info.model'; import { AuthTokenInfo } from './models/auth-token-info.model';
import { CheckAuthenticationTokenAction } from './auth.actions'; import { CheckAuthenticationTokenAction } from './auth.actions';
import { Eperson } from '../eperson/models/eperson.model'; import { EPerson } from '../eperson/models/eperson.model';
import { NormalizedEPerson } from '../eperson/models/normalized-eperson.model';
import { first, switchMap, map } from 'rxjs/operators';
/** /**
* The auth service. * The auth service.
@@ -22,7 +22,7 @@ export class ServerAuthService extends AuthService {
* Returns the authenticated user * Returns the authenticated user
* @returns {User} * @returns {User}
*/ */
public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> { public authenticatedUser(token: AuthTokenInfo): Observable<EPerson> {
// Determine if the user has an existing auth session on the server // Determine if the user has an existing auth session on the server
const options: HttpOptions = Object.create({}); const options: HttpOptions = Object.create({});
let headers = new HttpHeaders(); let headers = new HttpHeaders();
@@ -35,9 +35,15 @@ export class ServerAuthService extends AuthService {
options.headers = headers; options.headers = headers;
return this.authRequestService.getRequest('status', options).pipe( return this.authRequestService.getRequest('status', options).pipe(
map((status: AuthStatus) => { switchMap((status: AuthStatus) => {
if (status.authenticated) { if (status.authenticated) {
return status.eperson[0];
// TODO this should be cleaned up, AuthStatus could be parsed by the RemoteDataService as a whole...
const person$ = this.rdbService.buildSingle<NormalizedEPerson, EPerson>(status.eperson.toString());
// person$.subscribe(() => console.log('test'));
return person$.pipe(
map((eperson) => eperson.payload)
);
} else { } else {
throw(new Error('Not authenticated')); throw(new Error('Not authenticated'));
} }

View File

@@ -8,6 +8,8 @@ import { ResourceType } from '../../shared/resource-type';
import { NormalizedObject } from './normalized-object.model'; import { NormalizedObject } from './normalized-object.model';
import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model'; import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model';
import { NormalizedResourcePolicy } from './normalized-resource-policy.model'; import { NormalizedResourcePolicy } from './normalized-resource-policy.model';
import { NormalizedEPerson } from '../../eperson/models/normalized-eperson.model';
import { NormalizedGroup } from '../../eperson/models/normalized-group.model';
export class NormalizedObjectFactory { export class NormalizedObjectFactory {
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> { public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
@@ -33,6 +35,12 @@ export class NormalizedObjectFactory {
case ResourceType.ResourcePolicy: { case ResourceType.ResourcePolicy: {
return NormalizedResourcePolicy return NormalizedResourcePolicy
} }
case ResourceType.EPerson: {
return NormalizedEPerson
}
case ResourceType.Group: {
return NormalizedGroup
}
default: { default: {
return undefined; return undefined;
} }

View File

@@ -7,6 +7,8 @@ import { GlobalConfig } from '../../../config/global-config.interface';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
import { PaginatedList } from './paginated-list'; import { PaginatedList } from './paginated-list';
import { NormalizedObject } from '../cache/models/normalized-object.model'; import { NormalizedObject } from '../cache/models/normalized-object.model';
import { ResourceType } from '../shared/resource-type';
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
function isObjectLevel(halObj: any) { function isObjectLevel(halObj: any) {
return isNotEmpty(halObj._links) && hasValue(halObj._links.self); return isNotEmpty(halObj._links) && hasValue(halObj._links.self);
@@ -33,6 +35,7 @@ export abstract class BaseResponseParsingService {
} else if (Array.isArray(data)) { } else if (Array.isArray(data)) {
return this.processArray(data, requestHref); return this.processArray(data, requestHref);
} else if (isObjectLevel(data)) { } else if (isObjectLevel(data)) {
data = this.fixBadEPersonRestResponse(data);
const object = this.deserialize(data); const object = this.deserialize(data);
if (isNotEmpty(data._embedded)) { if (isNotEmpty(data._embedded)) {
Object Object
@@ -52,6 +55,7 @@ export abstract class BaseResponseParsingService {
} }
}); });
} }
this.cache(object, requestHref); this.cache(object, requestHref);
return object; return object;
} }
@@ -144,4 +148,23 @@ export abstract class BaseResponseParsingService {
} }
return obj[keys[0]]; return obj[keys[0]];
} }
// TODO Remove when https://jira.duraspace.org/browse/DS-4006 is fixed
// See https://github.com/DSpace/dspace-angular/issues/292
private fixBadEPersonRestResponse(obj: any): any {
if (obj.type === ResourceType.EPerson) {
const groups = obj.groups;
const normGroups = [];
if (isNotEmpty(groups)) {
groups.forEach((group) => {
const parts = ['eperson', 'groups', group.uuid];
const href = new RESTURLCombiner(this.EnvConfig, ...parts).toString();
normGroups.push(href);
}
)
}
return Object.assign({}, obj, { groups: normGroups });
}
return obj;
}
} }

View File

@@ -69,7 +69,7 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
hrefObs.pipe( hrefObs.pipe(
filter((href: string) => hasValue(href)), filter((href: string) => hasValue(href)),
take(1),) take(1))
.subscribe((href: string) => { .subscribe((href: string) => {
const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
this.requestService.configure(request); this.requestService.configure(request);

View File

@@ -1,7 +1,7 @@
import { DSpaceObject } from '../../shared/dspace-object.model'; import { DSpaceObject } from '../../shared/dspace-object.model';
import { Group } from './group.model'; import { Group } from './group.model';
export class Eperson extends DSpaceObject { export class EPerson extends DSpaceObject {
public handle: string; public handle: string;

View File

@@ -2,13 +2,13 @@ import { autoserialize, inheritSerialization } from 'cerialize';
import { CacheableObject } from '../../cache/object-cache.reducer'; import { CacheableObject } from '../../cache/object-cache.reducer';
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model'; import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
import { Eperson } from './eperson.model'; import { EPerson } from './eperson.model';
import { mapsTo, relationship } from '../../cache/builders/build-decorators'; import { mapsTo, relationship } from '../../cache/builders/build-decorators';
import { ResourceType } from '../../shared/resource-type'; import { ResourceType } from '../../shared/resource-type';
@mapsTo(Eperson) @mapsTo(EPerson)
@inheritSerialization(NormalizedDSpaceObject) @inheritSerialization(NormalizedDSpaceObject)
export class NormalizedEpersonModel extends NormalizedDSpaceObject implements CacheableObject, ListableObject { export class NormalizedEPerson extends NormalizedDSpaceObject implements CacheableObject, ListableObject {
@autoserialize @autoserialize
public handle: string; public handle: string;

View File

@@ -2,13 +2,12 @@ import { autoserialize, inheritSerialization } from 'cerialize';
import { CacheableObject } from '../../cache/object-cache.reducer'; import { CacheableObject } from '../../cache/object-cache.reducer';
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model'; import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
import { Eperson } from './eperson.model';
import { mapsTo } from '../../cache/builders/build-decorators'; import { mapsTo } from '../../cache/builders/build-decorators';
import { Group } from './group.model'; import { Group } from './group.model';
@mapsTo(Group) @mapsTo(Group)
@inheritSerialization(NormalizedDSpaceObject) @inheritSerialization(NormalizedDSpaceObject)
export class NormalizedGroupModel extends NormalizedDSpaceObject implements CacheableObject, ListableObject { export class NormalizedGroup extends NormalizedDSpaceObject implements CacheableObject, ListableObject {
@autoserialize @autoserialize
public handle: string; public handle: string;

View File

@@ -6,7 +6,7 @@ export enum ResourceType {
Item = 'item', Item = 'item',
Collection = 'collection', Collection = 'collection',
Community = 'community', Community = 'community',
Eperson = 'eperson', EPerson = 'eperson',
Group = 'group', Group = 'group',
ResourcePolicy = 'resourcePolicy' ResourcePolicy = 'resourcePolicy'
} }

View File

@@ -9,10 +9,6 @@ import { of as observableOf } from 'rxjs';
import { HeaderComponent } from './header.component'; import { HeaderComponent } from './header.component';
import { HeaderState } from './header.reducer'; import { HeaderState } from './header.reducer';
import { HeaderToggleAction } from './header.actions'; import { HeaderToggleAction } from './header.actions';
import { AuthNavMenuComponent } from '../shared/auth-nav-menu/auth-nav-menu.component';
import { LogInComponent } from '../shared/log-in/log-in.component';
import { LogOutComponent } from '../shared/log-out/log-out.component';
import { LoadingComponent } from '../shared/loading/loading.component';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { HostWindowService } from '../shared/host-window.service'; import { HostWindowService } from '../shared/host-window.service';
import { HostWindowServiceStub } from '../shared/testing/host-window-service-stub'; import { HostWindowServiceStub } from '../shared/testing/host-window-service-stub';
@@ -20,6 +16,8 @@ import { RouterStub } from '../shared/testing/router-stub';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import * as ngrx from '@ngrx/store'; import * as ngrx from '@ngrx/store';
import { NO_ERRORS_SCHEMA } from '@angular/core';
let comp: HeaderComponent; let comp: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>; let fixture: ComponentFixture<HeaderComponent>;
let store: Store<HeaderState>; let store: Store<HeaderState>;
@@ -35,11 +33,12 @@ describe('HeaderComponent', () => {
NgbCollapseModule.forRoot(), NgbCollapseModule.forRoot(),
NoopAnimationsModule, NoopAnimationsModule,
ReactiveFormsModule], ReactiveFormsModule],
declarations: [HeaderComponent, AuthNavMenuComponent, LoadingComponent, LogInComponent, LogOutComponent], declarations: [HeaderComponent],
providers: [ providers: [
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
{ provide: Router, useClass: RouterStub }, { provide: Router, useClass: RouterStub },
] ],
schemas: [NO_ERRORS_SCHEMA]
}) })
.compileComponents(); // compile template and css .compileComponents(); // compile template and css
})); }));

View File

@@ -5,7 +5,7 @@ import { By } from '@angular/platform-browser';
import { Store, StoreModule } from '@ngrx/store'; import { Store, StoreModule } from '@ngrx/store';
import { authReducer, AuthState } from '../../core/auth/auth.reducer'; import { authReducer, AuthState } from '../../core/auth/auth.reducer';
import { EpersonMock } from '../testing/eperson-mock'; import { EPersonMock } from '../testing/eperson-mock';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import { AuthNavMenuComponent } from './auth-nav-menu.component'; import { AuthNavMenuComponent } from './auth-nav-menu.component';
@@ -13,6 +13,7 @@ import { HostWindowServiceStub } from '../testing/host-window-service-stub';
import { HostWindowService } from '../host-window.service'; import { HostWindowService } from '../host-window.service';
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
import { AuthService } from '../../core/auth/auth.service';
describe('AuthNavMenuComponent', () => { describe('AuthNavMenuComponent', () => {
@@ -31,7 +32,7 @@ describe('AuthNavMenuComponent', () => {
loaded: true, loaded: true,
loading: false, loading: false,
authToken: new AuthTokenInfo('test_token'), authToken: new AuthTokenInfo('test_token'),
user: EpersonMock user: EPersonMock
}; };
let routerState = { let routerState = {
url: '/home' url: '/home'
@@ -53,6 +54,7 @@ describe('AuthNavMenuComponent', () => {
], ],
providers: [ providers: [
{provide: HostWindowService, useValue: window}, {provide: HostWindowService, useValue: window},
{provide: AuthService, useValue: {setRedirectUrl: () => { /*empty*/ }}}
], ],
schemas: [ schemas: [
CUSTOM_ELEMENTS_SCHEMA CUSTOM_ELEMENTS_SCHEMA
@@ -222,6 +224,7 @@ describe('AuthNavMenuComponent', () => {
], ],
providers: [ providers: [
{provide: HostWindowService, useValue: window}, {provide: HostWindowService, useValue: window},
{provide: AuthService, useValue: {setRedirectUrl: () => { /*empty*/ }}}
], ],
schemas: [ schemas: [
CUSTOM_ELEMENTS_SCHEMA CUSTOM_ELEMENTS_SCHEMA

View File

@@ -14,8 +14,9 @@ import {
isAuthenticated, isAuthenticated,
isAuthenticationLoading isAuthenticationLoading
} from '../../core/auth/selectors'; } from '../../core/auth/selectors';
import { Eperson } from '../../core/eperson/models/eperson.model'; import { EPerson } from '../../core/eperson/models/eperson.model';
import { LOGIN_ROUTE, LOGOUT_ROUTE } from '../../core/auth/auth.service'; import { AuthService, LOGIN_ROUTE, LOGOUT_ROUTE } from '../../core/auth/auth.service';
import { Subscription } from 'rxjs';
@Component({ @Component({
selector: 'ds-auth-nav-menu', selector: 'ds-auth-nav-menu',
@@ -40,10 +41,14 @@ export class AuthNavMenuComponent implements OnInit {
public showAuth = observableOf(false); public showAuth = observableOf(false);
public user: Observable<Eperson>; public user: Observable<EPerson>;
public sub: Subscription;
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
private windowService: HostWindowService) { private windowService: HostWindowService,
private authService: AuthService
) {
this.isXsOrSm$ = this.windowService.isXsOrSm(); this.isXsOrSm$ = this.windowService.isXsOrSm();
} }
@@ -60,7 +65,12 @@ export class AuthNavMenuComponent implements OnInit {
select(routerStateSelector), select(routerStateSelector),
filter((router: RouterReducerState) => isNotUndefined(router) && isNotUndefined(router.state)), filter((router: RouterReducerState) => isNotUndefined(router) && isNotUndefined(router.state)),
map((router: RouterReducerState) => { map((router: RouterReducerState) => {
return !router.state.url.startsWith(LOGIN_ROUTE) && !router.state.url.startsWith(LOGOUT_ROUTE); const url = router.state.url;
const show = !router.state.url.startsWith(LOGIN_ROUTE) && !router.state.url.startsWith(LOGOUT_ROUTE);
if (show) {
this.authService.setRedirectUrl(url);
}
return show;
}) })
); );
} }

View File

@@ -7,8 +7,8 @@ import { Store, StoreModule } from '@ngrx/store';
import { LogInComponent } from './log-in.component'; import { LogInComponent } from './log-in.component';
import { authReducer } from '../../core/auth/auth.reducer'; import { authReducer } from '../../core/auth/auth.reducer';
import { EpersonMock } from '../testing/eperson-mock'; import { EPersonMock } from '../testing/eperson-mock';
import { Eperson } from '../../core/eperson/models/eperson.model'; import { EPerson } from '../../core/eperson/models/eperson.model';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { AuthService } from '../../core/auth/auth.service'; import { AuthService } from '../../core/auth/auth.service';
import { AuthServiceStub } from '../testing/auth-service-stub'; import { AuthServiceStub } from '../testing/auth-service-stub';
@@ -19,7 +19,7 @@ describe('LogInComponent', () => {
let component: LogInComponent; let component: LogInComponent;
let fixture: ComponentFixture<LogInComponent>; let fixture: ComponentFixture<LogInComponent>;
let page: Page; let page: Page;
let user: Eperson; let user: EPerson;
const authState = { const authState = {
authenticated: false, authenticated: false,
@@ -28,7 +28,7 @@ describe('LogInComponent', () => {
}; };
beforeEach(() => { beforeEach(() => {
user = EpersonMock; user = EPersonMock;
}); });
beforeEach(async(() => { beforeEach(async(() => {

View File

@@ -5,8 +5,8 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Store, StoreModule } from '@ngrx/store'; import { Store, StoreModule } from '@ngrx/store';
import { authReducer } from '../../core/auth/auth.reducer'; import { authReducer } from '../../core/auth/auth.reducer';
import { EpersonMock } from '../testing/eperson-mock'; import { EPersonMock } from '../testing/eperson-mock';
import { Eperson } from '../../core/eperson/models/eperson.model'; import { EPerson } from '../../core/eperson/models/eperson.model';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
@@ -18,7 +18,7 @@ describe('LogOutComponent', () => {
let component: LogOutComponent; let component: LogOutComponent;
let fixture: ComponentFixture<LogOutComponent>; let fixture: ComponentFixture<LogOutComponent>;
let page: Page; let page: Page;
let user: Eperson; let user: EPerson;
const authState = { const authState = {
authenticated: false, authenticated: false,
@@ -28,7 +28,7 @@ describe('LogOutComponent', () => {
const routerStub = new RouterStub(); const routerStub = new RouterStub();
beforeEach(() => { beforeEach(() => {
user = EpersonMock; user = EPersonMock;
}); });
beforeEach(async(() => { beforeEach(async(() => {

View File

@@ -5,6 +5,7 @@ import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { RequestEntry } from '../../core/data/request.reducer'; import { RequestEntry } from '../../core/data/request.reducer';
import { hasValue } from '../empty.util'; import { hasValue } from '../empty.util';
import { NormalizedObject } from '../../core/cache/models/normalized-object.model';
export function getMockRemoteDataBuildService(toRemoteDataObservable$?: Observable<RemoteData<any>>): RemoteDataBuildService { export function getMockRemoteDataBuildService(toRemoteDataObservable$?: Observable<RemoteData<any>>): RemoteDataBuildService {
return { return {
@@ -17,7 +18,8 @@ export function getMockRemoteDataBuildService(toRemoteDataObservable$?: Observab
payload payload
} as RemoteData<any>))) } as RemoteData<any>)))
} }
} },
buildSingle: (href$: string | Observable<string>) => Observable.of(new RemoteData(false, false, true, undefined, {}))
} as RemoteDataBuildService; } as RemoteDataBuildService;
} }

View File

@@ -2,12 +2,13 @@ import {of as observableOf, Observable } from 'rxjs';
import { HttpOptions } from '../../core/dspace-rest-v2/dspace-rest-v2.service'; import { HttpOptions } from '../../core/dspace-rest-v2/dspace-rest-v2.service';
import { AuthStatus } from '../../core/auth/models/auth-status.model'; import { AuthStatus } from '../../core/auth/models/auth-status.model';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
import { Eperson } from '../../core/eperson/models/eperson.model'; import { EPerson } from '../../core/eperson/models/eperson.model';
import { isNotEmpty } from '../empty.util'; import { isNotEmpty } from '../empty.util';
import { EpersonMock } from './eperson-mock'; import { EPersonMock } from './eperson-mock';
import { RemoteData } from '../../core/data/remote-data';
export class AuthRequestServiceStub { export class AuthRequestServiceStub {
protected mockUser: Eperson = EpersonMock; protected mockUser: EPerson = EPersonMock;
protected mockTokenInfo = new AuthTokenInfo('test_token'); protected mockTokenInfo = new AuthTokenInfo('test_token');
public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable<any> { public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable<any> {
@@ -26,7 +27,7 @@ export class AuthRequestServiceStub {
if (this.validateToken(token)) { if (this.validateToken(token)) {
authStatusStub.authenticated = true; authStatusStub.authenticated = true;
authStatusStub.token = this.mockTokenInfo; authStatusStub.token = this.mockTokenInfo;
authStatusStub.eperson = [this.mockUser]; authStatusStub.eperson = Observable.of(new RemoteData<EPerson>(false, false, true, undefined, this.mockUser));
} else { } else {
authStatusStub.authenticated = false; authStatusStub.authenticated = false;
} }
@@ -45,7 +46,7 @@ export class AuthRequestServiceStub {
if (this.validateToken(token)) { if (this.validateToken(token)) {
authStatusStub.authenticated = true; authStatusStub.authenticated = true;
authStatusStub.token = this.mockTokenInfo; authStatusStub.token = this.mockTokenInfo;
authStatusStub.eperson = [this.mockUser]; authStatusStub.eperson = Observable.of(new RemoteData<EPerson>(false, false, true, undefined, this.mockUser));
} else { } else {
authStatusStub.authenticated = false; authStatusStub.authenticated = false;
} }

View File

@@ -2,8 +2,9 @@
import {of as observableOf, Observable } from 'rxjs'; import {of as observableOf, Observable } from 'rxjs';
import { AuthStatus } from '../../core/auth/models/auth-status.model'; import { AuthStatus } from '../../core/auth/models/auth-status.model';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
import { EpersonMock } from './eperson-mock'; import { EPersonMock } from './eperson-mock';
import { Eperson } from '../../core/eperson/models/eperson.model'; import { EPerson } from '../../core/eperson/models/eperson.model';
import { RemoteData } from '../../core/data/remote-data';
export class AuthServiceStub { export class AuthServiceStub {
@@ -20,7 +21,7 @@ export class AuthServiceStub {
authStatus.okay = true; authStatus.okay = true;
authStatus.authenticated = true; authStatus.authenticated = true;
authStatus.token = this.token; authStatus.token = this.token;
authStatus.eperson = [EpersonMock]; authStatus.eperson = observableOf(new RemoteData<EPerson>(false, false, true, undefined, EPersonMock));
return observableOf(authStatus); return observableOf(authStatus);
} else { } else {
console.log('error'); console.log('error');
@@ -28,9 +29,9 @@ export class AuthServiceStub {
} }
} }
public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> { public authenticatedUser(token: AuthTokenInfo): Observable<EPerson> {
if (token.accessToken === 'token_test') { if (token.accessToken === 'token_test') {
return observableOf(EpersonMock); return observableOf(EPersonMock);
} else { } else {
throw(new Error('Message Error test')); throw(new Error('Message Error test'));
} }

View File

@@ -1,6 +1,6 @@
import { Eperson } from '../../core/eperson/models/eperson.model'; import { EPerson } from '../../core/eperson/models/eperson.model';
export const EpersonMock: Eperson = Object.assign(new Eperson(),{ export const EPersonMock: EPerson = Object.assign(new EPerson(),{
handle: null, handle: null,
groups: [], groups: [],
netid: 'test@test.com', netid: 'test@test.com',

View File

@@ -1965,14 +1965,14 @@ copy-webpack-plugin@^4.4.1:
p-limit "^1.0.0" p-limit "^1.0.0"
serialize-javascript "^1.4.0" serialize-javascript "^1.4.0"
core-js@2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
core-js@^2.2.0, core-js@^2.4.0: core-js@^2.2.0, core-js@^2.4.0:
version "2.5.7" version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
core-js@^2.5.7:
version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
core-js@~2.3.0: core-js@~2.3.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65"