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-row>
<h1>
Federated User Credential Deployment Portal
</h1>
<div class="header-bar">
<span *ngIf="(userService.userSrc() | async) as user; else notLoggedIn">
<button mat-button mat-icon-button (click)="dialog.openAccount()">
<mat-icon>settings</mat-icon>
</button>
<button *ngIf="user.profile_name != undefined" mat-button (click)="profileDialog.open(user)">
{{ user.profile_name }}
</button>
<button *ngIf="user.profile_name == undefined" mat-button (click)="profileDialog.open(user)">
Profile
</button>
<button mat-raised-button color="accent" (click)="userService.logout()">
Logout
</button>
</span>
<ng-template #notLoggedIn>
<span *ngIf="(idps$ | async) as idps">
<form *ngIf="(selectedIdP$ | async) as selectedIdP"
(ngSubmit)="userService.login(selectedIdP)" #loginForm="ngForm">
<mat-form-field>
<mat-select name="idp" required [(ngModel)]="selectedIdP">
<mat-option *ngFor="let idp of idps" [value]="idp">
{{ idp.name }}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button [disabled]="!loginForm.form.valid" color="accent" type="submit">Login</button>
</form>
</span>
</ng-template>
</div>
</mat-toolbar-row>
<mat-toolbar-row style="display: flex; justify-content: space-between; width: 100%;">
<div>
<h1>
Federated User Credential Deployment Portal
</h1>
</div>
<div>
<span *ngIf="(userService.userSrc() | async) as user; else notLoggedIn">
<button mat-button mat-icon-button (click)="dialog.openAccount()">
<mat-icon>settings</mat-icon>
</button>
<button *ngIf="user.profile_name != undefined" mat-button (click)="profileDialog.open(user)">
{{ user.profile_name }}
</button>
<button *ngIf="user.profile_name == undefined" mat-button (click)="profileDialog.open(user)">
Profile
</button>
<button mat-raised-button color="accent" (click)="userService.logout()">
Logout
</button>
</span>
<ng-template #notLoggedIn>
<span *ngIf="(userService.connectIdPInfo() | async) as idpInfo">
<form (ngSubmit)="userService.login(selected)" #loginForm="ngForm">
<mat-form-field>
<mat-select name="idp" required [(ngModel)]="selected" [compareWith]="idpCompare">
<mat-option *ngFor="let idp of idpInfo.idps" [value]="idp">
{{ idp.name }}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button [disabled]="!loginForm.form.valid" color="accent" type="submit">
Login
</button>
</form>
</span>
</ng-template>
</div>
</mat-toolbar-row>
</mat-toolbar>
......@@ -3,7 +3,6 @@ import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { UserService } from '../user.service';
import { IdpService } from '../idp.service';
import { DialogService } from '../dialogues/dialog.service';
import { ProfileDialogService } from '../dialogues/profile-dialog.service';
import { IdP } from '../types/types.module';
......@@ -15,19 +14,24 @@ import { IdP } from '../types/types.module';
})
export class HeaderComponent implements OnInit {
public idps$: Observable<IdP[]>;
public selectedIdP$: Observable<IdP>;
selected: IdP;
constructor(
private idpService: IdpService,
public dialog: DialogService,
public profileDialog: ProfileDialogService,
public userService: UserService,
) {
this.idps$ = this.idpService.subscribeIdps();
this.selectedIdP$ = this.idpService.subscribeSelectedIdp();
}
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 { BehaviorSubject, Observable } from 'rxjs';
import { IdP } from '../types/types.module';
export const prefsKey = "feudalPrefs";
export interface Prefs {
showEmptyVOs?: boolean;
preferredIdP?: IdP;
};
@Injectable({
......@@ -20,16 +23,25 @@ export class PreferencesService {
}
public save(prefs: Prefs): void {
console.log("Saved prefs:", prefs);
localStorage.setItem(prefsKey, JSON.stringify(prefs));
console.log("Saved preferences:", prefs);
}
public load(): void {
this.prefs = JSON.parse(localStorage.getItem(prefsKey));
if (this.prefs != null && this.prefs != undefined) {
this.prefs$.next(this.prefs);
const loaded = JSON.parse(localStorage.getItem(prefsKey));
if (loaded != null) {
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> {
......
......@@ -6,9 +6,11 @@ import { CommonModule } from '@angular/common';
export interface IdP {
id: number;
name: string;
issuer_uri: string;
}
export interface AuthInfo {
export interface IdPInfo {
selectedIdP?: IdP;
idps: IdP[];
default: number;
}
......
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { throwError as observableThrowError, Observable, BehaviorSubject, of, EMPTY } from 'rxjs';
import { Observable, BehaviorSubject, AsyncSubject, throwError as observableThrowError, of, EMPTY } from 'rxjs';
import { map, catchError, combineLatest, tap } from 'rxjs/operators';
import { CookieService } from 'ngx-cookie-service';
......@@ -9,11 +8,10 @@ import { StompConfig, StompRService } from '@stomp/ng2-stompjs';
import { Message } from '@stomp/stompjs';
import { SnackBarService } from './snackbar.service';
import { IdpService } from './idp.service';
import { PreferencesService, Prefs } from './preferences/preferences.service';
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';
export interface Combination {
......@@ -26,7 +24,9 @@ export class UserService {
private initialized = false;
private loggedIn = false;
private observerDebugging = true;
private idpInfo: AsyncSubject<IdPInfo> = new AsyncSubject();
private observerDebugging = false;
// relogin on failed XHR calls
// is turned off when the user is deactivated
......@@ -52,7 +52,6 @@ export class UserService {
private cookieService: CookieService,
private http: HttpClient,
private snackBar: SnackBarService,
private idpService: IdpService,
private stompService: StompRService,
private prefs: PreferencesService,
) {
......@@ -62,6 +61,7 @@ export class UserService {
// PRIVATE API
private connect(): void {
this.fetch();
this.fetchIdPInfo();
this.userSrc().subscribe(
(newUser: User) => {
......@@ -255,18 +255,68 @@ export class UserService {
private tapLogger(name: string): (e: any) => void {
return e => {
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 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 {
if (idp) {
this.idpService.setIdPPreference(idp);
const redirect = (idp: 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 {
......
......@@ -105,6 +105,10 @@ mat-checkbox {
margin-right: 15px;
}
.mat-form-field-infix {
width: 220px !important;
}
.scrolling-dialog .mat-dialog-container {
margin: 70px 0px 6px 0px;
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