diff --git a/git b/git new file mode 100644 index 0000000000..e69de29bb2 diff --git a/resources/i18n/de.json b/resources/i18n/de.json index d184e7d091..a3ac45b82f 100644 --- a/resources/i18n/de.json +++ b/resources/i18n/de.json @@ -97,6 +97,7 @@ "login.form.new-user": "Sind Sie neu hier? Klicken Sie hier, um sich zu registrieren.", "login.form.password": "Passwort", "login.form.submit": "Einloggen", + "login.form.ssoLogin": "Shibboleth", "login.title": "Einloggen", "logout.form.header": "Ausloggen aus DSpace", "logout.form.submit": "Ausloggen", diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 5276ec469f..877a96a63f 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -353,7 +353,7 @@ "login.form.new-user": "New user? Click here to register.", "login.form.password": "Password", "login.form.submit": "Log in", - "login.shibbForm.submit": "Shibboleth", + "login.form.ssoLogin": "Shibboleth", "login.title": "Login", "logout.form.header": "Log out from DSpace", "logout.form.submit": "Log out", diff --git a/src/app/+login-page/shibbolethTargetPage/const/shibbConstants.ts b/src/app/+login-page/shibbolethTargetPage/const/shibbConstants.ts new file mode 100644 index 0000000000..b424e24fc9 --- /dev/null +++ b/src/app/+login-page/shibbolethTargetPage/const/shibbConstants.ts @@ -0,0 +1,3 @@ +export class ShibbConstants { + public static readonly SHIBBOLETH_REDIRECT_ROUTE = 'shibboleth'; +} diff --git a/src/app/+login-page/shibbolethTargetPage/shibboleth.component.html b/src/app/+login-page/shibbolethTargetPage/shibboleth-target-page.component.html similarity index 100% rename from src/app/+login-page/shibbolethTargetPage/shibboleth.component.html rename to src/app/+login-page/shibbolethTargetPage/shibboleth-target-page.component.html diff --git a/src/app/+login-page/shibbolethTargetPage/shibboleth.component.scss b/src/app/+login-page/shibbolethTargetPage/shibboleth-target-page.component.scss similarity index 100% rename from src/app/+login-page/shibbolethTargetPage/shibboleth.component.scss rename to src/app/+login-page/shibbolethTargetPage/shibboleth-target-page.component.scss diff --git a/src/app/+login-page/shibbolethTargetPage/shibboleth.component.spec.ts b/src/app/+login-page/shibbolethTargetPage/shibboleth-target-page.component.spec.ts similarity index 55% rename from src/app/+login-page/shibbolethTargetPage/shibboleth.component.spec.ts rename to src/app/+login-page/shibbolethTargetPage/shibboleth-target-page.component.spec.ts index a12c43866f..bfae541aef 100644 --- a/src/app/+login-page/shibbolethTargetPage/shibboleth.component.spec.ts +++ b/src/app/+login-page/shibbolethTargetPage/shibboleth-target-page.component.spec.ts @@ -1,20 +1,20 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ShibbolethComponent } from './shibboleth.component'; +import { ShibbolethTargetPageComponent } from './shibboleth-target-page.component'; describe('ShibbolethComponent', () => { - let component: ShibbolethComponent; - let fixture: ComponentFixture; + let component: ShibbolethTargetPageComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ ShibbolethComponent ] + declarations: [ ShibbolethTargetPageComponent ] }) .compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(ShibbolethComponent); + fixture = TestBed.createComponent(ShibbolethTargetPageComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/+login-page/shibbolethTargetPage/shibboleth.component.ts b/src/app/+login-page/shibbolethTargetPage/shibboleth-target-page.component.ts similarity index 77% rename from src/app/+login-page/shibbolethTargetPage/shibboleth.component.ts rename to src/app/+login-page/shibbolethTargetPage/shibboleth-target-page.component.ts index a06b2c0044..ef4510644a 100644 --- a/src/app/+login-page/shibbolethTargetPage/shibboleth.component.ts +++ b/src/app/+login-page/shibbolethTargetPage/shibboleth-target-page.component.ts @@ -6,10 +6,10 @@ import { Observable, of } from 'rxjs'; @Component({ selector: 'ds-shibboleth-page', - templateUrl: './shibboleth.component.html', - styleUrls: ['./shibboleth.component.scss'] + templateUrl: './shibboleth-target-page.component.html', + styleUrls: ['./shibboleth-target-page.component.scss'] }) -export class ShibbolethComponent implements OnInit { +export class ShibbolethTargetPageComponent implements OnInit { /** * True if the shibboleth authentication is loading. diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 14bf3ef35e..5dcb57615e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,7 +3,8 @@ import { RouterModule } from '@angular/router'; import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; import { AuthenticatedGuard } from './core/auth/authenticated.guard'; -import { ShibbolethComponent } from './+login-page/shibbolethTargetPage/shibboleth.component'; +import { ShibbolethTargetPageComponent } from './+login-page/shibbolethTargetPage/shibboleth-target-page.component'; +import { ShibbConstants } from './+login-page/shibbolethTargetPage/const/shibbConstants'; const ITEM_MODULE_PATH = 'items'; export function getItemModulePath() { @@ -40,7 +41,7 @@ export function getAdminModulePath() { { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' }, { path: 'workspaceitems', loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule' }, { path: 'workflowitems', loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowItemsEditPageModule' }, - { path: 'shibboleth', pathMatch: 'full', component: ShibbolethComponent }, + { path: ShibbConstants.SHIBBOLETH_REDIRECT_ROUTE, pathMatch: 'full', component: ShibbolethTargetPageComponent }, { path: '**', pathMatch: 'full', component: PageNotFoundComponent }, ]) ], diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 817e8d0a79..46da97e8f6 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -39,7 +39,7 @@ import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/e import { NavbarModule } from './navbar/navbar.module'; import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module'; import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module'; -import { ShibbolethComponent } from './+login-page/shibbolethTargetPage/shibboleth.component'; +import { ShibbolethTargetPageComponent } from './+login-page/shibbolethTargetPage/shibboleth-target-page.component'; export function getConfig() { return ENV_CONFIG; @@ -113,7 +113,7 @@ const DECLARATIONS = [ PageNotFoundComponent, NotificationComponent, NotificationsBoardComponent, - ShibbolethComponent + ShibbolethTargetPageComponent ]; const EXPORTS = [ diff --git a/src/app/core/auth/auth.interceptor.ts b/src/app/core/auth/auth.interceptor.ts index 405268cd62..6fab131fad 100644 --- a/src/app/core/auth/auth.interceptor.ts +++ b/src/app/core/auth/auth.interceptor.ts @@ -5,7 +5,8 @@ import { Injectable, Injector } from '@angular/core'; import { HttpErrorResponse, HttpEvent, - HttpHandler, HttpHeaders, + HttpHandler, + HttpHeaders, HttpInterceptor, HttpRequest, HttpResponse, @@ -17,12 +18,12 @@ import { AppState } from '../../app.reducer'; import { AuthService } from './auth.service'; import { AuthStatus } from './models/auth-status.model'; import { AuthTokenInfo } from './models/auth-token-info.model'; -import { isNotEmpty, isUndefined, isNotNull } from '../../shared/empty.util'; +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 { AuthError } from './models/auth-error.model'; import { AuthMethodModel } from './models/auth-method.model'; +import { AuthMethodType } from '../../shared/log-in/methods/authMethods-type'; @Injectable() export class AuthInterceptor implements HttpInterceptor { @@ -72,14 +73,31 @@ export class AuthInterceptor implements HttpInterceptor { return parsedLocation; } + private sortAuthMethods(authMethodModels: AuthMethodModel[]): AuthMethodModel[] { + const sortedAuthMethodModels: AuthMethodModel[] = new Array(); + authMethodModels.forEach((method) => { + if (method.authMethodType === AuthMethodType.Password) { + sortedAuthMethodModels.push(method); + } + }); + + authMethodModels.forEach((method) => { + if (method.authMethodType !== AuthMethodType.Password) { + sortedAuthMethodModels.push(method); + } + }); + + return sortedAuthMethodModels; + } + private parseAuthMethodsfromHeaders(headers: HttpHeaders): AuthMethodModel[] { - const authMethodModels: AuthMethodModel[] = []; + let authMethodModels: AuthMethodModel[] = []; 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; const realms = completeWWWauthenticateHeader.match(regex); - console.log('realms: ', realms) + // console.log('realms: ', realms) // tslint:disable-next-line:forin for (const j in realms) { @@ -87,26 +105,28 @@ export class AuthInterceptor implements HttpInterceptor { const splittedRealm = realms[j].split(', '); const methodName = splittedRealm[0].split(' ')[0].trim(); - console.log('methodName: ', methodName); + // console.log('methodName: ', methodName); - console.log('splittedRealm: ', splittedRealm); + // console.log('splittedRealm: ', splittedRealm); let authMethodModel: AuthMethodModel; if (splittedRealm.length === 1) { authMethodModel = new AuthMethodModel(methodName); authMethodModels.push(authMethodModel); } else if (splittedRealm.length > 1) { - authMethodModel = new AuthMethodModel(methodName); let location = splittedRealm[1]; - location = this.parseLocation(location) - authMethodModel.location = location; - console.log('location: ', location); + location = this.parseLocation(location); + authMethodModel = new AuthMethodModel(methodName, location); + // console.log('location: ', location); authMethodModels.push(authMethodModel); } } + + // make sure the email + password login component gets rendered first + authMethodModels = this.sortAuthMethods(authMethodModels); return authMethodModels; } - private makeAuthStatusObject(authenticated: boolean, accessToken?: string, error?: string, httpHeaders?: HttpHeaders): AuthStatus { + private makeAuthStatusObject(authenticated: boolean, accessToken ?: string, error ?: string, httpHeaders ?: HttpHeaders): AuthStatus { const authStatus = new AuthStatus(); // let authMethods: AuthMethodModel[]; if (httpHeaders) { @@ -135,7 +155,7 @@ export class AuthInterceptor implements HttpInterceptor { const token = authService.getToken(); let newReq; - // console.log('intercept() request: ', req); +// console.log('intercept() request: ', req); if (authService.isTokenExpired()) { authService.setRedirectUrl(this.router.url); @@ -163,7 +183,7 @@ export class AuthInterceptor implements HttpInterceptor { newReq = req; } - // 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) => { diff --git a/src/app/core/auth/models/auth-method.model.ts b/src/app/core/auth/models/auth-method.model.ts index 3b6fe38d3a..f5a506be2a 100644 --- a/src/app/core/auth/models/auth-method.model.ts +++ b/src/app/core/auth/models/auth-method.model.ts @@ -1,12 +1,12 @@ import {AuthMethodType} from '../../../shared/log-in/methods/authMethods-type'; +import { ShibbConstants } from '../../../+login-page/shibbolethTargetPage/const/shibbConstants'; export class AuthMethodModel { authMethodType: AuthMethodType; location?: string; constructor(authMethodName: string, location?: string) { - this.location = location; - switch (authMethodName) { + switch (authMethodName) { case 'ip': { this.authMethodType = AuthMethodType.Ip; break; @@ -17,6 +17,12 @@ export class AuthMethodModel { } case 'shibboleth': { this.authMethodType = AuthMethodType.Shibboleth; + const strings: string[] = location.split('target='); + const target = strings[1]; + + console.log('strings', strings); + + this.location = target + location + '/' + ShibbConstants.SHIBBOLETH_REDIRECT_ROUTE; break; } case 'x509': { diff --git a/src/app/core/auth/selectors.ts b/src/app/core/auth/selectors.ts index d30f3ff27e..4e51bc1fc9 100644 --- a/src/app/core/auth/selectors.ts +++ b/src/app/core/auth/selectors.ts @@ -107,7 +107,6 @@ const _getRegistrationError = (state: AuthState) => state.error; */ const _getRedirectUrl = (state: AuthState) => state.redirectUrl; -// @Art: these two are the ones i added: const _getAuthenticationMethods = (state: AuthState) => state.authMethods; /** diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 83071382ed..0980d48537 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -3,7 +3,7 @@ import { HttpHeaders } from '@angular/common/http'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { Observable, race as observableRace } from 'rxjs'; -import { filter, find, map, mergeMap, take } from 'rxjs/operators'; +import { filter, map, mergeMap, take } from 'rxjs/operators'; import { cloneDeep, remove } from 'lodash'; import { AppState } from '../../app.reducer'; @@ -65,8 +65,7 @@ const uuidsFromHrefSubstringSelector = const getUuidsFromHrefSubstring = (state: IndexState, href: string): string[] => { let result = []; if (isNotEmpty(state)) { - result = Object.values(state) - .filter((value: string) => value.startsWith(href)); + result = Object.keys(state).filter((key) => key.startsWith(href)).map((key) => state[key]); } return result; }; @@ -263,12 +262,13 @@ export class RequestService { */ private clearRequestsOnTheirWayToTheStore(request: GetRequest) { this.getByHref(request.href).pipe( - find((re: RequestEntry) => hasValue(re))) - .subscribe((re: RequestEntry) => { - if (!re.responsePending) { - remove(this.requestsOnTheirWayToTheStore, (item) => item === request.href); - } - }); + filter((re: RequestEntry) => hasValue(re)), + take(1) + ).subscribe((re: RequestEntry) => { + if (!re.responsePending) { + remove(this.requestsOnTheirWayToTheStore, (item) => item === request.href); + } + }); } /** @@ -315,4 +315,15 @@ export class RequestService { return result; } + /** + * Create an observable that emits a new value whenever the availability of the cached request changes. + * The value it emits is a boolean stating if the request exists in cache or not. + * @param href The href of the request to observe + */ + hasByHrefObservable(href: string): Observable { + return this.getByHref(href).pipe( + map((requestEntry: RequestEntry) => this.isValid(requestEntry)) + ); + } + } diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.ts b/src/app/shared/log-in/methods/password/log-in-password.component.ts index e993fc5606..7f98152391 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.ts +++ b/src/app/shared/log-in/methods/password/log-in-password.component.ts @@ -6,7 +6,7 @@ import { select, Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { AuthenticateAction, - ResetAuthenticationMessagesAction, GetJWTafterShibbLoginAction + ResetAuthenticationMessagesAction } from '../../../../core/auth/auth.actions'; import { diff --git a/src/app/shared/log-in/methods/shibboleth/dynamic-shibboleth.component.html b/src/app/shared/log-in/methods/shibboleth/dynamic-shibboleth.component.html deleted file mode 100644 index 9daa39527c..0000000000 --- a/src/app/shared/log-in/methods/shibboleth/dynamic-shibboleth.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - diff --git a/src/app/shared/log-in/methods/shibboleth/dynamic-shibboleth.component.scss b/src/app/shared/log-in/methods/shibboleth/dynamic-shibboleth.component.scss deleted file mode 100644 index 0eda382c0a..0000000000 --- a/src/app/shared/log-in/methods/shibboleth/dynamic-shibboleth.component.scss +++ /dev/null @@ -1,13 +0,0 @@ -.form-login .form-control:focus { - z-index: 2; -} -.form-login input[type="email"] { - margin-bottom: -1px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.form-login input[type="password"] { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - diff --git a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html new file mode 100644 index 0000000000..2bef5486ed --- /dev/null +++ b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html @@ -0,0 +1,9 @@ + + + + diff --git a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.scss b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.scss new file mode 100644 index 0000000000..bb0e91c64e --- /dev/null +++ b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.scss @@ -0,0 +1,3 @@ +.shibb { + color: #FFFFFF; +} diff --git a/src/app/shared/log-in/methods/shibboleth/dynamic-shibboleth.component.ts b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.ts similarity index 55% rename from src/app/shared/log-in/methods/shibboleth/dynamic-shibboleth.component.ts rename to src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.ts index 3ee2709123..fa1f572615 100644 --- a/src/app/shared/log-in/methods/shibboleth/dynamic-shibboleth.component.ts +++ b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.ts @@ -1,4 +1,14 @@ -import { Component, EventEmitter, Inject, Input, OnInit, Output, QueryList, ViewChildren } from '@angular/core'; +import { + Component, + EventEmitter, + Inject, + Input, + OnDestroy, + OnInit, + Output, + QueryList, + ViewChildren +} from '@angular/core'; import { renderAuthMethodFor } from '../authMethods-decorator'; import { AuthMethodType } from '../authMethods-type'; import { AuthMethodModel } from '../../../../core/auth/models/auth-method.model'; @@ -6,19 +16,25 @@ import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { select, Store } from '@ngrx/store'; import { CoreState } from '../../../../core/core.reducers'; import { StartShibbolethAuthenticationAction } from '../../../../core/auth/auth.actions'; -import { Observable } from 'rxjs'; -import { isAuthenticated, isAuthenticationLoading } from '../../../../core/auth/selectors'; +import { Observable, of, Subscription } from 'rxjs'; +import { + getAuthenticationMethods, + isAuthenticated, + isAuthenticationLoading +} from '../../../../core/auth/selectors'; import { HttpClient } from '@angular/common/http'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../../../config'; +import { ShibbConstants } from '../../../../+login-page/shibbolethTargetPage/const/shibbConstants'; +import { tap } from 'rxjs/operators'; @Component({ - selector: 'ds-dynamic-shibboleth', - templateUrl: './dynamic-shibboleth.component.html', - styleUrls: ['./dynamic-shibboleth.component.scss'], + selector: 'ds-log-in-shibboleth', + templateUrl: './log-in-shibboleth.component.html', + styleUrls: ['./log-in-shibboleth.component.scss'], }) @renderAuthMethodFor(AuthMethodType.Shibboleth) -export class DynamicShibbolethComponent implements OnInit { +export class LogInShibbolethComponent implements OnInit { @Input() authMethodModel: AuthMethodModel; @@ -34,38 +50,18 @@ export class DynamicShibbolethComponent implements OnInit { */ public isAuthenticated: Observable; - /** - * The authentication form. - * @type {FormGroup} - */ - public shibbForm: FormGroup; - - private host: string; - - // public shibbButton: FormControl; - /** * @constructor */ constructor(@Inject('authMethodModelProvider') public injectedAuthMethodModel: AuthMethodModel, - @Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig, private formBuilder: FormBuilder, private store: Store) { this.authMethodModel = injectedAuthMethodModel; } ngOnInit(): void { - console.log('conf: ',this.envConfig.rest.host); - this.host = this.envConfig.rest.host; - - // console.log('injectedAuthMethodModel', this.injectedAuthMethodModel); - // set formGroup - this.shibbForm = this.formBuilder.group({ - shibbButton: [''], - }); - - // this.shibbButton = new FormControl(''); + // console.log('Injected authMethodModel', this.injectedAuthMethodModel); // set isAuthenticated this.isAuthenticated = this.store.pipe(select(isAuthenticated)); @@ -78,8 +74,10 @@ export class DynamicShibbolethComponent implements OnInit { submit() { console.log('submit() was called'); this.store.dispatch(new StartShibbolethAuthenticationAction(this.authMethodModel)); - this.host = 'fis.tiss.tuwien.ac.at'; - // https://dspace.hostname/Shibboleth.sso/Login?target=https://dspace.hostname/shibboleth - window.location.href = 'https://' + this.host + '/Shibboleth.sso/Login?target=https://' + this.host + '/shibboleth'; + // e.g. host = 'fis.tiss.tuwien.ac.at'; + // https://host/Shibboleth.sso/Login?target=https://host/shibboleth + // https://fis.tiss.tuwien.ac.at/Shibboleth.sso/Login?target=https://fis.tiss.tuwien.ac.at/shibboleth'; + window.location.href = this.injectedAuthMethodModel.location; } + } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 5b8e54c31b..85abbb6e2d 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -137,7 +137,7 @@ import { RoleDirective } from './roles/role.directive'; import { UserMenuComponent } from './auth-nav-menu/user-menu/user-menu.component'; import { ClaimedTaskActionsReturnToPoolComponent } from './mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component'; import { ItemDetailPreviewFieldComponent } from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component'; -import {DynamicShibbolethComponent} from './log-in/methods/shibboleth/dynamic-shibboleth.component'; +import {LogInShibbolethComponent} from './log-in/methods/shibboleth/log-in-shibboleth.component'; // import {LogInComponent} from './log-in/log-in.component'; import {LogInPasswordComponent} from './log-in/methods/password/log-in-password.component'; import { LoginContainerComponent } from './log-in/container/login-container.component'; @@ -263,7 +263,7 @@ const COMPONENTS = [ ItemTypeSwitcherComponent, BrowseByComponent, // LogInComponent, - DynamicShibbolethComponent, + LogInShibbolethComponent, LogInPasswordComponent, LoginContainerComponent, LogInComponent @@ -311,7 +311,7 @@ const ENTRY_COMPONENTS = [ ItemMetadataListElementComponent, MetadataRepresentationListElementComponent, LogInPasswordComponent, - DynamicShibbolethComponent + LogInShibbolethComponent ]; const SHARED_ITEM_PAGE_COMPONENTS = [