feat(console): identity providers and login policies (#722)

* idp list, idp create route

* idp modules, lazy import, i18n, routing

* generic service, i18n

* seperate lockout, age policy component

* seperate component modules

* routing

* enum class

* login policy

* iam policy grid

* login policy providers

* idps login policy

* add idp dialog component

* add idp to loginpolicy

* delete idp config, iam policy grid

* remove idp from loginpolicy

* idp detail component, generic idp create

* lint

* idp detail clientid-secrets, issuer, scopes

* hide clientsecret on update

* rm background style, idp config

* app tooltip fix

* lint

* dont refresh on idp select

* Update console/src/app/modules/idp-create/idp-create.component.html

Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
Max Peintner
2020-09-17 10:22:41 +02:00
committed by GitHub
parent 845026e43f
commit 58b01cdf3f
96 changed files with 3401 additions and 735 deletions

View File

@@ -0,0 +1,36 @@
<h1 mat-dialog-title>
<span class="title">{{'LOGINPOLICY.ADDIDP.TITLE' | translate}}</span>
</h1>
<p class="desc"> {{'LOGINPOLICY.ADDIDP.DESCRIPTION' | translate}}</p>
<div mat-dialog-content>
<mat-form-field *ngIf="serviceType == PolicyComponentServiceType.MGMT" class="full-width" appearance="outline">
<mat-label>{{ 'IDP.TYPE' | translate }}</mat-label>
<mat-select [(ngModel)]="idpType" (selectionChange)="loadIdps()" (selectionChange)="loadIdps()">
<mat-option *ngFor="let type of idpTypes" [value]="type">
{{ 'IDP.TYPES.'+type | translate}}
</mat-option>
</mat-select>
</mat-form-field>
<p>{{'LOGINPOLICY.ADDIDP.SELECTIDPS' | translate}}</p>
<mat-form-field class="full-width" appearance="outline">
<mat-label>{{ 'LOGINPOLICY.ADDIDP.SELECTIDPS' | translate }}</mat-label>
<mat-select [(ngModel)]="idp">
<mat-option *ngFor="let idp of availableIdps" [value]="idp">
{{ idp.name }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div mat-dialog-actions class="action">
<button mat-button (click)="closeDialog()">
{{'ACTIONS.CANCEL' | translate}}
</button>
<button [disabled]="!idp || !idpType " color="primary" mat-raised-button class="ok-button"
(click)="closeDialogWithSuccess()">
{{'ACTIONS.ADD' | translate}}
</button>
</div>

View File

@@ -0,0 +1,25 @@
.title {
font-size: 1.2rem;
}
.desc {
color: #8795a1;
font-size: .9rem;
}
.full-width {
width: 100%;
}
.action {
display: flex;
justify-content: flex-end;
.ok-button {
margin-left: .5rem;
}
button {
border-radius: .5rem;
}
}

View File

@@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AddIdpDialogComponent } from './add-idp-dialog.component';
describe('AddIdpDialogComponent', () => {
let component: AddIdpDialogComponent;
let fixture: ComponentFixture<AddIdpDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AddIdpDialogComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AddIdpDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,84 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { IdpView as AdminIdpView } from 'src/app/proto/generated/admin_pb';
import {
Idp,
IdpProviderType,
IdpSearchKey,
IdpSearchQuery,
IdpView as MgmtIdpView,
SearchMethod,
} from 'src/app/proto/generated/management_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { PolicyComponentServiceType } from '../../policy-component-types.enum';
@Component({
selector: 'app-add-idp-dialog',
templateUrl: './add-idp-dialog.component.html',
styleUrls: ['./add-idp-dialog.component.scss'],
})
export class AddIdpDialogComponent {
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public idpType!: IdpProviderType;
public idpTypes: IdpProviderType[] = [
IdpProviderType.IDPPROVIDERTYPE_SYSTEM,
IdpProviderType.IDPPROVIDERTYPE_ORG,
];
public idp: Idp.AsObject | undefined = undefined;
public availableIdps: Array<AdminIdpView.AsObject | MgmtIdpView.AsObject> | string[] = [];
public IdpProviderType: any = IdpProviderType;
constructor(
private mgmtService: ManagementService,
private adminService: AdminService,
public dialogRef: MatDialogRef<AddIdpDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
if (data.serviceType) {
this.serviceType = data.serviceType;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.idpType = IdpProviderType.IDPPROVIDERTYPE_ORG;
break;
case PolicyComponentServiceType.ADMIN:
this.idpType = IdpProviderType.IDPPROVIDERTYPE_SYSTEM;
break;
}
}
this.loadIdps();
}
public loadIdps(): void {
this.idp = undefined;
if (this.serviceType === PolicyComponentServiceType.MGMT) {
const query: IdpSearchQuery = new IdpSearchQuery();
query.setKey(IdpSearchKey.IDPSEARCHKEY_PROVIDER_TYPE);
query.setMethod(SearchMethod.SEARCHMETHOD_EQUALS);
query.setValue(this.idpType.toString());
this.mgmtService.SearchIdps().then(idps => {
this.availableIdps = idps.toObject().resultList;
});
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
this.adminService.SearchIdps().then(idps => {
this.availableIdps = idps.toObject().resultList;
});
}
}
public closeDialog(): void {
this.dialogRef.close(false);
}
public closeDialogWithSuccess(): void {
this.dialogRef.close({
idp: this.idp,
type: this.idpType,
});
}
}

View File

@@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { TranslateModule } from '@ngx-translate/core';
import { AddIdpDialogComponent } from './add-idp-dialog.component';
@NgModule({
declarations: [AddIdpDialogComponent],
imports: [
CommonModule,
MatDialogModule,
MatButtonModule,
TranslateModule,
MatFormFieldModule,
MatSelectModule,
FormsModule,
],
})
export class AddIdpDialogModule { }

View File

@@ -0,0 +1,20 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginPolicyComponent } from './login-policy.component';
const routes: Routes = [
{
path: '',
component: LoginPolicyComponent,
data: {
animation: 'DetailPage',
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class LoginPolicyRoutingModule { }

View File

@@ -0,0 +1,51 @@
<app-detail-layout [backRouterLink]="[ '/org']" [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()"
mat-stroked-button>
{{'ORG.POLICY.DELETE' | translate}}
</button>
</ng-container>
<div class="content" *ngIf="loginData">
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="loginData.allowUsernamePassword">
</mat-slide-toggle>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.ALLOWREGISTER' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="loginData.allowRegister">
</mat-slide-toggle>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.ALLOWEXTERNALIDP' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="loginData.allowExternalIdp">
</mat-slide-toggle>
</div>
</div>
<p class="subheader">{{'LOGINPOLICY.IDPS' | translate}}</p>
<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>
</div>
<div class="new-idp" (click)="openDialog()">
<mat-icon>add</mat-icon>
</div>
</div>
<div class="btn-container">
<button (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div>
</app-detail-layout>

View File

@@ -0,0 +1,85 @@
button {
border-radius: .5rem;
}
.content {
padding-top: 1rem;
display: flex;
flex-direction: column;
width: 100%;
.row {
display: flex;
align-items: center;
padding: .5rem 0;
.left-desc {
color: #8795a1;
font-size: .9rem;
}
.fill-space {
flex: 1;
}
.length-wrapper {
display: flex;
align-items: center;
}
}
}
.btn-container {
display: flex;
justify-content: flex-end;
width: 100%;
button {
margin-top: 3rem;
display: block;
padding: .5rem 4rem;
border-radius: .5rem;
}
}
.subheader {
width: 100%;
}
.idps {
display: flex;
margin: 0 -.5rem;
.idp,
.new-idp {
display: flex;
align-items: center;
justify-content: center;
margin: .5rem;
height: 50px;
width: 50px;
border: 1px solid #8795a1;
border-radius: .5rem;
cursor: pointer;
position: relative;
.rm {
position: absolute;
top: 0;
left: 0;
transform: translateX(-50%) translateY(-50%);
cursor: pointer;
}
&:hover {
background-color: #ffffff10;
}
img {
height: 100%;
width: 100%;
object-fit: scale-down;
}
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginPolicyComponent } from './login-policy.component';
describe('LoginPolicyComponent', () => {
let component: LoginPolicyComponent;
let fixture: ComponentFixture<LoginPolicyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [LoginPolicyComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,179 @@
import { Component, Injector, OnDestroy, Type } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import {
DefaultLoginPolicy,
DefaultLoginPolicyView,
IdpProviderView as AdminIdpProviderView,
IdpView as AdminIdpView,
} from 'src/app/proto/generated/admin_pb';
import {
IdpProviderType,
IdpProviderView as MgmtIdpProviderView,
IdpView as MgmtIdpView,
LoginPolicy,
LoginPolicyView,
} 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';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component';
@Component({
selector: 'app-login-policy',
templateUrl: './login-policy.component.html',
styleUrls: ['./login-policy.component.scss'],
})
export class LoginPolicyComponent implements OnDestroy {
public loginData!: LoginPolicy.AsObject | DefaultLoginPolicy.AsObject;
private sub: Subscription = new Subscription();
private service!: ManagementService | AdminService;
PolicyComponentServiceType: any = PolicyComponentServiceType;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public idps: MgmtIdpProviderView.AsObject[] | AdminIdpProviderView.AsObject[] = [];
constructor(
private route: ActivatedRoute,
private router: Router,
private toast: ToastService,
private dialog: MatDialog,
private injector: Injector,
) {
this.sub = this.route.data.pipe(switchMap(data => {
console.log(data.serviceType);
this.serviceType = data.serviceType;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
break;
}
return this.route.params;
})).subscribe(() => {
this.getData().then(data => {
if (data) {
this.loginData = data.toObject();
}
});
this.getIdps().then(idps => {
console.log(idps);
this.idps = idps;
});
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getData():
Promise<LoginPolicyView | DefaultLoginPolicyView> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).GetLoginPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).GetDefaultLoginPolicy();
}
}
private async getIdps(): Promise<MgmtIdpProviderView.AsObject[] | AdminIdpProviderView.AsObject[]> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).GetLoginPolicyIdpProviders()
.then((providers) => {
return providers.toObject().resultList;
});
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).GetDefaultLoginPolicyIdpProviders()
.then((providers) => {
return providers.toObject().resultList;
});
}
}
private async updateData():
Promise<LoginPolicy | DefaultLoginPolicy> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
const mgmtreq = new LoginPolicy();
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
mgmtreq.setAllowRegister(this.loginData.allowRegister);
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
return (this.service as ManagementService).UpdateLoginPolicy(mgmtreq);
case PolicyComponentServiceType.ADMIN:
const adminreq = new DefaultLoginPolicy();
adminreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
adminreq.setAllowRegister(this.loginData.allowRegister);
adminreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
return (this.service as AdminService).UpdateDefaultLoginPolicy(adminreq);
}
}
public savePolicy(): void {
this.updateData().then(() => {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.router.navigate(['org']);
break;
case PolicyComponentServiceType.ADMIN:
this.router.navigate(['iam']);
break;
}
}).catch(error => {
this.toast.showError(error);
});
}
public deletePolicy(): Promise<Empty> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).RemoveLoginPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).GetDefaultLoginPolicy();
}
}
public openDialog(): void {
const dialogRef = this.dialog.open(AddIdpDialogComponent, {
data: {
serviceType: this.serviceType,
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp && resp.idp && resp.type) {
this.addIdp(resp.idp, resp.type);
}
});
}
private addIdp(idp: AdminIdpView.AsObject | MgmtIdpView.AsObject,
type: IdpProviderType = IdpProviderType.IDPPROVIDERTYPE_SYSTEM): Promise<any> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).addIdpProviderToLoginPolicy(idp.id, type);
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).AddIdpProviderToDefaultLoginPolicy(idp.id);
}
}
public removeIdp(idp: AdminIdpView.AsObject | MgmtIdpView.AsObject): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
(this.service as ManagementService).RemoveIdpProviderFromLoginPolicy(idp.id);
break;
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService).RemoveIdpProviderFromDefaultLoginPolicy(idp.id);
break;
}
}
}

View File

@@ -0,0 +1,36 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
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';
@NgModule({
declarations: [LoginPolicyComponent],
imports: [
LoginPolicyRoutingModule,
CommonModule,
FormsModule,
MatInputModule,
MatFormFieldModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRolePipeModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
AddIdpDialogModule,
],
})
export class LoginPolicyModule { }

View File

@@ -0,0 +1,30 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PolicyComponentAction } from '../policy-component-action.enum';
import { PasswordAgePolicyComponent } from './password-age-policy.component';
const routes: Routes = [
{
path: '',
component: PasswordAgePolicyComponent,
data: {
animation: 'DetailPage',
action: PolicyComponentAction.MODIFY,
},
},
{
path: 'create',
component: PasswordAgePolicyComponent,
data: {
animation: 'DetailPage',
action: PolicyComponentAction.CREATE,
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class PasswordAgePolicyRoutingModule { }

View File

@@ -0,0 +1,48 @@
<app-detail-layout [backRouterLink]="[ '/org']" [title]="title ? (title | translate) : ''"
[description]="desc ? (desc | translate) : ''">
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
mat-stroked-button>
{{'ORG.POLICY.DELETE' | translate}}
</button>
</ng-template>
<div class="content" *ngIf="ageData">
<mat-form-field class="description-formfield" appearance="outline">
<mat-label>{{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }}</mat-label>
<input matInput name="description" ngDefaultControl [(ngModel)]="ageData.description" required />
</mat-form-field>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.EXPIREWARNDAYS' | translate}}</span>
<span class="fill-space"></span>
<div class="length-wrapper">
<button mat-icon-button (click)="incrementExpireWarnDays()">
<mat-icon>add</mat-icon>
</button>
<span>{{ageData?.expireWarnDays}}</span>
<button mat-icon-button (click)="decrementExpireWarnDays()">
<mat-icon>remove</mat-icon>
</button>
</div>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.MAXAGEDAYS' | translate}}</span>
<span class="fill-space"></span>
<div class="length-wrapper">
<button mat-icon-button (click)="incrementMaxAgeDays()">
<mat-icon>add</mat-icon>
</button>
<span>{{ageData?.maxAgeDays}}</span>
<button mat-icon-button (click)="decrementMaxAgeDays()">
<mat-icon>remove</mat-icon>
</button>
</div>
</div>
</div>
<div class="btn-container">
<button (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div>
</app-detail-layout>

View File

@@ -0,0 +1,44 @@
button {
border-radius: .5rem;
}
.content {
padding-top: 1rem;
display: flex;
flex-direction: column;
width: 100%;
.row {
display: flex;
align-items: center;
padding: .5rem 0;
.left-desc {
color: #8795a1;
font-size: .9rem;
}
.fill-space {
flex: 1;
}
.length-wrapper {
display: flex;
align-items: center;
}
}
}
.btn-container {
display: flex;
justify-content: flex-end;
width: 100%;
button {
margin-top: 3rem;
display: block;
padding: .5rem 4rem;
border-radius: .5rem;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PasswordAgePolicyComponent } from './password-age-policy.component';
describe('PasswordAgePolicyComponent', () => {
let component: PasswordAgePolicyComponent;
let fixture: ComponentFixture<PasswordAgePolicyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PasswordAgePolicyComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PasswordAgePolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,126 @@
import { Component, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import {
OrgIamPolicy,
PasswordAgePolicy,
PasswordComplexityPolicy,
PasswordLockoutPolicy,
} from 'src/app/proto/generated/management_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentAction } from '../policy-component-action.enum';
@Component({
selector: 'app-password-age-policy',
templateUrl: './password-age-policy.component.html',
styleUrls: ['./password-age-policy.component.scss'],
})
export class PasswordAgePolicyComponent implements OnDestroy {
public title: string = '';
public desc: string = '';
componentAction: PolicyComponentAction = PolicyComponentAction.CREATE;
public PolicyComponentAction: any = PolicyComponentAction;
public ageData!: PasswordAgePolicy.AsObject;
private sub: Subscription = new Subscription();
constructor(
private route: ActivatedRoute,
private mgmtService: ManagementService,
private router: Router,
private toast: ToastService,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.componentAction = data.action;
return this.route.params;
})).subscribe(params => {
this.title = 'ORG.POLICY.PWD_AGE.TITLECREATE';
this.desc = 'ORG.POLICY.PWD_AGE.DESCRIPTIONCREATE';
if (this.componentAction === PolicyComponentAction.MODIFY) {
this.getData(params).then(data => {
if (data) {
this.ageData = data.toObject() as PasswordAgePolicy.AsObject;
}
});
}
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getData(params: any):
Promise<PasswordLockoutPolicy | PasswordAgePolicy | PasswordComplexityPolicy | OrgIamPolicy | undefined> {
this.title = 'ORG.POLICY.PWD_AGE.TITLE';
this.desc = 'ORG.POLICY.PWD_AGE.DESCRIPTION';
return this.mgmtService.GetPasswordAgePolicy();
}
public deletePolicy(): void {
this.mgmtService.DeletePasswordAgePolicy(this.ageData.id).then(() => {
this.toast.showInfo('Successfully deleted');
}).catch(error => {
this.toast.showError(error);
});
}
public incrementExpireWarnDays(): void {
if (this.ageData?.expireWarnDays !== undefined) {
this.ageData.expireWarnDays++;
}
}
public decrementExpireWarnDays(): void {
if (this.ageData?.expireWarnDays && this.ageData?.expireWarnDays > 0) {
this.ageData.expireWarnDays--;
}
}
public incrementMaxAgeDays(): void {
if (this.ageData?.maxAgeDays !== undefined) {
this.ageData.maxAgeDays++;
}
}
public decrementMaxAgeDays(): void {
if (this.ageData?.maxAgeDays && this.ageData?.maxAgeDays > 0) {
this.ageData.maxAgeDays--;
}
}
public savePolicy(): void {
if (this.componentAction === PolicyComponentAction.CREATE) {
this.mgmtService.CreatePasswordAgePolicy(
this.ageData.description,
this.ageData.maxAgeDays,
this.ageData.expireWarnDays,
).then(() => {
this.router.navigate(['org']);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentAction === PolicyComponentAction.MODIFY) {
this.mgmtService.UpdatePasswordAgePolicy(
this.ageData.description,
this.ageData.maxAgeDays,
this.ageData.expireWarnDays,
).then(() => {
this.router.navigate(['org']);
}).catch(error => {
this.toast.showError(error);
});
}
}
}

View File

@@ -0,0 +1,34 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { PasswordAgePolicyRoutingModule } from './password-age-policy-routing.module';
import { PasswordAgePolicyComponent } from './password-age-policy.component';
@NgModule({
declarations: [PasswordAgePolicyComponent],
imports: [
PasswordAgePolicyRoutingModule,
CommonModule,
FormsModule,
MatInputModule,
MatFormFieldModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
],
})
export class PasswordAgePolicyModule { }

View File

@@ -0,0 +1,30 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PolicyComponentAction } from '../policy-component-action.enum';
import { PasswordComplexityPolicyComponent } from './password-complexity-policy.component';
const routes: Routes = [
{
path: '',
component: PasswordComplexityPolicyComponent,
data: {
animation: 'DetailPage',
action: PolicyComponentAction.MODIFY,
},
},
{
path: 'create',
component: PasswordComplexityPolicyComponent,
data: {
animation: 'DetailPage',
action: PolicyComponentAction.CREATE,
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class PasswordComplexityPolicyRoutingModule { }

View File

@@ -0,0 +1,60 @@
<app-detail-layout [backRouterLink]="[ '/org']" [title]="title ? (title | translate) : ''"
[description]="desc ? (desc | translate) : ''">
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
mat-stroked-button>
{{'ORG.POLICY.DELETE' | translate}}
</button>
</ng-template>
<div *ngIf="complexityData" class="content">
<mat-form-field class="description-formfield" appearance="outline">
<mat-label>{{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }}</mat-label>
<input matInput name="description" ngDefaultControl [(ngModel)]="complexityData.description" required />
</mat-form-field>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.MINLENGTH' | translate}}</span>
<span class="fill-space"></span>
<div class="length-wrapper">
<button mat-icon-button (click)="decrementLength()">
<mat-icon>remove</mat-icon>
</button>
<span>{{complexityData?.minLength}}</span>
<button mat-icon-button (click)="incrementLength()">
<mat-icon>add</mat-icon>
</button>
</div>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASNUMBER' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="complexityData.hasNumber">
</mat-slide-toggle>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASSYMBOL' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasSymbol" ngDefaultControl [(ngModel)]="complexityData.hasSymbol">
</mat-slide-toggle>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASLOWERCASE' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasLowercase" ngDefaultControl
[(ngModel)]="complexityData.hasLowercase">
</mat-slide-toggle>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASUPPERCASE' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasUppercase" ngDefaultControl
[(ngModel)]="complexityData.hasUppercase">
</mat-slide-toggle>
</div>
</div>
<div class="btn-container">
<button (click)="savePolicy()" color="primary" type="submit" [disabled]="!complexityData?.description"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div>
</app-detail-layout>

View File

@@ -0,0 +1,44 @@
button {
border-radius: .5rem;
}
.content {
padding-top: 1rem;
display: flex;
flex-direction: column;
width: 100%;
.row {
display: flex;
align-items: center;
padding: .5rem 0;
.left-desc {
color: #8795a1;
font-size: .9rem;
}
.fill-space {
flex: 1;
}
.length-wrapper {
display: flex;
align-items: center;
}
}
}
.btn-container {
display: flex;
justify-content: flex-end;
width: 100%;
button {
margin-top: 3rem;
display: block;
padding: .5rem 4rem;
border-radius: .5rem;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PasswordComplexityPolicyComponent } from './password-complexity-policy.component';
describe('PasswordComplexityPolicyComponent', () => {
let component: PasswordComplexityPolicyComponent;
let fixture: ComponentFixture<PasswordComplexityPolicyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PasswordComplexityPolicyComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PasswordComplexityPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,119 @@
import { Component, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import {
OrgIamPolicy,
PasswordAgePolicy,
PasswordComplexityPolicy,
PasswordLockoutPolicy,
} from 'src/app/proto/generated/management_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentAction } from '../policy-component-action.enum';
@Component({
selector: 'app-password-policy',
templateUrl: './password-complexity-policy.component.html',
styleUrls: ['./password-complexity-policy.component.scss'],
})
export class PasswordComplexityPolicyComponent implements OnDestroy {
public title: string = '';
public desc: string = '';
componentAction: PolicyComponentAction = PolicyComponentAction.CREATE;
public PolicyComponentAction: any = PolicyComponentAction;
public complexityData!: PasswordComplexityPolicy.AsObject;
private sub: Subscription = new Subscription();
constructor(
private route: ActivatedRoute,
private mgmtService: ManagementService,
private router: Router,
private toast: ToastService,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.componentAction = data.action;
return this.route.params;
})).subscribe(params => {
this.title = 'ORG.POLICY.PWD_COMPLEXITY.TITLECREATE';
this.desc = 'ORG.POLICY.PWD_COMPLEXITY.DESCRIPTIONCREATE';
if (this.componentAction === PolicyComponentAction.MODIFY) {
this.getData(params).then(data => {
if (data) {
this.complexityData = data.toObject() as PasswordComplexityPolicy.AsObject;
}
});
}
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getData(params: any):
Promise<PasswordLockoutPolicy | PasswordAgePolicy | PasswordComplexityPolicy | OrgIamPolicy | undefined> {
this.title = 'ORG.POLICY.PWD_COMPLEXITY.TITLE';
this.desc = 'ORG.POLICY.PWD_COMPLEXITY.DESCRIPTION';
return this.mgmtService.GetPasswordComplexityPolicy();
}
public deletePolicy(): void {
this.mgmtService.DeletePasswordComplexityPolicy(this.complexityData.id).then(() => {
this.toast.showInfo('Successfully deleted');
}).catch(error => {
this.toast.showError(error);
});
}
public incrementLength(): void {
if (this.complexityData?.minLength !== undefined && this.complexityData?.minLength <= 72) {
this.complexityData.minLength++;
}
}
public decrementLength(): void {
if (this.complexityData?.minLength && this.complexityData?.minLength > 1) {
this.complexityData.minLength--;
}
}
public savePolicy(): void {
if (this.componentAction === PolicyComponentAction.CREATE) {
this.mgmtService.CreatePasswordComplexityPolicy(
this.complexityData.description,
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.router.navigate(['org']);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentAction === PolicyComponentAction.MODIFY) {
this.mgmtService.UpdatePasswordComplexityPolicy(
this.complexityData.description,
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.router.navigate(['org']);
}).catch(error => {
this.toast.showError(error);
});
}
}
}

View File

@@ -0,0 +1,34 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { PasswordComplexityPolicyRoutingModule } from './password-complexity-policy-routing.module';
import { PasswordComplexityPolicyComponent } from './password-complexity-policy.component';
@NgModule({
declarations: [PasswordComplexityPolicyComponent],
imports: [
PasswordComplexityPolicyRoutingModule,
CommonModule,
FormsModule,
MatInputModule,
MatFormFieldModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
],
})
export class PasswordComplexityPolicyModule { }

View File

@@ -0,0 +1,30 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PolicyComponentAction } from '../policy-component-action.enum';
import { PasswordIamPolicyComponent } from './password-iam-policy.component';
const routes: Routes = [
{
path: '',
component: PasswordIamPolicyComponent,
data: {
animation: 'DetailPage',
action: PolicyComponentAction.MODIFY,
},
},
{
path: 'create',
component: PasswordIamPolicyComponent,
data: {
animation: 'DetailPage',
action: PolicyComponentAction.CREATE,
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class PasswordIamPolicyRoutingModule { }

View File

@@ -0,0 +1,28 @@
<app-detail-layout [backRouterLink]="[ '/org']" [title]="title ? (title | translate) : ''"
[description]="desc ? (desc | translate) : ''">
<!-- <ng-template appHasRole [appHasRole]="['iam.policy.write']">
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
mat-stroked-button>
{{'ORG.POLICY.DELETE' | translate}}
</button>
</ng-template> -->
<div class="content" *ngIf="iamData">
<mat-form-field class="description-formfield" appearance="outline">
<mat-label>{{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }}</mat-label>
<input matInput name="description" ngDefaultControl [(ngModel)]="iamData.description" required />
</mat-form-field>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="iamData.userLoginMustBeDomain">
</mat-slide-toggle>
</div>
</div>
<div class="btn-container">
<button (click)="savePolicy()" color="primary" type="submit" [disabled]="!iamData?.description"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div>
</app-detail-layout>

View File

@@ -0,0 +1,44 @@
button {
border-radius: .5rem;
}
.content {
padding-top: 1rem;
display: flex;
flex-direction: column;
width: 100%;
.row {
display: flex;
align-items: center;
padding: .5rem 0;
.left-desc {
color: #8795a1;
font-size: .9rem;
}
.fill-space {
flex: 1;
}
.length-wrapper {
display: flex;
align-items: center;
}
}
}
.btn-container {
display: flex;
justify-content: flex-end;
width: 100%;
button {
margin-top: 3rem;
display: block;
padding: .5rem 4rem;
border-radius: .5rem;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PasswordIamPolicyComponent } from './password-iam-policy.component';
describe('PasswordIamPolicyComponent', () => {
let component: PasswordIamPolicyComponent;
let fixture: ComponentFixture<PasswordIamPolicyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PasswordIamPolicyComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PasswordIamPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,102 @@
import { Component, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import {
OrgIamPolicy,
PasswordAgePolicy,
PasswordComplexityPolicy,
PasswordLockoutPolicy,
} from 'src/app/proto/generated/management_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { StorageService } from 'src/app/services/storage.service';
import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentAction } from '../policy-component-action.enum';
@Component({
selector: 'app-password-iam-policy',
templateUrl: './password-iam-policy.component.html',
styleUrls: ['./password-iam-policy.component.scss'],
})
export class PasswordIamPolicyComponent implements OnDestroy {
public title: string = '';
public desc: string = '';
componentAction: PolicyComponentAction = PolicyComponentAction.CREATE;
public PolicyComponentAction: any = PolicyComponentAction;
public iamData!: OrgIamPolicy.AsObject;
private sub: Subscription = new Subscription();
constructor(
private route: ActivatedRoute,
private mgmtService: ManagementService,
private adminService: AdminService,
private router: Router,
private toast: ToastService,
private sessionStorage: StorageService,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.componentAction = data.action;
console.log(data.action);
return this.route.params;
})).subscribe(params => {
this.title = 'ORG.POLICY.IAM_POLICY.TITLECREATE';
this.desc = 'ORG.POLICY.IAM_POLICY.DESCRIPTIONCREATE';
if (this.componentAction === PolicyComponentAction.MODIFY) {
this.getData(params).then(data => {
if (data) {
this.iamData = data.toObject() as OrgIamPolicy.AsObject;
}
});
}
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getData(params: any):
Promise<PasswordLockoutPolicy | PasswordAgePolicy | PasswordComplexityPolicy | OrgIamPolicy | undefined> {
this.title = 'ORG.POLICY.IAM_POLICY.TITLECREATE';
this.desc = 'ORG.POLICY.IAM_POLICY.DESCRIPTIONCREATE';
return this.mgmtService.GetMyOrgIamPolicy();
}
public savePolicy(): void {
if (this.componentAction === PolicyComponentAction.CREATE) {
const orgId = this.sessionStorage.getItem('organization');
if (orgId) {
this.adminService.CreateOrgIamPolicy(
orgId,
this.iamData.description,
this.iamData.userLoginMustBeDomain,
).then(() => {
this.router.navigate(['org']);
}).catch(error => {
this.toast.showError(error);
});
}
} else if (this.componentAction === PolicyComponentAction.MODIFY) {
const orgId = this.sessionStorage.getItem('organization');
if (orgId) {
this.adminService.UpdateOrgIamPolicy(
orgId,
this.iamData.description,
this.iamData.userLoginMustBeDomain,
).then(() => {
this.router.navigate(['org']);
}).catch(error => {
this.toast.showError(error);
});
}
}
}
}

View File

@@ -0,0 +1,34 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { PasswordIamPolicyRoutingModule } from './password-iam-policy-routing.module';
import { PasswordIamPolicyComponent } from './password-iam-policy.component';
@NgModule({
declarations: [PasswordIamPolicyComponent],
imports: [
PasswordIamPolicyRoutingModule,
CommonModule,
FormsModule,
MatInputModule,
MatFormFieldModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
],
})
export class PasswordIamPolicyModule { }

View File

@@ -0,0 +1,30 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PolicyComponentAction } from '../policy-component-action.enum';
import { PasswordLockoutPolicyComponent } from './password-lockout-policy.component';
const routes: Routes = [
{
path: '',
component: PasswordLockoutPolicyComponent,
data: {
animation: 'DetailPage',
action: PolicyComponentAction.MODIFY,
},
},
{
path: 'create',
component: PasswordLockoutPolicyComponent,
data: {
animation: 'DetailPage',
action: PolicyComponentAction.CREATE,
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class PasswordLockoutPolicyRoutingModule { }

View File

@@ -0,0 +1,41 @@
<app-detail-layout [backRouterLink]="[ '/org']" [title]="title ? (title | translate) : ''"
[description]="desc ? (desc | translate) : ''">
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
mat-stroked-button>
{{'ORG.POLICY.DELETE' | translate}}
</button>
</ng-template>
<div class="content" *ngIf="lockoutData">
<mat-form-field class="description-formfield" appearance="outline">
<mat-label>{{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }}</mat-label>
<input matInput name="description" ngDefaultControl [(ngModel)]="lockoutData.description" required />
</mat-form-field>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.MAXATTEMPTS' | translate}}</span>
<span class="fill-space"></span>
<div class="length-wrapper">
<button mat-icon-button (click)="incrementMaxAttempts()">
<mat-icon>add</mat-icon>
</button>
<span>{{lockoutData?.maxAttempts}}</span>
<button mat-icon-button (click)="decrementMaxAttempts()">
<mat-icon>remove</mat-icon>
</button>
</div>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.SHOWLOCKOUTFAILURES' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="showLockOutFailures" ngDefaultControl
[(ngModel)]="lockoutData.showLockOutFailures">
</mat-slide-toggle>
</div>
</div>
<div class="btn-container">
<button (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div>
</app-detail-layout>

View File

@@ -0,0 +1,44 @@
button {
border-radius: .5rem;
}
.content {
padding-top: 1rem;
display: flex;
flex-direction: column;
width: 100%;
.row {
display: flex;
align-items: center;
padding: .5rem 0;
.left-desc {
color: #8795a1;
font-size: .9rem;
}
.fill-space {
flex: 1;
}
.length-wrapper {
display: flex;
align-items: center;
}
}
}
.btn-container {
display: flex;
justify-content: flex-end;
width: 100%;
button {
margin-top: 3rem;
display: block;
padding: .5rem 4rem;
border-radius: .5rem;
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PasswordLockoutPolicyComponent } from './password-lockout-policy.component';
describe('PasswordLockoutPolicyComponent', () => {
let component: PasswordLockoutPolicyComponent;
let fixture: ComponentFixture<PasswordLockoutPolicyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PasswordLockoutPolicyComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PasswordLockoutPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,113 @@
import { Component, OnDestroy } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import {
OrgIamPolicy,
PasswordAgePolicy,
PasswordComplexityPolicy,
PasswordLockoutPolicy,
} from 'src/app/proto/generated/management_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentAction } from '../policy-component-action.enum';
@Component({
selector: 'app-password-lockout-policy',
templateUrl: './password-lockout-policy.component.html',
styleUrls: ['./password-lockout-policy.component.scss'],
})
export class PasswordLockoutPolicyComponent implements OnDestroy {
public title: string = '';
public desc: string = '';
componentAction: PolicyComponentAction = PolicyComponentAction.CREATE;
public PolicyComponentAction: any = PolicyComponentAction;
public lockoutForm!: FormGroup;
public lockoutData!: PasswordLockoutPolicy.AsObject;
private sub: Subscription = new Subscription();
constructor(
private route: ActivatedRoute,
private mgmtService: ManagementService,
private router: Router,
private toast: ToastService,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.componentAction = data.action;
return this.route.params;
})).subscribe(params => {
this.title = 'ORG.POLICY.PWD_LOCKOUT.TITLECREATE';
this.desc = 'ORG.POLICY.PWD_LOCKOUT.DESCRIPTIONCREATE';
if (this.componentAction === PolicyComponentAction.MODIFY) {
this.getData(params).then(data => {
if (data) {
this.lockoutData = data.toObject() as PasswordLockoutPolicy.AsObject;
}
});
}
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getData(params: any):
Promise<PasswordLockoutPolicy | PasswordAgePolicy | PasswordComplexityPolicy | OrgIamPolicy | undefined> {
this.title = 'ORG.POLICY.PWD_LOCKOUT.TITLE';
this.desc = 'ORG.POLICY.PWD_LOCKOUT.DESCRIPTION';
return this.mgmtService.GetPasswordLockoutPolicy();
}
public deletePolicy(): void {
this.mgmtService.DeletePasswordLockoutPolicy(this.lockoutData.id).then(() => {
this.toast.showInfo('Successfully deleted');
}).catch(error => {
this.toast.showError(error);
});
}
public incrementMaxAttempts(): void {
if (this.lockoutData?.maxAttempts !== undefined) {
this.lockoutData.maxAttempts++;
}
}
public decrementMaxAttempts(): void {
if (this.lockoutData?.maxAttempts && this.lockoutData?.maxAttempts > 0) {
this.lockoutData.maxAttempts--;
}
}
public savePolicy(): void {
if (this.componentAction === PolicyComponentAction.CREATE) {
this.mgmtService.CreatePasswordLockoutPolicy(
this.lockoutData.description,
this.lockoutData.maxAttempts,
this.lockoutData.showLockOutFailures,
).then(() => {
this.router.navigate(['org']);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentAction === PolicyComponentAction.MODIFY) {
this.mgmtService.UpdatePasswordLockoutPolicy(
this.lockoutData.description,
this.lockoutData.maxAttempts,
this.lockoutData.showLockOutFailures,
).then(() => {
this.router.navigate(['org']);
}).catch(error => {
this.toast.showError(error);
});
}
}
}

View File

@@ -0,0 +1,34 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { PasswordLockoutPolicyRoutingModule } from './password-lockout-policy-routing.module';
import { PasswordLockoutPolicyComponent } from './password-lockout-policy.component';
@NgModule({
declarations: [PasswordLockoutPolicyComponent],
imports: [
PasswordLockoutPolicyRoutingModule,
CommonModule,
FormsModule,
MatInputModule,
MatFormFieldModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
],
})
export class PasswordLockoutPolicyModule { }

View File

@@ -0,0 +1,5 @@
export enum PolicyComponentAction {
CREATE = 'create',
MODIFY = 'modify',
}

View File

@@ -0,0 +1,11 @@
export enum PolicyComponentType {
LOCKOUT = 'lockout',
AGE = 'age',
COMPLEXITY = 'complexity',
IAM = 'iam',
LOGIN = 'login',
}
export enum PolicyComponentServiceType {
MGMT = 'mgmt',
ADMIN = 'admin',
}