Commit b27c7d04 authored by Lukas Burgey's avatar Lukas Burgey

Rework IdP selection

parent 5170363d
.header-bar {
width: 100%;
}
.header-bar>* {
float: right;
}
<mat-toolbar color="primary" class="mat-typography"> <mat-toolbar color="primary" class="mat-typography">
<mat-toolbar-row> <mat-toolbar-row style="display: flex; justify-content: space-between; width: 100%;">
<h1> <div>
Federated User Credential Deployment Portal <h1>
</h1> Federated User Credential Deployment Portal
<div class="header-bar"> </h1>
<span *ngIf="(userService.userSrc() | async) as user; else notLoggedIn"> </div>
<button mat-button mat-icon-button (click)="dialog.openAccount()"> <div>
<mat-icon>settings</mat-icon> <span *ngIf="(userService.userSrc() | async) as user; else notLoggedIn">
</button> <button mat-button mat-icon-button (click)="dialog.openAccount()">
<button *ngIf="user.profile_name != undefined" mat-button (click)="profileDialog.open(user)"> <mat-icon>settings</mat-icon>
{{ user.profile_name }} </button>
</button> <button *ngIf="user.profile_name != undefined" mat-button (click)="profileDialog.open(user)">
<button *ngIf="user.profile_name == undefined" mat-button (click)="profileDialog.open(user)"> {{ user.profile_name }}
Profile </button>
</button> <button *ngIf="user.profile_name == undefined" mat-button (click)="profileDialog.open(user)">
<button mat-raised-button color="accent" (click)="userService.logout()"> Profile
Logout </button>
</button> <button mat-raised-button color="accent" (click)="userService.logout()">
</span> Logout
<ng-template #notLoggedIn> </button>
<span *ngIf="(idps$ | async) as idps"> </span>
<form *ngIf="(selectedIdP$ | async) as selectedIdP" <ng-template #notLoggedIn>
(ngSubmit)="userService.login(selectedIdP)" #loginForm="ngForm"> <span *ngIf="(userService.connectIdPInfo() | async) as idpInfo">
<mat-form-field> <form (ngSubmit)="userService.login(selected)" #loginForm="ngForm">
<mat-select name="idp" required [(ngModel)]="selectedIdP"> <mat-form-field>
<mat-option *ngFor="let idp of idps" [value]="idp"> <mat-select name="idp" required [(ngModel)]="selected" [compareWith]="idpCompare">
{{ idp.name }} <mat-option *ngFor="let idp of idpInfo.idps" [value]="idp">
</mat-option> {{ idp.name }}
</mat-select> </mat-option>
</mat-form-field> </mat-select>
<button mat-raised-button [disabled]="!loginForm.form.valid" color="accent" type="submit">Login</button> </mat-form-field>
</form> <button mat-raised-button [disabled]="!loginForm.form.valid" color="accent" type="submit">
</span> Login
</ng-template> </button>
</div> </form>
</mat-toolbar-row> </span>
</ng-template>
</div>
</mat-toolbar-row>
</mat-toolbar> </mat-toolbar>
...@@ -3,7 +3,6 @@ import { Component, OnInit } from '@angular/core'; ...@@ -3,7 +3,6 @@ import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { UserService } from '../user.service'; import { UserService } from '../user.service';
import { IdpService } from '../idp.service';
import { DialogService } from '../dialogues/dialog.service'; import { DialogService } from '../dialogues/dialog.service';
import { ProfileDialogService } from '../dialogues/profile-dialog.service'; import { ProfileDialogService } from '../dialogues/profile-dialog.service';
import { IdP } from '../types/types.module'; import { IdP } from '../types/types.module';
...@@ -15,19 +14,24 @@ import { IdP } from '../types/types.module'; ...@@ -15,19 +14,24 @@ import { IdP } from '../types/types.module';
}) })
export class HeaderComponent implements OnInit { export class HeaderComponent implements OnInit {
public idps$: Observable<IdP[]>; selected: IdP;
public selectedIdP$: Observable<IdP>;
constructor( constructor(
private idpService: IdpService,
public dialog: DialogService, public dialog: DialogService,
public profileDialog: ProfileDialogService, public profileDialog: ProfileDialogService,
public userService: UserService, public userService: UserService,
) { ) {
this.idps$ = this.idpService.subscribeIdps();
this.selectedIdP$ = this.idpService.subscribeSelectedIdp();
} }
ngOnInit() { ngOnInit() {
this.userService.connectIdPInfo().subscribe(
info => {
this.selected = info.selectedIdP;
}
);
}
idpCompare(a: IdP, b: IdP): boolean {
return a != undefined && b != undefined && a.id == b.id;
} }
} }
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { CookieService } from 'ngx-cookie-service';
import { environment } from '../environments/environment';
import { IdP, AuthInfo } from './types/types.module';
@Injectable({
providedIn: 'root'
})
export class IdpService {
idps: IdP[];
idps$: BehaviorSubject<IdP[]>;
selectedIdp: IdP;
selectedIdp$: BehaviorSubject<IdP>;
constructor(
public http: HttpClient,
public cookieService: CookieService,
) {
this.initializeDataService();
}
private initializeDataService() {
if (!this.idps$) {
this.idps$ = <BehaviorSubject<IdP[]>> new BehaviorSubject(undefined);
}
if (!this.selectedIdp$) {
this.selectedIdp$ = <BehaviorSubject<IdP>> new BehaviorSubject(undefined);
}
this.apiCall();
}
private apiCall(): void {
// initialize the subject with data
this.http.get('/backend/auth/v1/info').subscribe(
(info: AuthInfo) => {
this.idps = info.idps;
this.idps$.next(this.idps);
let preferredIdP: IdP = this.idps.find((idp: IdP) => idp.id === this.getIdPPreference());
let defaultIdP: IdP = this.idps.find((idp: IdP) => idp.id === info.default);
if (preferredIdP) {
this.selectedIdp = preferredIdP;
} else if (defaultIdP) {
this.selectedIdp = defaultIdP;
} else if (this.idps.length > 0) {
this.selectedIdp = this.idps[0];
} else {
console.log("No IdPs available. Unable to login");
}
this.selectedIdp$.next(this.selectedIdp);
}
);
}
private getIdPPreference(): number {
return Number(this.cookieService.get(environment.idpCookieName));
}
public subscribeIdps(): Observable<IdP[]> {
return this.idps$.asObservable();
}
public subscribeSelectedIdp(): Observable<IdP> {
return this.selectedIdp$.asObservable();
}
public setIdPPreference(idp: IdP) {
this.cookieService.set(environment.idpCookieName, String(idp.id));
}
}
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { IdP } from '../types/types.module';
export const prefsKey = "feudalPrefs"; export const prefsKey = "feudalPrefs";
export interface Prefs { export interface Prefs {
showEmptyVOs?: boolean; showEmptyVOs?: boolean;
preferredIdP?: IdP;
}; };
@Injectable({ @Injectable({
...@@ -20,16 +23,25 @@ export class PreferencesService { ...@@ -20,16 +23,25 @@ export class PreferencesService {
} }
public save(prefs: Prefs): void { public save(prefs: Prefs): void {
console.log("Saved prefs:", prefs);
localStorage.setItem(prefsKey, JSON.stringify(prefs)); localStorage.setItem(prefsKey, JSON.stringify(prefs));
console.log("Saved preferences:", prefs);
} }
public load(): void { public load(): void {
this.prefs = JSON.parse(localStorage.getItem(prefsKey)); const loaded = JSON.parse(localStorage.getItem(prefsKey));
if (this.prefs != null && this.prefs != undefined) { if (loaded != null) {
this.prefs$.next(this.prefs); this.prefs = loaded;
this.prefs$.next(loaded);
console.log("Loaded preferences:", this.prefs);
} else {
console.log("No preferences to load");
} }
console.log("Loaded prefs:", this.prefs); }
public setPreferredIdP(idp: IdP): void {
this.prefs.preferredIdP = idp;
this.save(this.prefs);
this.prefs$.next(this.prefs);
} }
public connect(): Observable<Prefs> { public connect(): Observable<Prefs> {
......
...@@ -6,9 +6,11 @@ import { CommonModule } from '@angular/common'; ...@@ -6,9 +6,11 @@ import { CommonModule } from '@angular/common';
export interface IdP { export interface IdP {
id: number; id: number;
name: string; name: string;
issuer_uri: string;
} }
export interface AuthInfo { export interface IdPInfo {
selectedIdP?: IdP;
idps: IdP[]; idps: IdP[];
default: number; default: number;
} }
......
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, BehaviorSubject, AsyncSubject, throwError as observableThrowError, of, EMPTY } from 'rxjs';
import { throwError as observableThrowError, Observable, BehaviorSubject, of, EMPTY } from 'rxjs';
import { map, catchError, combineLatest, tap } from 'rxjs/operators'; import { map, catchError, combineLatest, tap } from 'rxjs/operators';
import { CookieService } from 'ngx-cookie-service'; import { CookieService } from 'ngx-cookie-service';
...@@ -9,11 +8,10 @@ import { StompConfig, StompRService } from '@stomp/ng2-stompjs'; ...@@ -9,11 +8,10 @@ import { StompConfig, StompRService } from '@stomp/ng2-stompjs';
import { Message } from '@stomp/stompjs'; import { Message } from '@stomp/stompjs';
import { SnackBarService } from './snackbar.service'; import { SnackBarService } from './snackbar.service';
import { IdpService } from './idp.service';
import { PreferencesService, Prefs } from './preferences/preferences.service'; import { PreferencesService, Prefs } from './preferences/preferences.service';
import { import {
VO, User, Update, State, Deployment, DeploymentState, SSHKey, NewSSHKey, IdP, Service, Site, JSONObject VO, User, Update, State, Deployment, DeploymentState, SSHKey, NewSSHKey, IdP, Service, Site, JSONObject, IdPInfo
} from './types/types.module'; } from './types/types.module';
export interface Combination { export interface Combination {
...@@ -26,7 +24,9 @@ export class UserService { ...@@ -26,7 +24,9 @@ export class UserService {
private initialized = false; private initialized = false;
private loggedIn = false; private loggedIn = false;
private observerDebugging = true; private idpInfo: AsyncSubject<IdPInfo> = new AsyncSubject();
private observerDebugging = false;
// relogin on failed XHR calls // relogin on failed XHR calls
// is turned off when the user is deactivated // is turned off when the user is deactivated
...@@ -52,7 +52,6 @@ export class UserService { ...@@ -52,7 +52,6 @@ export class UserService {
private cookieService: CookieService, private cookieService: CookieService,
private http: HttpClient, private http: HttpClient,
private snackBar: SnackBarService, private snackBar: SnackBarService,
private idpService: IdpService,
private stompService: StompRService, private stompService: StompRService,
private prefs: PreferencesService, private prefs: PreferencesService,
) { ) {
...@@ -62,6 +61,7 @@ export class UserService { ...@@ -62,6 +61,7 @@ export class UserService {
// PRIVATE API // PRIVATE API
private connect(): void { private connect(): void {
this.fetch(); this.fetch();
this.fetchIdPInfo();
this.userSrc().subscribe( this.userSrc().subscribe(
(newUser: User) => { (newUser: User) => {
...@@ -255,18 +255,68 @@ export class UserService { ...@@ -255,18 +255,68 @@ export class UserService {
private tapLogger(name: string): (e: any) => void { private tapLogger(name: string): (e: any) => void {
return e => { return e => {
if (this.observerDebugging) { if (this.observerDebugging) {
console.log(name, e); if (name === 'userSrc') {
this.userEmissions++;
console.log(name, this.userEmissions, e);
} else {
console.log(name, e);
}
} }
} }
} }
private fetchIdPInfo(): void {
this.http.get<IdPInfo>('/backend/auth/v1/info').subscribe(
idpInfo => {
this.idpInfo.next(idpInfo);
this.idpInfo.complete();
}
);
}
// PUBLIC API // PUBLIC API
public connectIdPInfo(): Observable<IdPInfo> {
return this.idpInfo.asObservable().pipe(
combineLatest(
this.prefs.connect(),
(authInfo, prefs) => {
const defaultIdP: IdP = authInfo.idps.find((idp: IdP) => idp.id === authInfo.default);
if (prefs != undefined && prefs.preferredIdP != undefined) {
authInfo.selectedIdP = prefs.preferredIdP;
} else if (defaultIdP) {
authInfo.selectedIdP = defaultIdP;
} else if (authInfo.idps.length > 0) {
authInfo.selectedIdP = authInfo.idps[0];
} else {
console.log("No IdPs available. Unable to login");
}
return authInfo;
},
),
);
}
public login(idp?: IdP): void { public login(idp?: IdP): void {
if (idp) { const redirect = (idp: IdP) => {
this.idpService.setIdPPreference(idp); window.location.href = '/?idp=' + encodeURIComponent(idp.issuer_uri);
};
if (idp != undefined) {
this.prefs.setPreferredIdP(idp);
redirect(idp);
} else {
this.prefs.connect().subscribe(
prefs => {
if (prefs.preferredIdP != undefined) {
redirect(prefs.preferredIdP);
}
}
)
} }
window.location.href = '/backend/auth/v1/request'; console.log('Unable to login: No IdP');
} }
public logout(): void { public logout(): void {
......
...@@ -105,6 +105,10 @@ mat-checkbox { ...@@ -105,6 +105,10 @@ mat-checkbox {
margin-right: 15px; margin-right: 15px;
} }
.mat-form-field-infix {
width: 220px !important;
}
.scrolling-dialog .mat-dialog-container { .scrolling-dialog .mat-dialog-container {
margin: 70px 0px 6px 0px; margin: 70px 0px 6px 0px;
height: unset; height: unset;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment