Prototype ready

This commit is contained in:
Julius Gruber
2019-09-20 11:41:01 +02:00
parent 52ef2acb08
commit 9844e9eff7
21 changed files with 125 additions and 106 deletions

0
git Normal file
View File

View File

@@ -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",

View File

@@ -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",

View File

@@ -0,0 +1,3 @@
export class ShibbConstants {
public static readonly SHIBBOLETH_REDIRECT_ROUTE = 'shibboleth';
}

View File

@@ -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<ShibbolethComponent>;
let component: ShibbolethTargetPageComponent;
let fixture: ComponentFixture<ShibbolethTargetPageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ShibbolethComponent ]
declarations: [ ShibbolethTargetPageComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ShibbolethComponent);
fixture = TestBed.createComponent(ShibbolethTargetPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@@ -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.

View File

@@ -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 },
])
],

View File

@@ -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 = [

View File

@@ -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<AuthMethodModel>();
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) => {

View File

@@ -1,11 +1,11 @@
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) {
case 'ip': {
this.authMethodType = AuthMethodType.Ip;
@@ -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': {

View File

@@ -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;
/**

View File

@@ -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,8 +262,9 @@ export class RequestService {
*/
private clearRequestsOnTheirWayToTheStore(request: GetRequest) {
this.getByHref(request.href).pipe(
find((re: RequestEntry) => hasValue(re)))
.subscribe((re: RequestEntry) => {
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<boolean> {
return this.getByHref(href).pipe(
map((requestEntry: RequestEntry) => this.isValid(requestEntry))
);
}
}

View File

@@ -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 {

View File

@@ -1,19 +0,0 @@
<form *ngIf="!(loading | async) && !(isAuthenticated | async)" class="form-login px-4 py-3" (ngSubmit)="submit()"
[formGroup]="shibbForm" novalidate>
<button class="btn btn-lg btn-primary btn-block mt-3" type="submit"
[disabled]="!shibbForm.valid"
>{{"login.shibbForm.submit" | translate}}</button>
</form>
<!--
<div *ngIf="!(loading | async) && !(isAuthenticated | async)" class="form-login px-4 py-3">
<button class="btn btn-lg btn-primary btn-block mt-3"
type="submit"
[formControl]="shibbButton"
(click)="submit()"
>{{"login.shibbForm.submit" | translate}}</button>
</div>
-->

View File

@@ -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;
}

View File

@@ -0,0 +1,9 @@
<div class="form-login px-4 py-3 shibb" *ngIf="!(loading | async) && !(isAuthenticated | async)">
<a class="btn btn-lg btn-primary btn-block mt-3"
(click)="submit()"
role="button"
>{{"login.form.ssoLogin" | translate}}</a>
</div>

View File

@@ -0,0 +1,3 @@
.shibb {
color: #FFFFFF;
}

View File

@@ -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<boolean>;
/**
* 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<CoreState>) {
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;
}
}

View File

@@ -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 = [