Dynamic rendering of auth methods via decorator

This commit is contained in:
Julius Gruber
2019-08-29 17:58:07 +02:00
parent 466d01ce09
commit beb0f2d410
15 changed files with 137 additions and 132 deletions

View File

@@ -3,8 +3,7 @@
<div> <div>
<img class="mb-4 login-logo" src="assets/images/dspace-logo.png"> <img class="mb-4 login-logo" src="assets/images/dspace-logo.png">
<h1 class="h3 mb-0 font-weight-normal">{{"login.form.header" | translate}}</h1> <h1 class="h3 mb-0 font-weight-normal">{{"login.form.header" | translate}}</h1>
<!-- <ds-log-in></ds-log-in>--> <ds-login-container></ds-login-container>
<ds-log-in></ds-log-in>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,30 +1,32 @@
import {AuthMethodType} from '../../../shared/log-in/authMethods-type';
export class AuthMethodModel { export class AuthMethodModel {
authMethodName: string; authMethodName: string;
location?: string; location?: string;
authMethodConstant: AuthMethodType authMethodType: AuthMethodType
constructor(authMethodName: string, location?: string) { constructor(authMethodName: string, location?: string) {
this.authMethodName = authMethodName; this.authMethodName = authMethodName;
this.location = location; this.location = location;
switch (authMethodName) { switch (authMethodName) {
case 'ip': { case 'ip': {
this.authMethodConstant = AuthMethodType.Ip; this.authMethodType = AuthMethodType.Ip;
break; break;
} }
case 'ldap': { case 'ldap': {
this.authMethodConstant = AuthMethodType.Ldap; this.authMethodType = AuthMethodType.Ldap;
break; break;
} }
case 'shibboleth': { case 'shibboleth': {
this.authMethodConstant = AuthMethodType.Shibboleth; this.authMethodType = AuthMethodType.Shibboleth;
break; break;
} }
case 'x509': { case 'x509': {
this.authMethodConstant = AuthMethodType.X509; this.authMethodType = AuthMethodType.X509;
break; break;
} }
case 'password': { case 'password': {
this.authMethodConstant = AuthMethodType.Password; this.authMethodType = AuthMethodType.Password;
break; break;
} }
@@ -34,11 +36,3 @@ export class AuthMethodModel {
} }
} }
} }
export enum AuthMethodType {
Password = 'password',
Shibboleth = 'shibboleth',
Ldap = 'ldap',
Ip = 'ip',
X509 = 'x509'
}

View File

@@ -3,8 +3,8 @@
<div ngbDropdown placement="bottom-right" class="d-inline-block" @fadeInOut> <div ngbDropdown placement="bottom-right" class="d-inline-block" @fadeInOut>
<a href="#" id="dropdownLogin" (click)="$event.preventDefault()" ngbDropdownToggle class="px-1">{{ 'nav.login' | translate }}</a> <a href="#" id="dropdownLogin" (click)="$event.preventDefault()" ngbDropdownToggle class="px-1">{{ 'nav.login' | translate }}</a>
<div id="loginDropdownMenu" [ngClass]="{'pl-3 pr-3': (loading | async)}" ngbDropdownMenu aria-labelledby="dropdownLogin"> <div id="loginDropdownMenu" [ngClass]="{'pl-3 pr-3': (loading | async)}" ngbDropdownMenu aria-labelledby="dropdownLogin">
<!-- <ds-log-in></ds-log-in>--> <!-- <ds-log-in></ds-log-in>-->
<ds-log-in></ds-log-in> <ds-login-container></ds-login-container>
</div> </div>
</div> </div>
</li> </li>

View File

@@ -0,0 +1,15 @@
import { AuthMethodType } from './authMethods-type';
const authMethodsMap = new Map();
export function renderAuthMethodFor(authMethodType: AuthMethodType) {
return function decorator(objectElement: any) {
if (!objectElement) {
return;
}
authMethodsMap.set(authMethodType, objectElement);
};
}
export function rendersAuthMethodType(authMethodType: AuthMethodType) {
return authMethodsMap.get(authMethodType);
}

View File

@@ -0,0 +1,7 @@
export enum AuthMethodType {
Password = 'password',
Shibboleth = 'shibboleth',
Ldap = 'ldap',
Ip = 'ip',
X509 = 'x509'
}

View File

@@ -0,0 +1,3 @@
<div *ngFor="let authMethod of authMethodData | async">
<ng-container *ngComponentOutlet="getAuthMethodContent(authMethod.authMethodType); injector: objectInjector;"></ng-container>
</div>

View File

@@ -0,0 +1,21 @@
:host /deep/ .card {
margin-bottom: $submission-sections-margin-bottom;
overflow: unset;
}
.section-focus {
border-radius: $border-radius;
box-shadow: $btn-focus-box-shadow;
}
// TODO to remove the following when upgrading @ng-bootstrap
:host /deep/ .card:first-of-type {
border-bottom: $card-border-width solid $card-border-color !important;
border-bottom-left-radius: $card-border-radius !important;
border-bottom-right-radius: $card-border-radius !important;
}
:host /deep/ .card-header button {
box-shadow: none !important;
width: 100%;
}

View File

@@ -0,0 +1,60 @@
import { Component, Injector, Input, OnInit, ViewChild } from '@angular/core';
import { rendersAuthMethodType } from '../authMethods-decorator';
import { AuthMethodModel } from '../../../core/auth/models/auth-method.model';
import { getAuthenticationMethods } from '../../../core/auth/selectors';
import { Store } from '@ngrx/store';
import { AppState } from '../../../app.reducer';
import { Observable } from 'rxjs';
import { AuthMethodType } from '../authMethods-type';
/**
* This component represents a section that contains the submission license form.
*/
@Component({
selector: 'ds-login-container',
templateUrl: './login-container.component.html',
styleUrls: ['./login-container.component.scss']
})
export class LoginContainerComponent implements OnInit {
/**
* The section data
* @type {SectionDataObject}
*/
@Input() authMethodData: Observable<AuthMethodModel[]>;
/**
* Injector to inject a section component with the @Input parameters
* @type {Injector}
*/
public objectInjector: Injector;
/**
* Initialize instance variables
*
* @param {Injector} injector
*/
constructor(private injector: Injector, private store: Store<AppState>) {
}
/**
* Initialize all instance variables
*/
ngOnInit() {
this.objectInjector = Injector.create({
providers: [
{provide: 'authMethodProvider', useFactory: () => (this.authMethodData), deps: []},
],
parent: this.injector
});
this.authMethodData = this.store.select(getAuthenticationMethods);
}
/**
* Find the correct component based on the authMethod's type
*/
getAuthMethodContent(authMethodType: AuthMethodType): string {
return rendersAuthMethodType(authMethodType)
}
}

View File

@@ -1,12 +0,0 @@
<br>
<div *ngFor="let method of dynamicLoginMethods | async; let i = index" >
<!-- <div class="dropdown-divider"></div>-->
<br *ngIf="i >= 1">
<ng-container *ngComponentOutlet="method.component; injector: objectInjector;">
</ng-container>
</div>

View File

@@ -1,84 +0,0 @@
import {Component, Injector, OnDestroy, OnInit} from '@angular/core';
import {Store} from '@ngrx/store';
import {DynamicShibbolethComponent} from './methods/shibboleth/dynamic-shibboleth.component';
import {getAuthenticationMethods} from '../../core/auth/selectors';
import {map} from 'rxjs/operators';
import {AppState} from '../../app.reducer';
import {Observable} from 'rxjs';
import {AuthMethodType} from '../../core/auth/models/auth-method.model';
import {DynamicLoginMethod} from './log-in.model';
import {LogInPasswordComponent} from './methods/password/log-in-password.component';
@Component({
selector: 'ds-log-in',
templateUrl: './log-in.component.html',
styleUrls: ['./log-in.component.scss'],
})
export class LogInComponent implements OnDestroy, OnInit {
public dynamicLoginMethods: Observable<DynamicLoginMethod[]>;
/**
* Injector to inject a section component with the @Input parameters
* @type {Injector}
*/
public objectInjector: Injector;
private shibbolethUrl: string;
/**
* @constructor
* @param {Store<State>} store
* @param {Injector} injector
*/
constructor(
private store: Store<AppState>,
private injector: Injector
) {
}
/**
* Lifecycle hook that is called after data-bound properties of a directive are initialized.
* @method ngOnInit
*/
public ngOnInit() {
this.objectInjector = Injector.create({
providers: [
{provide: 'shibbolethUrlProvider', useFactory: () => (this.shibbolethUrl), deps: []},
// if other authentication methods need further data to work add a provider here e.g
// {provide: 'otherDataProvider', useFactory: () => (this.otherData), deps: []},
],
parent: this.injector
});
this.dynamicLoginMethods = this.store.select(getAuthenticationMethods).pipe(
map(((authMethods) => authMethods.map((authMethod) => {
switch (authMethod.authMethodConstant) {
case AuthMethodType.Password:
return new DynamicLoginMethod(authMethod.authMethodName, LogInPasswordComponent)
break;
case AuthMethodType.Shibboleth:
this.shibbolethUrl = authMethod.location;
// this.shibbolethUrl = 'https://fis.tiss.tuwien.ac.at/Shibboleth.sso/Login?target=https://fis.tiss.tuwien.ac.at/shibboleth';
return new DynamicLoginMethod(authMethod.authMethodName, DynamicShibbolethComponent, authMethod.location)
break;
default:
break;
}
}
)
)
)
);
}
/**
* Lifecycle hook that is called when a directive, pipe or service is destroyed.
* @method ngOnDestroy
*/
public ngOnDestroy() {
// console.log('ngOnDestroy() in LogInContainerComponent was called');
}
}

View File

@@ -1,11 +0,0 @@
export class DynamicLoginMethod {
label: string;
component: any;
location?: string;
constructor(label, component, location?) {
this.label = label;
this.component = component;
this.location = location;
}
}

View File

@@ -20,6 +20,8 @@ import {CoreState} from '../../../../core/core.reducers';
import {isNotEmpty} from '../../../empty.util'; import {isNotEmpty} from '../../../empty.util';
import {fadeOut} from '../../../animations/fade'; import {fadeOut} from '../../../animations/fade';
import {AuthService} from '../../../../core/auth/auth.service'; import {AuthService} from '../../../../core/auth/auth.service';
import { AuthMethodType } from '../../authMethods-type';
import { renderAuthMethodFor } from '../../authMethods-decorator';
/** /**
* /users/sign-in * /users/sign-in
@@ -31,6 +33,7 @@ import {AuthService} from '../../../../core/auth/auth.service';
styleUrls: ['./log-in-password.component.scss'], styleUrls: ['./log-in-password.component.scss'],
animations: [fadeOut] animations: [fadeOut]
}) })
@renderAuthMethodFor(AuthMethodType.Password)
export class LogInPasswordComponent implements OnDestroy, OnInit { export class LogInPasswordComponent implements OnDestroy, OnInit {
/** /**

View File

@@ -1,5 +1,7 @@
import {Component, Inject, Input, OnInit} from '@angular/core'; import {Component, Inject, Input, OnInit} from '@angular/core';
import { renderAuthMethodFor } from '../../authMethods-decorator';
import { AuthMethodType } from '../../authMethods-type';
import { AuthMethodModel } from '../../../../core/auth/models/auth-method.model';
@Component({ @Component({
selector: 'ds-dynamic-shibboleth', selector: 'ds-dynamic-shibboleth',
@@ -7,11 +9,17 @@ import {Component, Inject, Input, OnInit} from '@angular/core';
styleUrls: ['./dynamic-shibboleth.component.scss'], styleUrls: ['./dynamic-shibboleth.component.scss'],
}) })
export class DynamicShibbolethComponent { @renderAuthMethodFor(AuthMethodType.Shibboleth)
export class DynamicShibbolethComponent implements OnInit {
/** /**
* @constructor * @constructor
*/ */
constructor(@Inject('shibbolethUrlProvider') public injectedShibbolethUrl: string) { constructor(@Inject('authMethodProvider') public injectedObject: AuthMethodModel) {
} }
ngOnInit(): void {
console.log('injectedObject', this.injectedObject)
}
} }

View File

@@ -138,8 +138,9 @@ 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 { 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 { 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 {DynamicShibbolethComponent} from './log-in/methods/shibboleth/dynamic-shibboleth.component';
import {LogInComponent} from './log-in/log-in.component'; // import {LogInComponent} from './log-in/log-in.component';
import {LogInPasswordComponent} from './log-in/methods/password/log-in-password.component'; import {LogInPasswordComponent} from './log-in/methods/password/log-in-password.component';
import { LoginContainerComponent } from './log-in/container/login-container.component';
const MODULES = [ const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here // Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -212,7 +213,7 @@ const COMPONENTS = [
FormComponent, FormComponent,
LangSwitchComponent, LangSwitchComponent,
LoadingComponent, LoadingComponent,
LogInComponent, // LogInComponent,
LogOutComponent, LogOutComponent,
NumberPickerComponent, NumberPickerComponent,
ObjectListComponent, ObjectListComponent,
@@ -260,9 +261,10 @@ const COMPONENTS = [
TypedItemSearchResultListElementComponent, TypedItemSearchResultListElementComponent,
ItemTypeSwitcherComponent, ItemTypeSwitcherComponent,
BrowseByComponent, BrowseByComponent,
LogInComponent, // LogInComponent,
DynamicShibbolethComponent, DynamicShibbolethComponent,
LogInPasswordComponent LogInPasswordComponent,
LoginContainerComponent
]; ];
const ENTRY_COMPONENTS = [ const ENTRY_COMPONENTS = [