fix: add prompt on oidc rp, fix idp and login policy in console (#769)

* fix: add prompt on oidc rp

* fix: add prompt on oidc rp

* fix: translation

* fix: translation

* fix: not existing login policy

* fix: login policy

* fix: identity provider detail

* fix: idp update

* fix: idps in login policy

* fix: lint

* fix: scss

* fix: external idps on auth user detail

* fix: idp create mapping fields

* fix: remove idp provider

* fix: angular lint

* fix: login policy view

* fix: translations
This commit is contained in:
Fabi
2020-09-23 16:52:19 +02:00
committed by GitHub
parent 9887e897ee
commit 0bd27bc8e4
47 changed files with 890 additions and 648 deletions

View File

@@ -47,6 +47,24 @@
</mat-chip-list>
</mat-form-field>
</div>
<div class="content">
<mat-form-field class="formfield" appearance="outline">
<mat-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</mat-label>
<mat-select formControlName="idpDisplayNameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINTFIELD.'+field | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="formfield" appearance="outline">
<mat-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</mat-label>
<mat-select formControlName="usernameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINTFIELD.'+field | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</ng-container>
<button color="primary" mat-raised-button class="continue-button" [disabled]="formGroup.invalid" type="submit">

View File

@@ -6,12 +6,19 @@ import { MatChipInputEvent } from '@angular/material/chips';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { OidcIdpConfigCreate } from 'src/app/proto/generated/admin_pb';
import {
OidcIdpConfigCreate as AdminOidcIdpConfigCreate,
OIDCMappingField as authMappingFields,
} from 'src/app/proto/generated/admin_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import {
OidcIdpConfigCreate as MgmtOidcIdpConfigCreate,
OIDCMappingField as mgmtMappingFields,
} from '../../proto/generated/management_pb';
@Component({
selector: 'app-idp-create',
@@ -22,6 +29,7 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
private service!: ManagementService | AdminService;
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
public mappingFields: mgmtMappingFields[] | authMappingFields[] = [];
private subscription?: Subscription;
public projectId: string = '';
@@ -43,7 +51,9 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
clientId: new FormControl('', [Validators.required]),
clientSecret: new FormControl('', [Validators.required]),
issuer: new FormControl('', [Validators.required]),
scopesList: new FormControl([], []),
scopesList: new FormControl(['openid', 'profile', 'email'], []),
idpDisplayNameMapping: new FormControl(0),
usernameMapping: new FormControl(0),
});
this.route.data.pipe(take(1)).subscribe(data => {
@@ -51,9 +61,15 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
this.mappingFields = [
mgmtMappingFields.OIDCMAPPINGFIELD_PREFERRED_USERNAME,
mgmtMappingFields.OIDCMAPPINGFIELD_EMAIL];
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
this.mappingFields = [
authMappingFields.OIDCMAPPINGFIELD_PREFERRED_USERNAME,
authMappingFields.OIDCMAPPINGFIELD_EMAIL];
break;
}
});
@@ -72,7 +88,16 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
}
public addIdp(): void {
const req: OidcIdpConfigCreate = new OidcIdpConfigCreate();
let req: AdminOidcIdpConfigCreate | MgmtOidcIdpConfigCreate;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
req = new MgmtOidcIdpConfigCreate();
break;
case PolicyComponentServiceType.ADMIN:
req = new AdminOidcIdpConfigCreate();
break;
}
req.setName(this.name?.value);
req.setClientId(this.clientId?.value);
@@ -80,6 +105,8 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
req.setIssuer(this.issuer?.value);
req.setLogoSrc(this.logoSrc?.value);
req.setScopesList(this.scopesList?.value);
req.setIdpDisplayNameMapping(this.idpDisplayNameMapping?.value);
req.setUsernameMapping(this.usernameMapping?.value);
this.service.CreateOidcIdp(req).then((idp) => {
this.router.navigate(['idp', idp.getId()]);
@@ -135,8 +162,16 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
public get issuer(): AbstractControl | null {
return this.formGroup.get('issuer');
}
public get scopesList(): AbstractControl | null {
return this.formGroup.get('scopesList');
return this.formGroup.get('scopesList');
}
public get idpDisplayNameMapping(): AbstractControl | null {
return this.formGroup.get('idpDisplayNameMapping');
}
public get usernameMapping(): AbstractControl | null {
return this.formGroup.get('usernameMapping');
}
}

View File

@@ -11,6 +11,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { IdpCreateRoutingModule } from './idp-create-routing.module';
import { IdpCreateComponent } from './idp-create.component';
import {MatSelectModule} from '@angular/material/select';
@NgModule({
declarations: [IdpCreateComponent],
@@ -22,6 +23,7 @@ import { IdpCreateComponent } from './idp-create.component';
MatInputModule,
MatFormFieldModule,
MatButtonModule,
MatSelectModule,
MatIconModule,
MatChipsModule,
MatTooltipModule,

View File

@@ -1,18 +1,8 @@
<div class="container">
<div class="abort-container">
<button (click)="close()" mat-icon-button>
<mat-icon>close</mat-icon>
</button>
<span class="abort">{{ 'IDP.CREATE.TITLE' | translate }}</span><span class="abort-2">Step
{{ currentCreateStep }} of
{{ createSteps }}</span>
</div>
<h1>{{'IDP.CREATE.TITLE' | translate}}</h1>
<p>{{'IDP.CREATE.DESCRIPTION' | translate}}</p>
<app-detail-layout [backRouterLink]="backroutes" [title]="'IDP.DETAIL.TITLE' | translate"
[description]="'IDP.DETAIL.DESCRIPTION' | translate">
<div class="container">
<form (ngSubmit)="updateIdp()">
<ng-container [formGroup]="formGroup">
<ng-container [formGroup]="idpForm">
<div class="content">
<mat-form-field appearance="outline" class="formfield">
<mat-label>{{ 'IDP.ID' | translate }}</mat-label>
@@ -22,47 +12,80 @@
<mat-label>{{ 'IDP.NAME' | translate }}</mat-label>
<input matInput formControlName="name" />
</mat-form-field>
<mat-form-field appearance="outline" class="formfield">
<!--<mat-form-field appearance="outline" class="formfield">
<mat-label>{{ 'IDP.LOGOSRC' | translate }}</mat-label>
<input matInput formControlName="logoSrc" />
</mat-form-field>
<mat-form-field appearance="outline" class="formfield">
<mat-label>{{ 'IDP.ISSUER' | translate }}</mat-label>
<input matInput formControlName="issuer" />
</mat-form-field>
</div>
<div class="content">
<mat-checkbox class="desc" [(ngModel)]="showIdSecretSection" [ngModelOptions]="{standalone: true}">
Update Client Id / Client Secret
</mat-checkbox>
<ng-container *ngIf="showIdSecretSection">
<mat-form-field appearance="outline" class="formfield">
<mat-label>{{ 'IDP.CLIENTID' | translate }}</mat-label>
<input matInput formControlName="clientId" />
</mat-form-field>
<mat-form-field appearance="outline" class="formfield">
<mat-label>{{ 'IDP.CLIENTSECRET' | translate }}</mat-label>
<input matInput formControlName="clientSecret" />
</mat-form-field>
</ng-container>
</div>
<div class="content">
<mat-form-field appearance="outline" class="formfield">
<mat-label>{{ 'IDP.SCOPESLIST' | translate }}</mat-label>
<mat-chip-list #chipScopesList aria-label="scope selection" *ngIf="scopesList">
<mat-chip class="chip" *ngFor="let scope of scopesList.value" selectable="false" removable
(removed)="removeScope(scope)">
{{scope}} <mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input [matChipInputFor]="chipScopesList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true" (matChipInputTokenEnd)="addScope($event)">
</mat-chip-list>
</mat-form-field>
</mat-form-field>-->
</div>
</ng-container>
<button color="primary" mat-raised-button class="continue-button" [disabled]="formGroup.invalid" type="submit">
<button color="primary" mat-raised-button class="continue-button" [disabled]="idpForm.invalid" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</form>
</div>
<h2 *ngIf="oidcConfigForm">{{'IDP.DETAIL.OIDC.TITLE' | translate}}</h2>
<form (ngSubmit)="updateOidcConfig()" *ngIf="oidcConfigForm">
<ng-container [formGroup]="oidcConfigForm">
<div class="content">
<mat-form-field appearance="outline" class="formfield">
<mat-label>{{ 'IDP.ISSUER' | translate }}</mat-label>
<input matInput formControlName="issuer" />
</mat-form-field>
</div>
<div class="content">
<mat-form-field appearance="outline" class="formfield">
<mat-label>{{ 'IDP.CLIENTID' | translate }}</mat-label>
<input matInput formControlName="clientId" />
</mat-form-field>
</div>
<div class="content">
<mat-checkbox class="desc" [(ngModel)]="showIdSecretSection" [ngModelOptions]="{standalone: true}">
Update Client Secret
</mat-checkbox>
<mat-form-field appearance="outline" class="formfield" *ngIf="showIdSecretSection">
<mat-label>{{ 'IDP.CLIENTSECRET' | translate }}</mat-label>
<input matInput formControlName="clientSecret" />
</mat-form-field>
</div>
<div class="content">
<mat-form-field appearance="outline" class="formfield">
<mat-label>{{ 'IDP.SCOPESLIST' | translate }}</mat-label>
<mat-chip-list #chipScopesList aria-label="scope selection" >
<mat-chip class="chip" *ngFor="let scope of scopesList?.value" selectable="false" removable
(removed)="removeScope(scope)">
{{scope}} <mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input [matChipInputFor]="chipScopesList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true" (matChipInputTokenEnd)="addScope($event)">
</mat-chip-list>
</mat-form-field>
</div>
<div class="content">
<mat-form-field class="formfield" appearance="outline">
<mat-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</mat-label>
<mat-select formControlName="idpDisplayNameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINTFIELD.'+field | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="formfield" appearance="outline">
<mat-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</mat-label>
<mat-select formControlName="usernameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINTFIELD.'+field | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</ng-container>
<button color="primary" mat-raised-button class="continue-button" [disabled]="oidcConfigForm.invalid" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</form>
</div>
</app-detail-layout>

View File

@@ -1,32 +1,10 @@
.container {
padding: 4rem 4rem 2rem 4rem;
width: 100%;
@media only screen and (max-width: 450px) {
padding: 4rem 1rem 2rem 1rem;
}
.abort-container {
display: flex;
align-items: center;
margin-bottom: 2rem;
.abort {
font-size: 1.2rem;
margin-left: 2rem;
}
.abort-2 {
font-size: 1.2rem;
margin-left: 2rem;
white-space: nowrap;
}
}
.add-line-btn {
margin-bottom: 1rem;
border-radius: .5rem;
}
}
.content {
@@ -52,7 +30,7 @@
}
.continue-button {
margin-top: 3rem;
margin-bottom: 4rem;
display: block;
padding: .5rem 4rem;
border-radius: .5rem;

View File

@@ -1,13 +1,21 @@
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { Location } from '@angular/common';
import { Component, Injector, OnDestroy, OnInit, Type } from '@angular/core';
import {Component, Injector, Input, OnDestroy, OnInit, Type} from '@angular/core';
import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
import { MatChipInputEvent } from '@angular/material/chips';
import { ActivatedRoute, Params } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap, take } from 'rxjs/operators';
import { OidcIdpConfigUpdate as AdminOidcIdpConfigUpdate } from 'src/app/proto/generated/admin_pb';
import { OidcIdpConfigUpdate as MgmtOidcIdpConfigUpdate } from 'src/app/proto/generated/management_pb';
import {
OIDCMappingField as authMappingFields,
OidcIdpConfigUpdate as AdminOidcIdpConfigUpdate,
IdpUpdate as AdminIdpConfigUpdate,
} from 'src/app/proto/generated/admin_pb';
import {
OIDCMappingField as mgmtMappingFields,
OidcIdpConfigUpdate as MgmtOidcIdpConfigUpdate,
IdpUpdate as MgmtIdpConfigUpdate,
} from 'src/app/proto/generated/management_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -20,6 +28,7 @@ import { PolicyComponentServiceType } from '../policies/policy-component-types.e
styleUrls: ['./idp.component.scss'],
})
export class IdpComponent implements OnInit, OnDestroy {
public mappingFields: mgmtMappingFields[] | authMappingFields[] = [];
public showIdSecretSection: boolean = false;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
private service!: ManagementService | AdminService;
@@ -28,9 +37,8 @@ export class IdpComponent implements OnInit, OnDestroy {
private subscription?: Subscription;
public projectId: string = '';
public formGroup!: FormGroup;
public createSteps: number = 1;
public currentCreateStep: number = 1;
public idpForm!: FormGroup;
public oidcConfigForm!: FormGroup;
constructor(
// private router: Router,
@@ -39,14 +47,19 @@ export class IdpComponent implements OnInit, OnDestroy {
private route: ActivatedRoute,
private _location: Location,
) {
this.formGroup = new FormGroup({
this.idpForm = new FormGroup({
id: new FormControl({ disabled: true, value: '' }, [Validators.required]),
name: new FormControl({ disabled: true, value: '' }, [Validators.required]),
name: new FormControl('', [Validators.required]),
logoSrc: new FormControl({ disabled: true, value: '' }, [Validators.required]),
clientId: new FormControl('', [Validators.required]),
clientSecret: new FormControl('', [Validators.required]),
issuer: new FormControl('', [Validators.required]),
scopesList: new FormControl([], []),
});
this.oidcConfigForm = new FormGroup({
clientId: new FormControl('', [Validators.required]),
clientSecret: new FormControl(''),
issuer: new FormControl('', [Validators.required]),
scopesList: new FormControl([], []),
idpDisplayNameMapping: new FormControl(0),
usernameMapping: new FormControl(0),
});
this.route.data.pipe(switchMap(data => {
@@ -55,9 +68,15 @@ export class IdpComponent implements OnInit, OnDestroy {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
this.mappingFields = [
mgmtMappingFields.OIDCMAPPINGFIELD_PREFERRED_USERNAME,
mgmtMappingFields.OIDCMAPPINGFIELD_EMAIL];
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
this.mappingFields = [
authMappingFields.OIDCMAPPINGFIELD_PREFERRED_USERNAME,
authMappingFields.OIDCMAPPINGFIELD_EMAIL];
break;
}
@@ -66,7 +85,11 @@ export class IdpComponent implements OnInit, OnDestroy {
const { id } = params;
if (id) {
this.service.IdpByID(id).then(idp => {
this.formGroup.patchValue(idp.toObject());
const idpObject = idp.toObject();
this.idpForm.patchValue(idpObject);
if (idpObject.oidcConfig) {
this.oidcConfigForm.patchValue(idpObject.oidcConfig);
}
});
}
});
@@ -85,29 +108,57 @@ export class IdpComponent implements OnInit, OnDestroy {
}
public updateIdp(): void {
let req: AdminOidcIdpConfigUpdate | MgmtOidcIdpConfigUpdate;
let req: AdminIdpConfigUpdate | MgmtIdpConfigUpdate;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
req = new MgmtOidcIdpConfigUpdate();
req = new MgmtIdpConfigUpdate();
break;
case PolicyComponentServiceType.ADMIN:
req = new AdminOidcIdpConfigUpdate();
req = new AdminIdpConfigUpdate();
break;
}
req.setClientId(this.clientId?.value);
req.setClientSecret(this.clientSecret?.value);
req.setIssuer(this.issuer?.value);
req.setScopesList(this.scopesList?.value);
req.setId(this.id?.value);
req.setName(this.name?.value);
req.setLogoSrc(this.logoSrc?.value);
this.service.UpdateOidcIdpConfig(req).then((idp) => {
this.service.UpdateIdp(req).then((idp) => {
this.toast.showInfo('IDP.TOAST.SAVED', true);
// this.router.navigate(['idp', ]);
}).catch(error => {
this.toast.showError(error);
});
}
public updateOidcConfig(): void {
let req: AdminOidcIdpConfigUpdate | MgmtOidcIdpConfigUpdate;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
req = new MgmtOidcIdpConfigUpdate();
break;
case PolicyComponentServiceType.ADMIN:
req = new AdminOidcIdpConfigUpdate();
break;
}
req.setIdpId(this.id?.value);
req.setClientId(this.clientId?.value);
req.setClientSecret(this.clientSecret?.value);
req.setIssuer(this.issuer?.value);
req.setScopesList(this.scopesList?.value);
req.setUsernameMapping(this.usernameMapping?.value);
req.setIdpDisplayNameMapping(this.idpDisplayNameMapping?.value);
this.service.UpdateOidcIdpConfig(req).then((oidcConfig) => {
this.toast.showInfo('IDP.TOAST.SAVED', true);
// this.router.navigate(['idp', ]);
}).catch(error => {
this.toast.showError(error);
});
}
public close(): void {
this._location.back();
}
@@ -128,39 +179,58 @@ export class IdpComponent implements OnInit, OnDestroy {
public removeScope(uri: string): void {
if (this.scopesList?.value) {
const index = this.scopesList.value.indexOf(uri);
const index = this.scopesList?.value.indexOf(uri);
if (index !== undefined && index >= 0) {
this.scopesList.value.splice(index, 1);
this.scopesList?.value.splice(index, 1);
}
}
}
public get backroutes(): string[] {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return ['/org', 'policy', 'login'];
case PolicyComponentServiceType.ADMIN:
return ['/iam', 'policy', 'login'];
break;
}
return [];
}
public get id(): AbstractControl | null {
return this.formGroup.get('id');
return this.idpForm.get('id');
}
public get name(): AbstractControl | null {
return this.formGroup.get('name');
}
public get clientId(): AbstractControl | null {
return this.formGroup.get('clientId');
}
public get clientSecret(): AbstractControl | null {
return this.formGroup.get('clientSecret');
return this.idpForm.get('name');
}
public get logoSrc(): AbstractControl | null {
return this.formGroup.get('logoSrc');
return this.idpForm.get('logoSrc');
}
public get clientId(): AbstractControl | null {
return this.oidcConfigForm.get('clientId');
}
public get clientSecret(): AbstractControl | null {
return this.oidcConfigForm.get('clientSecret');
}
public get issuer(): AbstractControl | null {
return this.formGroup.get('issuer');
return this.oidcConfigForm.get('issuer');
}
public get scopesList(): AbstractControl | null {
return this.formGroup.get('scopesList');
return this.oidcConfigForm.get('scopesList');
}
public get idpDisplayNameMapping(): AbstractControl | null {
return this.oidcConfigForm.get('idpDisplayNameMapping');
}
public get usernameMapping(): AbstractControl | null {
return this.oidcConfigForm.get('usernameMapping');
}
}

View File

@@ -12,6 +12,8 @@ import { TranslateModule } from '@ngx-translate/core';
import { IdpRoutingModule } from './idp-routing.module';
import { IdpComponent } from './idp.component';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { MatSelectModule } from '@angular/material/select';
@NgModule({
declarations: [IdpComponent],
@@ -25,9 +27,11 @@ import { IdpComponent } from './idp.component';
MatButtonModule,
MatIconModule,
MatTooltipModule,
MatSelectModule,
TranslateModule,
MatCheckboxModule,
MatChipsModule,
DetailLayoutModule,
],
})
export class IdpModule { }

View File

@@ -1,4 +1,4 @@
<app-detail-layout [backRouterLink]="[ '/org']" [title]="'ORG.POLICY.LOGIN_POLICY.TITLECREATE' | translate"
<app-detail-layout [backRouterLink]="backroutes" [title]="'ORG.POLICY.LOGIN_POLICY.TITLECREATE' | translate"
[description]="'ORG.POLICY.LOGIN_POLICY.DESCRIPTIONCREATE' | translate">
<ng-container *ngIf="(['policy.delete'] | hasRole | async) && serviceType == PolicyComponentServiceType.MGMT">
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
@@ -35,9 +35,9 @@
<div class="idps">
<div class="idp" *ngFor="let idp of idps">
<mat-icon (click)="removeIdp(idp)" class="rm">remove_circle</mat-icon>
<p>{{idp.name}}</p>
<span>{{idp.type}}</span>
<span>{{idp.configId}}</span>
<span>{{idp.name}}</span>
<span class="meta">{{ 'IDP.TYPE' | translate }}: {{ 'IDP.TYPES.'+idp.type | translate }}</span>
<span class="meta">{{ 'IDP.ID' | translate }}: {{idp.idpConfigId}}</span>
</div>
<div class="new-idp" (click)="openDialog()">
<mat-icon>add</mat-icon>
@@ -48,4 +48,13 @@
<button (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div>
</app-detail-layout>
<ng-template appHasRole [appHasRole]="['org.idp.read']">
<app-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}">
<app-idp-table [service]="service" [serviceType]="serviceType"
[disabled]="(['iam.idp.write'] | hasRole | async) == false">
</app-idp-table>
</app-card>
</ng-template>
</app-detail-layout>

View File

@@ -53,17 +53,24 @@ button {
.idp,
.new-idp {
display: flex;
display: grid;
align-items: center;
justify-content: center;
margin: .5rem;
height: 50px;
width: 50px;
padding: 10px;
border: 1px solid #8795a1;
border-radius: .5rem;
cursor: pointer;
position: relative;
span {
padding: 2px;
}
.meta {
font-size: 12px;
}
.rm {
position: absolute;
top: 0;

View File

@@ -11,11 +11,11 @@ import {
IdpView as AdminIdpView,
} from 'src/app/proto/generated/admin_pb';
import {
IdpProviderType,
IdpProviderView as MgmtIdpProviderView,
IdpView as MgmtIdpView,
LoginPolicy,
LoginPolicyView,
IdpProviderType,
IdpProviderView as MgmtIdpProviderView,
IdpView as MgmtIdpView,
LoginPolicy,
LoginPolicyView, OrgDomainView,
} from 'src/app/proto/generated/management_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
@@ -30,10 +30,10 @@ import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component
styleUrls: ['./login-policy.component.scss'],
})
export class LoginPolicyComponent implements OnDestroy {
public loginData!: LoginPolicy.AsObject | DefaultLoginPolicy.AsObject;
public loginData!: LoginPolicyView.AsObject | DefaultLoginPolicyView.AsObject;
private sub: Subscription = new Subscription();
private service!: ManagementService | AdminService;
public service!: ManagementService | AdminService;
PolicyComponentServiceType: any = PolicyComponentServiceType;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public idps: MgmtIdpProviderView.AsObject[] | AdminIdpProviderView.AsObject[] = [];
@@ -166,14 +166,26 @@ export class LoginPolicyComponent implements OnDestroy {
}
}
public removeIdp(idp: AdminIdpView.AsObject | MgmtIdpView.AsObject): void {
public removeIdp(idp: AdminIdpProviderView.AsObject | MgmtIdpProviderView.AsObject): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
(this.service as ManagementService).RemoveIdpProviderFromLoginPolicy(idp.id);
(this.service as ManagementService).RemoveIdpProviderFromLoginPolicy(idp.idpConfigId);
break;
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService).RemoveIdpProviderFromDefaultLoginPolicy(idp.id);
(this.service as AdminService).RemoveIdpProviderFromDefaultLoginPolicy(idp.idpConfigId);
break;
}
}
public get backroutes(): string[] {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return ['/org'];
case PolicyComponentServiceType.ADMIN:
return ['/iam'];
break;
}
return [];
}
}

View File

@@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { CardModule } from 'src/app/modules/card/card.module';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
@@ -14,6 +15,8 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
import { AddIdpDialogModule } from './add-idp-dialog/add-idp-dialog.module';
import { LoginPolicyRoutingModule } from './login-policy-routing.module';
import { LoginPolicyComponent } from './login-policy.component';
import { IdpTableModule } from 'src/app/modules/idp-table/idp-table.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
@NgModule({
declarations: [LoginPolicyComponent],
@@ -21,16 +24,19 @@ import { LoginPolicyComponent } from './login-policy.component';
LoginPolicyRoutingModule,
CommonModule,
FormsModule,
CardModule,
MatInputModule,
MatFormFieldModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
HasRolePipeModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
AddIdpDialogModule,
IdpTableModule,
],
})
export class LoginPolicyModule { }

View File

@@ -5,14 +5,6 @@
<app-iam-policy-grid></app-iam-policy-grid>
<ng-template appHasRole [appHasRole]="['iam.idp.read']">
<app-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}">
<app-idp-table [service]="adminService" [serviceType]="PolicyComponentServiceType.ADMIN"
[disabled]="(['org.idp.write'] | hasRole | async) == false">
</app-idp-table>
</app-card>
</ng-template>
<app-card title="{{ 'IAM.VIEWS.TITLE' | translate }}" description="{{ 'IAM.VIEWS.DESCRIPTION' | translate }}">
<app-iam-views></app-iam-views>
</app-card>
@@ -30,4 +22,4 @@
(showDetailClicked)="showDetail()" (refreshClicked)="loadMembers()" [disabled]="false">
</app-contributors>
</div>
</app-meta-layout>
</app-meta-layout>

View File

@@ -26,7 +26,6 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe.module';
import { IdpTableModule } from '../../modules/idp-table/idp-table.module';
import { FailedEventsComponent } from './failed-events/failed-events.component';
import { IamPolicyGridComponent } from './iam-policy-grid/iam-policy-grid.component';
import { IamRoutingModule } from './iam-routing.module';
@@ -38,7 +37,6 @@ import { IamComponent } from './iam.component';
imports: [
CommonModule,
IamRoutingModule,
IdpTableModule,
ChangesModule,
CardModule,
MatAutocompleteModule,

View File

@@ -27,14 +27,6 @@
</app-card>
</ng-container>
<ng-template appHasRole [appHasRole]="['org.idp.read']">
<app-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}">
<app-idp-table [service]="mgmtService" [serviceType]="PolicyComponentServiceType.MGMT"
[disabled]="(['iam.idp.write'] | hasRole | async) == false">
</app-idp-table>
</app-card>
</ng-template>
<ng-template appHasRole [appHasRole]="['policy.read']">
<app-policy-grid></app-policy-grid>
</ng-template>
@@ -66,4 +58,4 @@
</mat-tab>
</mat-tab-group>
</div>
</app-meta-layout>
</app-meta-layout>

View File

@@ -16,7 +16,6 @@ import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { MemberCreateDialogModule } from 'src/app/modules/add-member-dialog/member-create-dialog.module';
import { CardModule } from 'src/app/modules/card/card.module';
import { ContributorsModule } from 'src/app/modules/contributors/contributors.module';
import { IdpTableModule } from 'src/app/modules/idp-table/idp-table.module';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
import { SharedModule } from 'src/app/modules/shared/shared.module';
import { WarnDialogModule } from 'src/app/modules/warn-dialog/warn-dialog.module';
@@ -41,7 +40,6 @@ import { PolicyGridComponent } from './policy-grid/policy-grid.component';
MatFormFieldModule,
MatInputModule,
MatButtonModule,
IdpTableModule,
MatDialogModule,
CardModule,
MatIconModule,

View File

@@ -32,6 +32,11 @@
</app-card>
</div>
<app-card *ngIf="user && user.human && user.id" title="{{ 'USER.EXTERNALIDP.TITLE' | translate }}"
description="{{ 'USER.EXTERNALIDP.DESC' | translate }}">
<app-external-idps [userId]="user.id" [service]="userService"></app-external-idps>
</app-card>
<app-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
<div class="method-col">
@@ -148,4 +153,4 @@
<app-changes class="changes" [changeType]="ChangeType.MYUSER" [id]="user.id"></app-changes>
</div>
</app-meta-layout>
</app-meta-layout>

View File

@@ -35,7 +35,7 @@ export class AuthUserDetailComponent implements OnDestroy {
constructor(
public translate: TranslateService,
private toast: ToastService,
private userService: GrpcAuthService,
public userService: GrpcAuthService,
private dialog: MatDialog,
) {
this.loading = true;

View File

@@ -3,12 +3,17 @@ import {MatPaginator, PageEvent} from '@angular/material/paginator';
import {MatTableDataSource} from '@angular/material/table';
import {
ExternalIDPSearchResponse,
ExternalIDPView,
ExternalIDPView as MgmtExternalIDPView,
IdpView as MgmtIdpView,
} from '../../../../proto/generated/management_pb';
import {
ExternalIDPView as AuthExternalIDPView,
} from '../../../../proto/generated/auth_pb';
import {BehaviorSubject, Observable} from 'rxjs';
import {ManagementService} from '../../../../services/mgmt.service';
import {ToastService} from '../../../../services/toast.service';
import {SelectionModel} from '@angular/cdk/collections';
import {GrpcAuthService} from '../../../../services/grpc-auth.service';
@Component({
selector: 'app-external-idps',
@@ -16,17 +21,19 @@ import {SelectionModel} from '@angular/cdk/collections';
styleUrls: ['./external-idps.component.scss'],
})
export class ExternalIdpsComponent implements OnInit {
@Input() service!: GrpcAuthService | ManagementService;
@Input() userId!: string;
@ViewChild(MatPaginator) public paginator!: MatPaginator;
public externalIdpResult!: ExternalIDPSearchResponse.AsObject;
public dataSource: MatTableDataSource<ExternalIDPView.AsObject> = new MatTableDataSource<ExternalIDPView.AsObject>();
public selection: SelectionModel<ExternalIDPView.AsObject> = new SelectionModel<ExternalIDPView.AsObject>(true, []);
public dataSource: MatTableDataSource<MgmtExternalIDPView.AsObject | AuthExternalIDPView.AsObject>
= new MatTableDataSource<MgmtExternalIDPView.AsObject | AuthExternalIDPView.AsObject>();
public selection: SelectionModel<MgmtExternalIDPView.AsObject | AuthExternalIDPView.AsObject>
= new SelectionModel<MgmtExternalIDPView.AsObject | AuthExternalIDPView.AsObject>(true, []);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@Input() public displayedColumns: string[] = [ 'idpConfigId', 'idpName', 'externalUserId', 'externalUserDisplayName'];
constructor(private mgmtService: ManagementService,
private toast: ToastService) { }
constructor(private toast: ToastService) { }
ngOnInit(): void {
this.getData(10, 0);
@@ -51,7 +58,7 @@ export class ExternalIdpsComponent implements OnInit {
private async getData(limit: number, offset: number): Promise<void> {
this.loadingSubject.next(true);
this.mgmtService.SearchExternalIdps(this.userId, limit, offset).then(resp => {
this.service.SearchExternalIdps(this.userId, limit, offset).then(resp => {
this.externalIdpResult = resp.toObject();
this.dataSource.data = this.externalIdpResult.resultList;
console.log(this.externalIdpResult.resultList);

View File

@@ -43,9 +43,9 @@
</app-detail-form>
</app-card>
<app-card *ngIf="user.human && user.id" title="{{ 'USER.EXTERNALIDP.TITLE' | translate }}"
<app-card *ngIf="user && user.human && user.id" title="{{ 'USER.EXTERNALIDP.TITLE' | translate }}"
description="{{ 'USER.EXTERNALIDP.DESC' | translate }}">
<app-external-idps [userId]="user.id"></app-external-idps>
<app-external-idps [userId]="user.id" [service]="mgmtService" ></app-external-idps>
</app-card>
<app-card *ngIf="user.machine" title="{{ 'USER.MACHINE.TITLE' | translate }}">

View File

@@ -1,22 +1,22 @@
import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import {Location} from '@angular/common';
import {Component, OnDestroy, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {TranslateService} from '@ngx-translate/core';
import {Subscription} from 'rxjs';
import {ChangeType} from 'src/app/modules/changes/changes.component';
import {
Gender,
MachineResponse,
MachineView,
NotificationType,
UserEmail,
UserPhone,
UserProfile,
UserState,
UserView,
Gender,
MachineResponse,
MachineView,
NotificationType,
UserEmail,
UserPhone,
UserProfile,
UserState,
UserView,
} from 'src/app/proto/generated/management_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import {ManagementService} from 'src/app/services/mgmt.service';
import {ToastService} from 'src/app/services/toast.service';
@Component({
selector: 'app-user-detail',

View File

@@ -38,6 +38,7 @@ import {
Views,
} from '../proto/generated/admin_pb';
import { GrpcService } from './grpc.service';
import {IdpUpdate} from '../proto/generated/management_pb';
@Injectable({
providedIn: 'root',
@@ -149,6 +150,12 @@ export class AdminService {
return this.grpcService.admin.idpByID(req);
}
public async UpdateIdp(
req: IdpUpdate,
): Promise<Idp> {
return this.grpcService.admin.updateIdpConfig(req);
}
public async CreateOidcIdp(
req: OidcIdpConfigCreate,
): Promise<Idp> {
@@ -158,7 +165,7 @@ export class AdminService {
public async UpdateOidcIdpConfig(
req: OidcIdpConfigUpdate,
): Promise<OidcIdpConfig> {
return this.grpcService.mgmt.updateOidcIdpConfig(req);
return this.grpcService.admin.updateOidcIdpConfig(req);
}
public async RemoveIdpConfig(

View File

@@ -7,6 +7,8 @@ import { catchError, filter, finalize, first, map, mergeMap, switchMap, take, ti
import {
Changes,
ChangesRequest,
ExternalIDPSearchRequest,
ExternalIDPSearchResponse,
Gender,
MfaOtpResponse,
MultiFactors,
@@ -283,6 +285,18 @@ export class GrpcAuthService {
return this.grpcService.auth.changeMyPassword(req);
}
public async SearchExternalIdps(
userId: string,
limit: number,
offset: number,
asc?: boolean,
): Promise<ExternalIDPSearchResponse> {
const req = new ExternalIDPSearchRequest();
req.setLimit(limit);
req.setOffset(offset);
return this.grpcService.auth.searchMyExternalIDPs(req);
}
public async AddMfaOTP(): Promise<MfaOtpResponse> {
return this.grpcService.auth.addMfaOTP(
new Empty(),

View File

@@ -26,6 +26,7 @@ import {
Iam,
Idp,
IdpID,
IdpUpdate,
IdpProviderAdd,
IdpProviderID,
IdpProviderSearchRequest,
@@ -224,6 +225,12 @@ export class ManagementService {
return this.grpcService.mgmt.idpByID(req);
}
public async UpdateIdp(
req: IdpUpdate,
): Promise<Idp> {
return this.grpcService.mgmt.updateIdpConfig(req);
}
public async CreateOidcIdp(
req: OidcIdpConfigCreate,
): Promise<Idp> {
@@ -331,18 +338,18 @@ export class ManagementService {
return this.grpcService.mgmt.searchMachineKeys(req);
}
public async SearchExternalIdps(
userId: string,
limit: number,
offset: number,
asc?: boolean,
): Promise<ExternalIDPSearchResponse> {
const req = new ExternalIDPSearchRequest();
req.setUserId(userId);
req.setLimit(limit);
req.setOffset(offset);
return this.grpcService.mgmt.searchUserExternalIDPs(req);
}
public async SearchExternalIdps(
userId: string,
limit: number,
offset: number,
asc?: boolean,
): Promise<ExternalIDPSearchResponse> {
const req = new ExternalIDPSearchRequest();
req.setUserId(userId);
req.setLimit(limit);
req.setOffset(offset);
return this.grpcService.mgmt.searchUserExternalIDPs(req);
}
public async GetIam(): Promise<Iam> {
const req = new Empty();
return this.grpcService.mgmt.getIam(req);

View File

@@ -633,12 +633,19 @@
},
"IDP":{
"LIST": {
"TITLE":"Identity Providers",
"TITLE":"Identitäts Providers",
"DESCRIPTION":"Definieren Sie hier Ihre zusätzlichen Idps, die sie für die Authentifizierung in Ihren Organisationen verwenden können."
},
"CREATE": {
"TITLE":"Neuer Identity Provider",
"DESCRIPTION":"Definieren Sie hier die Zugangsdaten des neuen Identity Providers"
"TITLE":"Neuer Identitäts Provider",
"DESCRIPTION":"Definieren Sie hier die Zugangsdaten des neuen Identitäts Providers"
},
"DETAIL": {
"TITLE":"Identitäts Provider",
"DESCRIPTION":"Konfiguration deines Identitäts Providers",
"OIDC": {
"TITLE": "OIDC Konfiguration"
}
},
"TYPES": {
"0":"unknown",
@@ -646,10 +653,15 @@
"2":"Organisation"
},
"STATES":{
"0":"aktiv",
"1":"inaktiv"
"1":"aktiv",
"2":"inaktiv"
},
"MAPPINTFIELD": {
"1": "Preferred Username",
"2": "Email"
},
"TYPE":"Typ",
"ID": "ID",
"NAME":"Name",
"CONFIG":"Konfiguration",
"STATE":"Status",
@@ -658,11 +670,16 @@
"SCOPESLIST":"Scopes List",
"CLIENTID":"Client ID",
"CLIENTSECRET":"Client Secret",
"IDPDISPLAYNAMMAPPING": "IDP Anzeigename Mapping",
"USERNAMEMAPPING": "Username Mapping",
"CREATIONDATE":"Erstelldatum",
"CHANGEDATE":"Letzte Änderung",
"DEACTIVATE":"Deaktivieren",
"ACTIVATE":"Aktivieren",
"DELETE":"Löschen"
"DELETE":"Löschen",
"TOAST": {
"SAVED": "Erfolgreich gespeichert."
}
},
"LOGINPOLICY": {
"CREATE": {

View File

@@ -640,16 +640,28 @@
"TITLE":"New Identity Provider",
"DESCRIPTION":"Configure the Endpoint of your new service provider."
},
"DETAIL": {
"TITLE":"Identity Provider",
"DESCRIPTION":"Configuration of your identity provider.",
"OIDC": {
"TITLE": "OIDC Configuration"
}
},
"TYPES": {
"0":"unknown",
"1":"System",
"2":"Organisation"
},
"STATES":{
"0":"active",
"1":"inactive"
"1":"active",
"2":"inactive"
},
"MAPPINTFIELD": {
"1": "Preferred Username",
"2": "Email"
},
"TYPE":"Type",
"ID": "ID",
"NAME":"Name",
"CONFIG":"Configuration",
"STATE":"State",
@@ -658,11 +670,16 @@
"SCOPESLIST":"Scopes List",
"CLIENTID":"Client ID",
"CLIENTSECRET":"Client Secret",
"IDPDISPLAYNAMMAPPING": "IDP Anzeigename Mapping",
"USERNAMEMAPPING": "Username Mapping",
"CREATIONDATE":"Created At",
"CHANGEDATE":"Last Modified",
"DEACTIVATE":"Deactivate",
"ACTIVATE":"Activate",
"DELETE":"Delete"
"DELETE":"Delete",
"TOAST": {
"SAVED": "Successfully saved."
}
},
"LOGINPOLICY": {
"CREATE": {