From 58b01cdf3f5616232fdb7b55ee867e56befa8532 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Thu, 17 Sep 2020 10:22:41 +0200 Subject: [PATCH] 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 Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> --- console/src/app/app.component.scss | 1 + .../idp-create/idp-create-routing.module.ts} | 6 +- .../idp-create/idp-create.component.html | 56 +++ .../idp-create/idp-create.component.scss | 64 ++++ .../idp-create/idp-create.component.spec.ts} | 12 +- .../idp-create/idp-create.component.ts | 142 ++++++++ .../idp-create/idp-create.module.ts} | 29 +- .../idp-table/idp-table.component.html | 85 +++++ .../idp-table/idp-table.component.scss | 77 ++++ .../idp-table/idp-table.component.spec.ts | 25 ++ .../modules/idp-table/idp-table.component.ts | 126 +++++++ .../app/modules/idp-table/idp-table.module.ts | 42 +++ .../src/app/modules/idp/idp-routing.module.ts | 18 + .../src/app/modules/idp/idp.component.html | 68 ++++ .../src/app/modules/idp/idp.component.scss | 64 ++++ .../src/app/modules/idp/idp.component.spec.ts | 25 ++ console/src/app/modules/idp/idp.component.ts | 166 +++++++++ console/src/app/modules/idp/idp.module.ts | 33 ++ .../add-idp-dialog.component.html | 36 ++ .../add-idp-dialog.component.scss | 25 ++ .../add-idp-dialog.component.spec.ts | 26 ++ .../add-idp-dialog.component.ts | 84 +++++ .../add-idp-dialog/add-idp-dialog.module.ts | 24 ++ .../login-policy-routing.module.ts | 20 ++ .../login-policy/login-policy.component.html | 51 +++ .../login-policy/login-policy.component.scss | 85 +++++ .../login-policy.component.spec.ts | 25 ++ .../login-policy/login-policy.component.ts | 179 ++++++++++ .../login-policy/login-policy.module.ts | 36 ++ .../password-age-policy-routing.module.ts | 30 ++ .../password-age-policy.component.html | 48 +++ .../password-age-policy.component.scss} | 0 .../password-age-policy.component.spec.ts | 25 ++ .../password-age-policy.component.ts | 126 +++++++ .../password-age-policy.module.ts} | 10 +- ...ssword-complexity-policy-routing.module.ts | 30 ++ .../password-complexity-policy.component.html | 60 ++++ .../password-complexity-policy.component.scss | 44 +++ ...ssword-complexity-policy.component.spec.ts | 25 ++ .../password-complexity-policy.component.ts | 119 +++++++ .../password-complexity-policy.module.ts | 34 ++ .../password-iam-policy-routing.module.ts | 30 ++ .../password-iam-policy.component.html | 28 ++ .../password-iam-policy.component.scss | 44 +++ .../password-iam-policy.component.spec.ts | 25 ++ .../password-iam-policy.component.ts | 102 ++++++ .../password-iam-policy.module.ts | 34 ++ .../password-lockout-policy-routing.module.ts | 30 ++ .../password-lockout-policy.component.html | 41 +++ .../password-lockout-policy.component.scss | 44 +++ .../password-lockout-policy.component.spec.ts | 25 ++ .../password-lockout-policy.component.ts | 113 ++++++ .../password-lockout-policy.module.ts | 34 ++ .../policies/policy-component-action.enum.ts | 5 + .../policies/policy-component-types.enum.ts | 11 + .../project-members.component.html | 4 +- .../failed-events.component.html | 5 +- .../iam-members/iam-members.component.html | 5 +- .../iam-policy-grid.component.html | 30 ++ .../iam-policy-grid.component.scss | 76 ++++ .../iam-policy-grid.component.spec.ts | 25 ++ .../iam-policy-grid.component.ts | 27 ++ .../src/app/pages/iam/iam-routing.module.ts | 27 ++ .../iam/iam-views/iam-views.component.html | 5 +- console/src/app/pages/iam/iam.component.html | 10 + console/src/app/pages/iam/iam.component.ts | 4 +- console/src/app/pages/iam/iam.module.ts | 8 +- .../orgs/org-create/org-create.component.html | 3 - .../orgs/org-detail/org-detail.component.html | 8 + .../orgs/org-detail/org-detail.component.ts | 4 +- .../org-members/org-members.component.html | 5 +- .../src/app/pages/orgs/orgs-routing.module.ts | 66 +++- console/src/app/pages/orgs/orgs.module.ts | 2 + .../password-policy.component.html | 136 -------- .../password-policy.component.ts | 329 ------------------ .../policy-grid/policy-grid.component.html | 34 +- .../policy-grid/policy-grid.component.scss | 3 +- .../orgs/policy-grid/policy-grid.component.ts | 19 +- .../search-org-autocomplete.component.html | 25 -- .../search-org-autocomplete.component.scss | 3 - .../search-org-autocomplete.component.spec.ts | 27 -- .../search-org-autocomplete.component.ts | 99 ------ .../apps/app-detail/app-detail.component.html | 8 +- .../granted-project-list.component.html | 4 +- .../owned-project-list.component.html | 8 +- .../project-grant-members.component.html | 7 +- .../auth-user-mfa.component.html | 2 +- .../machine-keys/machine-keys.component.html | 6 +- .../membership-detail.component.html | 5 +- .../user-mfa/user-mfa.component.html | 2 +- .../user-table/user-table.component.html | 6 +- console/src/app/services/admin.service.ts | 113 +++++- console/src/app/services/mgmt.service.ts | 115 ++++++ console/src/assets/i18n/de.json | 60 +++- console/src/assets/i18n/en.json | 60 +++- console/src/styles/table.scss | 4 - 96 files changed, 3401 insertions(+), 735 deletions(-) rename console/src/app/{pages/orgs/password-policy/password-policy-routing.module.ts => modules/idp-create/idp-create-routing.module.ts} (64%) create mode 100644 console/src/app/modules/idp-create/idp-create.component.html create mode 100644 console/src/app/modules/idp-create/idp-create.component.scss rename console/src/app/{pages/orgs/password-policy/password-policy.component.spec.ts => modules/idp-create/idp-create.component.spec.ts} (53%) create mode 100644 console/src/app/modules/idp-create/idp-create.component.ts rename console/src/app/{pages/orgs/search-org-autocomplete/search-org-autocomplete.module.ts => modules/idp-create/idp-create.module.ts} (59%) create mode 100644 console/src/app/modules/idp-table/idp-table.component.html create mode 100644 console/src/app/modules/idp-table/idp-table.component.scss create mode 100644 console/src/app/modules/idp-table/idp-table.component.spec.ts create mode 100644 console/src/app/modules/idp-table/idp-table.component.ts create mode 100644 console/src/app/modules/idp-table/idp-table.module.ts create mode 100644 console/src/app/modules/idp/idp-routing.module.ts create mode 100644 console/src/app/modules/idp/idp.component.html create mode 100644 console/src/app/modules/idp/idp.component.scss create mode 100644 console/src/app/modules/idp/idp.component.spec.ts create mode 100644 console/src/app/modules/idp/idp.component.ts create mode 100644 console/src/app/modules/idp/idp.module.ts create mode 100644 console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.html create mode 100644 console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.scss create mode 100644 console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.spec.ts create mode 100644 console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.ts create mode 100644 console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.module.ts create mode 100644 console/src/app/modules/policies/login-policy/login-policy-routing.module.ts create mode 100644 console/src/app/modules/policies/login-policy/login-policy.component.html create mode 100644 console/src/app/modules/policies/login-policy/login-policy.component.scss create mode 100644 console/src/app/modules/policies/login-policy/login-policy.component.spec.ts create mode 100644 console/src/app/modules/policies/login-policy/login-policy.component.ts create mode 100644 console/src/app/modules/policies/login-policy/login-policy.module.ts create mode 100644 console/src/app/modules/policies/password-age-policy/password-age-policy-routing.module.ts create mode 100644 console/src/app/modules/policies/password-age-policy/password-age-policy.component.html rename console/src/app/{pages/orgs/password-policy/password-policy.component.scss => modules/policies/password-age-policy/password-age-policy.component.scss} (100%) create mode 100644 console/src/app/modules/policies/password-age-policy/password-age-policy.component.spec.ts create mode 100644 console/src/app/modules/policies/password-age-policy/password-age-policy.component.ts rename console/src/app/{pages/orgs/password-policy/password-policy.module.ts => modules/policies/password-age-policy/password-age-policy.module.ts} (78%) create mode 100644 console/src/app/modules/policies/password-complexity-policy/password-complexity-policy-routing.module.ts create mode 100644 console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.html create mode 100644 console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.scss create mode 100644 console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.spec.ts create mode 100644 console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.ts create mode 100644 console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.module.ts create mode 100644 console/src/app/modules/policies/password-iam-policy/password-iam-policy-routing.module.ts create mode 100644 console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.html create mode 100644 console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.scss create mode 100644 console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.spec.ts create mode 100644 console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.ts create mode 100644 console/src/app/modules/policies/password-iam-policy/password-iam-policy.module.ts create mode 100644 console/src/app/modules/policies/password-lockout-policy/password-lockout-policy-routing.module.ts create mode 100644 console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html create mode 100644 console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.scss create mode 100644 console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.spec.ts create mode 100644 console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts create mode 100644 console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.module.ts create mode 100644 console/src/app/modules/policies/policy-component-action.enum.ts create mode 100644 console/src/app/modules/policies/policy-component-types.enum.ts create mode 100644 console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.html create mode 100644 console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.scss create mode 100644 console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.spec.ts create mode 100644 console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.ts delete mode 100644 console/src/app/pages/orgs/password-policy/password-policy.component.html delete mode 100644 console/src/app/pages/orgs/password-policy/password-policy.component.ts delete mode 100644 console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.html delete mode 100644 console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.scss delete mode 100644 console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.spec.ts delete mode 100644 console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.ts diff --git a/console/src/app/app.component.scss b/console/src/app/app.component.scss index d79284f33..a5b97c8c3 100644 --- a/console/src/app/app.component.scss +++ b/console/src/app/app.component.scss @@ -127,6 +127,7 @@ display: flex; justify-content: space-between; align-items: center; + font-size: .9rem; .count { font-size: 12px; diff --git a/console/src/app/pages/orgs/password-policy/password-policy-routing.module.ts b/console/src/app/modules/idp-create/idp-create-routing.module.ts similarity index 64% rename from console/src/app/pages/orgs/password-policy/password-policy-routing.module.ts rename to console/src/app/modules/idp-create/idp-create-routing.module.ts index d74cadc6f..41ed91fb9 100644 --- a/console/src/app/pages/orgs/password-policy/password-policy-routing.module.ts +++ b/console/src/app/modules/idp-create/idp-create-routing.module.ts @@ -1,12 +1,12 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { PasswordPolicyComponent } from './password-policy.component'; +import { IdpCreateComponent } from './idp-create.component'; const routes: Routes = [ { path: '', - component: PasswordPolicyComponent, + component: IdpCreateComponent, data: { animation: 'DetailPage' }, }, ]; @@ -15,4 +15,4 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], }) -export class PasswordPolicyRoutingModule { } +export class IdpCreateRoutingModule { } diff --git a/console/src/app/modules/idp-create/idp-create.component.html b/console/src/app/modules/idp-create/idp-create.component.html new file mode 100644 index 000000000..41af3da18 --- /dev/null +++ b/console/src/app/modules/idp-create/idp-create.component.html @@ -0,0 +1,56 @@ +
+
+ + {{ 'IDP.CREATE.TITLE' | translate }}Step + {{ currentCreateStep }} of + {{ createSteps }} +
+ +

{{'IDP.CREATE.TITLE' | translate}}

+

{{'IDP.CREATE.DESCRIPTION' | translate}}

+ +
+ +
+ + {{ 'IDP.NAME' | translate }} + + + + {{ 'IDP.ISSUER' | translate }} + + +
+
+

Client Id / Client Secret

+ + {{ 'IDP.CLIENTID' | translate }} + + + + {{ 'IDP.CLIENTSECRET' | translate }} + + +
+
+ + {{ 'IDP.SCOPESLIST' | translate }} + + + {{scope}} cancel + + + + +
+
+ + +
+
diff --git a/console/src/app/modules/idp-create/idp-create.component.scss b/console/src/app/modules/idp-create/idp-create.component.scss new file mode 100644 index 000000000..677989d7d --- /dev/null +++ b/console/src/app/modules/idp-create/idp-create.component.scss @@ -0,0 +1,64 @@ + +.container { + padding: 4rem 4rem 2rem 4rem; + + @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 { + display: flex; + margin: 0 -.5rem; + flex-wrap: wrap; + + .desc { + flex-basis: 100%; + margin: 0 .5rem; + margin-bottom: 1rem; + color: #8795a1; + } + + .formfield { + flex: 1 0 auto; + margin: 0 .5rem; + + @media only screen and (max-width: 450px) { + flex-basis: 100%; + } + } +} + +.continue-button { + margin-top: 3rem; + display: block; + padding: .5rem 4rem; + border-radius: .5rem; + + @media only screen and (max-width: 450px) { + margin-top: 1rem; + margin-bottom: 2rem; + } +} diff --git a/console/src/app/pages/orgs/password-policy/password-policy.component.spec.ts b/console/src/app/modules/idp-create/idp-create.component.spec.ts similarity index 53% rename from console/src/app/pages/orgs/password-policy/password-policy.component.spec.ts rename to console/src/app/modules/idp-create/idp-create.component.spec.ts index 383adfa04..c37be3b23 100644 --- a/console/src/app/pages/orgs/password-policy/password-policy.component.spec.ts +++ b/console/src/app/modules/idp-create/idp-create.component.spec.ts @@ -1,20 +1,20 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { PasswordPolicyComponent } from './password-policy.component'; +import { IdpCreateComponent } from './idp-create.component'; -describe('PasswordPolicyComponent', () => { - let component: PasswordPolicyComponent; - let fixture: ComponentFixture; +describe('IdpCreateComponent', () => { + let component: IdpCreateComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [PasswordPolicyComponent], + declarations: [IdpCreateComponent], }) .compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(PasswordPolicyComponent); + fixture = TestBed.createComponent(IdpCreateComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/console/src/app/modules/idp-create/idp-create.component.ts b/console/src/app/modules/idp-create/idp-create.component.ts new file mode 100644 index 000000000..90467a2cb --- /dev/null +++ b/console/src/app/modules/idp-create/idp-create.component.ts @@ -0,0 +1,142 @@ +import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes'; +import { Location } from '@angular/common'; +import { Component, Injector, OnDestroy, OnInit, Type } from '@angular/core'; +import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms'; +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 { 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'; + +@Component({ + selector: 'app-idp-create', + templateUrl: './idp-create.component.html', + styleUrls: ['./idp-create.component.scss'], +}) +export class IdpCreateComponent implements OnInit, OnDestroy { + public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT; + private service!: ManagementService | AdminService; + public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE]; + + private subscription?: Subscription; + public projectId: string = ''; + + public formGroup!: FormGroup; + public createSteps: number = 1; + public currentCreateStep: number = 1; + + constructor( + private router: Router, + private route: ActivatedRoute, + private toast: ToastService, + private injector: Injector, + private _location: Location, + ) { + this.formGroup = new FormGroup({ + name: new FormControl('', [Validators.required]), + logoSrc: new FormControl('', []), + clientId: new FormControl('', [Validators.required]), + clientSecret: new FormControl('', [Validators.required]), + issuer: new FormControl('', [Validators.required]), + scopesList: new FormControl([], []), + }); + + this.route.data.pipe(take(1)).subscribe(data => { + this.serviceType = data.serviceType; + switch (this.serviceType) { + case PolicyComponentServiceType.MGMT: + this.service = this.injector.get(ManagementService as Type); + break; + case PolicyComponentServiceType.ADMIN: + this.service = this.injector.get(AdminService as Type); + break; + } + }); + } + + public ngOnInit(): void { + this.subscription = this.route.params.subscribe(params => this.getData(params)); + } + + public ngOnDestroy(): void { + this.subscription?.unsubscribe(); + } + + private getData({ projectid }: Params): void { + this.projectId = projectid; + } + + public addIdp(): void { + const req: OidcIdpConfigCreate = new OidcIdpConfigCreate(); + + req.setName(this.name?.value); + req.setClientId(this.clientId?.value); + req.setClientSecret(this.clientSecret?.value); + req.setIssuer(this.issuer?.value); + req.setLogoSrc(this.logoSrc?.value); + req.setScopesList(this.scopesList?.value); + + this.service.CreateOidcIdp(req).then((idp) => { + this.router.navigate(['idp', idp.getId()]); + }).catch(error => { + this.toast.showError(error); + }); + } + + public close(): void { + this._location.back(); + } + + public addScope(event: MatChipInputEvent): void { + const input = event.input; + const value = event.value.trim(); + + if (value !== '') { + if (this.scopesList?.value) { + this.scopesList.value.push(value); + if (input) { + input.value = ''; + } + } + } + } + + public removeScope(uri: string): void { + if (this.scopesList?.value) { + const index = this.scopesList.value.indexOf(uri); + + if (index !== undefined && index >= 0) { + this.scopesList.value.splice(index, 1); + } + } + } + + public get name(): AbstractControl | null { + return this.formGroup.get('name'); + } + + public get logoSrc(): AbstractControl | null { + return this.formGroup.get('logoSrc'); + } + + public get clientId(): AbstractControl | null { + return this.formGroup.get('clientId'); + } + + public get clientSecret(): AbstractControl | null { + return this.formGroup.get('clientSecret'); + } + + public get issuer(): AbstractControl | null { + return this.formGroup.get('issuer'); + } + + public get scopesList(): AbstractControl | null { + return this.formGroup.get('scopesList'); + } +} diff --git a/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.module.ts b/console/src/app/modules/idp-create/idp-create.module.ts similarity index 59% rename from console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.module.ts rename to console/src/app/modules/idp-create/idp-create.module.ts index 8d8733a7a..d03435a92 100644 --- a/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.module.ts +++ b/console/src/app/modules/idp-create/idp-create.module.ts @@ -1,32 +1,31 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatInputModule } from '@angular/material/input'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { TranslateModule } from '@ngx-translate/core'; -import { SearchOrgAutocompleteComponent } from './search-org-autocomplete.component'; +import { IdpCreateRoutingModule } from './idp-create-routing.module'; +import { IdpCreateComponent } from './idp-create.component'; @NgModule({ - declarations: [SearchOrgAutocompleteComponent], + declarations: [IdpCreateComponent], imports: [ + IdpCreateRoutingModule, CommonModule, - MatAutocompleteModule, - MatChipsModule, - MatButtonModule, - MatFormFieldModule, - MatIconModule, - ReactiveFormsModule, - MatProgressSpinnerModule, FormsModule, + ReactiveFormsModule, + MatInputModule, + MatFormFieldModule, + MatButtonModule, + MatIconModule, + MatChipsModule, + MatTooltipModule, TranslateModule, ], - exports: [ - SearchOrgAutocompleteComponent, - ], }) -export class SearchOrgAutocompleteModule { } +export class IdpCreateModule { } diff --git a/console/src/app/modules/idp-table/idp-table.component.html b/console/src/app/modules/idp-table/idp-table.component.html new file mode 100644 index 000000000..f579dba7e --- /dev/null +++ b/console/src/app/modules/idp-table/idp-table.component.html @@ -0,0 +1,85 @@ + + + + + + + add{{ 'ACTIONS.NEW' | translate }} + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + ipp logo {{idp?.name}} + +
+ {{idp.name.charAt(0)}} +
+
+
+
{{ 'IDP.NAME' | translate }} {{idp?.name}} {{ 'IDP.CONFIG' | translate }} +
+ {{elem.key}}: + {{elem.value}} +
+
{{ 'IDP.STATE' | translate }} {{ 'IDP.STATES.'+idp.state | translate }} {{ 'IDP.CREATIONDATE' | translate }} + {{idp.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} {{ 'IDP.CHANGEDATE' | translate }} + {{idp.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}
+ +
+
\ No newline at end of file diff --git a/console/src/app/modules/idp-table/idp-table.component.scss b/console/src/app/modules/idp-table/idp-table.component.scss new file mode 100644 index 000000000..c3bf36ea6 --- /dev/null +++ b/console/src/app/modules/idp-table/idp-table.component.scss @@ -0,0 +1,77 @@ + +.table-wrapper { + overflow: auto; + + .table, + .paginator { + width: 100%; + + td, + th { + padding: 0 1rem; + + &:first-child { + padding-left: 0; + padding-right: 1rem; + } + + &:last-child { + padding-right: 0; + } + } + + .data-row { + cursor: pointer; + + &:hover { + background-color: #ffffff05; + } + } + } +} + +tr { + outline: none; +} + +.add-button { + border-radius: .5rem; +} + +.avatar { + height: 30px; + width: 30px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: black; + color: white; + + span { + font-size: 14px; + text-transform: uppercase; + } +} + +.flex-row { + display: flex; + justify-content: space-between; + + .key { + margin-right: 1rem; + font-size: 14px; + } + + .value { + font-size: 14px; + } + + &:first-child { + padding-top: .5rem; + } + + &:last-child { + padding-bottom: .5rem; + } +} diff --git a/console/src/app/modules/idp-table/idp-table.component.spec.ts b/console/src/app/modules/idp-table/idp-table.component.spec.ts new file mode 100644 index 000000000..35bf476c5 --- /dev/null +++ b/console/src/app/modules/idp-table/idp-table.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IdpTableComponent } from './idp-table.component'; + +describe('UserTableComponent', () => { + let component: IdpTableComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [IdpTableComponent], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(IdpTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/idp-table/idp-table.component.ts b/console/src/app/modules/idp-table/idp-table.component.ts new file mode 100644 index 000000000..2ba42419f --- /dev/null +++ b/console/src/app/modules/idp-table/idp-table.component.ts @@ -0,0 +1,126 @@ +import { SelectionModel } from '@angular/cdk/collections'; +import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { MatPaginator, PageEvent } from '@angular/material/paginator'; +import { MatTableDataSource } from '@angular/material/table'; +import { RouterLink } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { IdpSearchResponse as AdminIdpSearchResponse, IdpView as AdminIdpView } from 'src/app/proto/generated/admin_pb'; +import { IdpView as MgmtIdpView } 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 '../policies/policy-component-types.enum'; + +@Component({ + selector: 'app-idp-table', + templateUrl: './idp-table.component.html', + styleUrls: ['./idp-table.component.scss'], +}) +export class IdpTableComponent implements OnInit { + @Input() public serviceType!: PolicyComponentServiceType; + @Input() service!: AdminService | ManagementService; + @Input() disabled: boolean = false; + @ViewChild(MatPaginator) public paginator!: MatPaginator; + public dataSource: MatTableDataSource + = new MatTableDataSource(); + public selection: SelectionModel + = new SelectionModel(true, []); + public idpResult!: AdminIdpSearchResponse.AsObject; + private loadingSubject: BehaviorSubject = new BehaviorSubject(false); + public loading$: Observable = this.loadingSubject.asObservable(); + public PolicyComponentServiceType: any = PolicyComponentServiceType; + @Input() public displayedColumns: string[] = ['select', 'name', 'config', 'creationDate', 'changeDate', 'state']; + + @Output() public changedSelection: EventEmitter> + = new EventEmitter(); + + constructor(public translate: TranslateService, private toast: ToastService) { + this.selection.changed.subscribe(() => { + this.changedSelection.emit(this.selection.selected); + }); + } + + ngOnInit(): void { + this.getData(10, 0); + } + + public isAllSelected(): boolean { + const numSelected = this.selection.selected.length; + const numRows = this.dataSource.data.length; + return numSelected === numRows; + } + + public masterToggle(): void { + this.isAllSelected() ? + this.selection.clear() : + this.dataSource.data.forEach(row => this.selection.select(row)); + } + + + public changePage(event: PageEvent): void { + this.getData(event.pageSize, event.pageIndex * event.pageSize); + } + + public deactivateSelectedIdps(): void { + Promise.all(this.selection.selected.map(value => { + return this.service.DeactivateIdpConfig(value.id); + })).then(() => { + this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true); + this.getData(10, 0); + }); + } + + public reactivateSelectedIdps(): void { + Promise.all(this.selection.selected.map(value => { + return this.service.ReactivateIdpConfig(value.id); + })).then(() => { + this.toast.showInfo('USER.TOAST.SELECTEDREACTIVATED', true); + this.getData(10, 0); + }); + } + + public removeSelectedIdps(): void { + Promise.all(this.selection.selected.map(value => { + return this.service.RemoveIdpConfig(value.id); + })).then(() => { + this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true); + this.getData(10, 0); + }); + } + + private async getData(limit: number, offset: number): Promise { + this.loadingSubject.next(true); + + // let query: AdminIdpSearchQuery | MgmtIdpSearchQuery; + // if (this.service instanceof AdminService) { + // query = new AdminIdpSearchQuery(); + // query.setKey(AdminIdpSearchKey.) + // } else if (this.service instanceof ManagementService) { + // return ['/org', 'idp', 'create']; + // } + + this.service.SearchIdps(limit, offset).then(resp => { + this.idpResult = resp.toObject(); + this.dataSource.data = this.idpResult.resultList; + console.log(this.idpResult.resultList); + this.loadingSubject.next(false); + }).catch(error => { + this.toast.showError(error); + this.loadingSubject.next(false); + }); + } + + public refreshPage(): void { + this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize); + } + + public get createRouterLink(): RouterLink | any { + if (this.service instanceof AdminService) { + return ['/iam', 'idp', 'create']; + } else if (this.service instanceof ManagementService) { + return ['/org', 'idp', 'create']; + } + } +} diff --git a/console/src/app/modules/idp-table/idp-table.module.ts b/console/src/app/modules/idp-table/idp-table.module.ts new file mode 100644 index 000000000..2d0ba9d25 --- /dev/null +++ b/console/src/app/modules/idp-table/idp-table.module.ts @@ -0,0 +1,42 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatIconModule } from '@angular/material/icon'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatTableModule } from '@angular/material/table'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; +import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module'; +import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe.module'; +import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe.module'; + +import { IdpTableComponent } from './idp-table.component'; + +@NgModule({ + declarations: [IdpTableComponent], + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatCheckboxModule, + MatIconModule, + MatTooltipModule, + TranslateModule, + LocalizedDatePipeModule, + TimestampToDatePipeModule, + MatTableModule, + MatPaginatorModule, + RouterModule, + RefreshTableModule, + HasRoleModule, + ], + exports: [ + IdpTableComponent, + ], +}) +export class IdpTableModule { } diff --git a/console/src/app/modules/idp/idp-routing.module.ts b/console/src/app/modules/idp/idp-routing.module.ts new file mode 100644 index 000000000..56e99c9a5 --- /dev/null +++ b/console/src/app/modules/idp/idp-routing.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { IdpComponent } from './idp.component'; + +const routes: Routes = [ + { + path: '', + component: IdpComponent, + data: { animation: 'DetailPage' }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class IdpRoutingModule { } diff --git a/console/src/app/modules/idp/idp.component.html b/console/src/app/modules/idp/idp.component.html new file mode 100644 index 000000000..e0f3fe15c --- /dev/null +++ b/console/src/app/modules/idp/idp.component.html @@ -0,0 +1,68 @@ +
+
+ + {{ 'IDP.CREATE.TITLE' | translate }}Step + {{ currentCreateStep }} of + {{ createSteps }} +
+ +

{{'IDP.CREATE.TITLE' | translate}}

+

{{'IDP.CREATE.DESCRIPTION' | translate}}

+ +
+ +
+ + {{ 'IDP.ID' | translate }} + + + + {{ 'IDP.NAME' | translate }} + + + + {{ 'IDP.LOGOSRC' | translate }} + + + + {{ 'IDP.ISSUER' | translate }} + + +
+
+ + Update Client Id / Client Secret + + + + {{ 'IDP.CLIENTID' | translate }} + + + + {{ 'IDP.CLIENTSECRET' | translate }} + + + +
+
+ + {{ 'IDP.SCOPESLIST' | translate }} + + + {{scope}} cancel + + + + +
+
+ + +
+
\ No newline at end of file diff --git a/console/src/app/modules/idp/idp.component.scss b/console/src/app/modules/idp/idp.component.scss new file mode 100644 index 000000000..677989d7d --- /dev/null +++ b/console/src/app/modules/idp/idp.component.scss @@ -0,0 +1,64 @@ + +.container { + padding: 4rem 4rem 2rem 4rem; + + @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 { + display: flex; + margin: 0 -.5rem; + flex-wrap: wrap; + + .desc { + flex-basis: 100%; + margin: 0 .5rem; + margin-bottom: 1rem; + color: #8795a1; + } + + .formfield { + flex: 1 0 auto; + margin: 0 .5rem; + + @media only screen and (max-width: 450px) { + flex-basis: 100%; + } + } +} + +.continue-button { + margin-top: 3rem; + display: block; + padding: .5rem 4rem; + border-radius: .5rem; + + @media only screen and (max-width: 450px) { + margin-top: 1rem; + margin-bottom: 2rem; + } +} diff --git a/console/src/app/modules/idp/idp.component.spec.ts b/console/src/app/modules/idp/idp.component.spec.ts new file mode 100644 index 000000000..cd5c8064e --- /dev/null +++ b/console/src/app/modules/idp/idp.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IdpComponent } from './idp.component'; + +describe('IdComponent', () => { + let component: IdpComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [IdpComponent], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(IdpComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/idp/idp.component.ts b/console/src/app/modules/idp/idp.component.ts new file mode 100644 index 000000000..638c7512e --- /dev/null +++ b/console/src/app/modules/idp/idp.component.ts @@ -0,0 +1,166 @@ +import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes'; +import { Location } from '@angular/common'; +import { Component, Injector, 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 { 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'; + +@Component({ + selector: 'app-idp', + templateUrl: './idp.component.html', + styleUrls: ['./idp.component.scss'], +}) +export class IdpComponent implements OnInit, OnDestroy { + public showIdSecretSection: boolean = false; + public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT; + private service!: ManagementService | AdminService; + public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE]; + + private subscription?: Subscription; + public projectId: string = ''; + + public formGroup!: FormGroup; + public createSteps: number = 1; + public currentCreateStep: number = 1; + + constructor( + // private router: Router, + private toast: ToastService, + private injector: Injector, + private route: ActivatedRoute, + private _location: Location, + ) { + this.formGroup = new FormGroup({ + id: new FormControl({ disabled: true, value: '' }, [Validators.required]), + name: new FormControl({ disabled: true, value: '' }, [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.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); + break; + case PolicyComponentServiceType.ADMIN: + this.service = this.injector.get(AdminService as Type); + break; + } + + return this.route.params.pipe(take(1)); + })).subscribe((params) => { + const { id } = params; + if (id) { + this.service.IdpByID(id).then(idp => { + this.formGroup.patchValue(idp.toObject()); + }); + } + }); + } + + public ngOnInit(): void { + this.subscription = this.route.params.subscribe(params => this.getData(params)); + } + + public ngOnDestroy(): void { + this.subscription?.unsubscribe(); + } + + private getData({ projectid }: Params): void { + this.projectId = projectid; + } + + public updateIdp(): void { + let req: AdminOidcIdpConfigUpdate | MgmtOidcIdpConfigUpdate; + + switch (this.serviceType) { + case PolicyComponentServiceType.MGMT: + req = new MgmtOidcIdpConfigUpdate(); + break; + case PolicyComponentServiceType.ADMIN: + req = new AdminOidcIdpConfigUpdate(); + break; + } + + req.setClientId(this.clientId?.value); + req.setClientSecret(this.clientSecret?.value); + req.setIssuer(this.issuer?.value); + req.setScopesList(this.scopesList?.value); + + this.service.UpdateOidcIdpConfig(req).then((idp) => { + // this.router.navigate(['idp', ]); + }).catch(error => { + this.toast.showError(error); + }); + } + + public close(): void { + this._location.back(); + } + + public addScope(event: MatChipInputEvent): void { + const input = event.input; + const value = event.value.trim(); + + if (value !== '') { + if (this.scopesList?.value) { + this.scopesList.value.push(value); + if (input) { + input.value = ''; + } + } + } + } + + public removeScope(uri: string): void { + if (this.scopesList?.value) { + const index = this.scopesList.value.indexOf(uri); + + if (index !== undefined && index >= 0) { + this.scopesList.value.splice(index, 1); + } + } + } + + public get id(): AbstractControl | null { + return this.formGroup.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'); + } + + public get logoSrc(): AbstractControl | null { + return this.formGroup.get('logoSrc'); + } + + public get issuer(): AbstractControl | null { + return this.formGroup.get('issuer'); + } + + public get scopesList(): AbstractControl | null { + return this.formGroup.get('scopesList'); + } +} diff --git a/console/src/app/modules/idp/idp.module.ts b/console/src/app/modules/idp/idp.module.ts new file mode 100644 index 000000000..b6c9dfca2 --- /dev/null +++ b/console/src/app/modules/idp/idp.module.ts @@ -0,0 +1,33 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { TranslateModule } from '@ngx-translate/core'; + +import { IdpRoutingModule } from './idp-routing.module'; +import { IdpComponent } from './idp.component'; + +@NgModule({ + declarations: [IdpComponent], + imports: [ + CommonModule, + IdpRoutingModule, + FormsModule, + ReactiveFormsModule, + MatInputModule, + MatFormFieldModule, + MatButtonModule, + MatIconModule, + MatTooltipModule, + TranslateModule, + MatCheckboxModule, + MatChipsModule, + ], +}) +export class IdpModule { } diff --git a/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.html b/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.html new file mode 100644 index 000000000..719b94e64 --- /dev/null +++ b/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.html @@ -0,0 +1,36 @@ +

+ {{'LOGINPOLICY.ADDIDP.TITLE' | translate}} +

+

{{'LOGINPOLICY.ADDIDP.DESCRIPTION' | translate}}

+ +
+ + {{ 'IDP.TYPE' | translate }} + + + {{ 'IDP.TYPES.'+type | translate}} + + + + +

{{'LOGINPOLICY.ADDIDP.SELECTIDPS' | translate}}

+ + + {{ 'LOGINPOLICY.ADDIDP.SELECTIDPS' | translate }} + + + {{ idp.name }} + + + +
+
+ + + +
\ No newline at end of file diff --git a/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.scss b/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.scss new file mode 100644 index 000000000..deaa9f79e --- /dev/null +++ b/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.scss @@ -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; + } +} diff --git a/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.spec.ts b/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.spec.ts new file mode 100644 index 000000000..0c7885d56 --- /dev/null +++ b/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [AddIdpDialogComponent], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AddIdpDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.ts b/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.ts new file mode 100644 index 000000000..107a69d90 --- /dev/null +++ b/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.component.ts @@ -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 | string[] = []; + public IdpProviderType: any = IdpProviderType; + + constructor( + private mgmtService: ManagementService, + private adminService: AdminService, + public dialogRef: MatDialogRef, + @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, + }); + } +} diff --git a/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.module.ts b/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.module.ts new file mode 100644 index 000000000..95eba2f7d --- /dev/null +++ b/console/src/app/modules/policies/login-policy/add-idp-dialog/add-idp-dialog.module.ts @@ -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 { } diff --git a/console/src/app/modules/policies/login-policy/login-policy-routing.module.ts b/console/src/app/modules/policies/login-policy/login-policy-routing.module.ts new file mode 100644 index 000000000..63ebc2137 --- /dev/null +++ b/console/src/app/modules/policies/login-policy/login-policy-routing.module.ts @@ -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 { } diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.html b/console/src/app/modules/policies/login-policy/login-policy.component.html new file mode 100644 index 000000000..df5b38a2a --- /dev/null +++ b/console/src/app/modules/policies/login-policy/login-policy.component.html @@ -0,0 +1,51 @@ + + + + + +
+
+ {{'ORG.POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}} + + + +
+
+ {{'ORG.POLICY.DATA.ALLOWREGISTER' | translate}} + + + +
+
+ {{'ORG.POLICY.DATA.ALLOWEXTERNALIDP' | translate}} + + + +
+
+ +

{{'LOGINPOLICY.IDPS' | translate}}

+ +
+
+ remove_circle +

{{idp.name}}

+ {{idp.type}} + {{idp.configId}} +
+
+ add +
+
+ +
+ +
+
\ No newline at end of file diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.scss b/console/src/app/modules/policies/login-policy/login-policy.component.scss new file mode 100644 index 000000000..dbd54a377 --- /dev/null +++ b/console/src/app/modules/policies/login-policy/login-policy.component.scss @@ -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; + } + } +} diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.spec.ts b/console/src/app/modules/policies/login-policy/login-policy.component.spec.ts new file mode 100644 index 000000000..635018e4a --- /dev/null +++ b/console/src/app/modules/policies/login-policy/login-policy.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [LoginPolicyComponent], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginPolicyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.ts b/console/src/app/modules/policies/login-policy/login-policy.component.ts new file mode 100644 index 000000000..47600ade5 --- /dev/null +++ b/console/src/app/modules/policies/login-policy/login-policy.component.ts @@ -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); + break; + case PolicyComponentServiceType.ADMIN: + this.service = this.injector.get(AdminService as Type); + 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 { + 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 { + 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 { + 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 { + 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 { + 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; + } + } +} diff --git a/console/src/app/modules/policies/login-policy/login-policy.module.ts b/console/src/app/modules/policies/login-policy/login-policy.module.ts new file mode 100644 index 000000000..cf8119b90 --- /dev/null +++ b/console/src/app/modules/policies/login-policy/login-policy.module.ts @@ -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 { } diff --git a/console/src/app/modules/policies/password-age-policy/password-age-policy-routing.module.ts b/console/src/app/modules/policies/password-age-policy/password-age-policy-routing.module.ts new file mode 100644 index 000000000..3e39b87b4 --- /dev/null +++ b/console/src/app/modules/policies/password-age-policy/password-age-policy-routing.module.ts @@ -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 { } diff --git a/console/src/app/modules/policies/password-age-policy/password-age-policy.component.html b/console/src/app/modules/policies/password-age-policy/password-age-policy.component.html new file mode 100644 index 000000000..0c30ebe6b --- /dev/null +++ b/console/src/app/modules/policies/password-age-policy/password-age-policy.component.html @@ -0,0 +1,48 @@ + + + + + +
+ + {{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }} + + +
+ {{'ORG.POLICY.DATA.EXPIREWARNDAYS' | translate}} + +
+ + {{ageData?.expireWarnDays}} + +
+
+ +
+ {{'ORG.POLICY.DATA.MAXAGEDAYS' | translate}} + +
+ + {{ageData?.maxAgeDays}} + +
+
+
+ +
+ +
+
\ No newline at end of file diff --git a/console/src/app/pages/orgs/password-policy/password-policy.component.scss b/console/src/app/modules/policies/password-age-policy/password-age-policy.component.scss similarity index 100% rename from console/src/app/pages/orgs/password-policy/password-policy.component.scss rename to console/src/app/modules/policies/password-age-policy/password-age-policy.component.scss diff --git a/console/src/app/modules/policies/password-age-policy/password-age-policy.component.spec.ts b/console/src/app/modules/policies/password-age-policy/password-age-policy.component.spec.ts new file mode 100644 index 000000000..27b9cae99 --- /dev/null +++ b/console/src/app/modules/policies/password-age-policy/password-age-policy.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [PasswordAgePolicyComponent], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordAgePolicyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/policies/password-age-policy/password-age-policy.component.ts b/console/src/app/modules/policies/password-age-policy/password-age-policy.component.ts new file mode 100644 index 000000000..edf5c3e2f --- /dev/null +++ b/console/src/app/modules/policies/password-age-policy/password-age-policy.component.ts @@ -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 { + 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); + }); + } + } +} diff --git a/console/src/app/pages/orgs/password-policy/password-policy.module.ts b/console/src/app/modules/policies/password-age-policy/password-age-policy.module.ts similarity index 78% rename from console/src/app/pages/orgs/password-policy/password-policy.module.ts rename to console/src/app/modules/policies/password-age-policy/password-age-policy.module.ts index 3ea94e13e..b1451d50d 100644 --- a/console/src/app/pages/orgs/password-policy/password-policy.module.ts +++ b/console/src/app/modules/policies/password-age-policy/password-age-policy.module.ts @@ -11,13 +11,13 @@ 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 { PasswordPolicyRoutingModule } from './password-policy-routing.module'; -import { PasswordPolicyComponent } from './password-policy.component'; +import { PasswordAgePolicyRoutingModule } from './password-age-policy-routing.module'; +import { PasswordAgePolicyComponent } from './password-age-policy.component'; @NgModule({ - declarations: [PasswordPolicyComponent], + declarations: [PasswordAgePolicyComponent], imports: [ - PasswordPolicyRoutingModule, + PasswordAgePolicyRoutingModule, CommonModule, FormsModule, MatInputModule, @@ -31,4 +31,4 @@ import { PasswordPolicyComponent } from './password-policy.component'; DetailLayoutModule, ], }) -export class PasswordPolicyModule { } +export class PasswordAgePolicyModule { } diff --git a/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy-routing.module.ts b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy-routing.module.ts new file mode 100644 index 000000000..65157f3e4 --- /dev/null +++ b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy-routing.module.ts @@ -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 { } diff --git a/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.html b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.html new file mode 100644 index 000000000..8402e9ec1 --- /dev/null +++ b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.html @@ -0,0 +1,60 @@ + + + + + +
+ + {{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }} + + +
+ {{'ORG.POLICY.DATA.MINLENGTH' | translate}} + +
+ + {{complexityData?.minLength}} + +
+
+
+ {{'ORG.POLICY.DATA.HASNUMBER' | translate}} + + + +
+
+ {{'ORG.POLICY.DATA.HASSYMBOL' | translate}} + + + +
+
+ {{'ORG.POLICY.DATA.HASLOWERCASE' | translate}} + + + +
+
+ {{'ORG.POLICY.DATA.HASUPPERCASE' | translate}} + + + +
+
+ +
+ +
+
\ No newline at end of file diff --git a/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.scss b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.scss new file mode 100644 index 000000000..3884c0cd5 --- /dev/null +++ b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.scss @@ -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; + } +} diff --git a/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.spec.ts b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.spec.ts new file mode 100644 index 000000000..41a1061a6 --- /dev/null +++ b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [PasswordComplexityPolicyComponent], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordComplexityPolicyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.ts b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.ts new file mode 100644 index 000000000..4cc5c6c47 --- /dev/null +++ b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.component.ts @@ -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 { + 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); + }); + } + } +} diff --git a/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.module.ts b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.module.ts new file mode 100644 index 000000000..82da4dd1f --- /dev/null +++ b/console/src/app/modules/policies/password-complexity-policy/password-complexity-policy.module.ts @@ -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 { } diff --git a/console/src/app/modules/policies/password-iam-policy/password-iam-policy-routing.module.ts b/console/src/app/modules/policies/password-iam-policy/password-iam-policy-routing.module.ts new file mode 100644 index 000000000..c0c86d4d2 --- /dev/null +++ b/console/src/app/modules/policies/password-iam-policy/password-iam-policy-routing.module.ts @@ -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 { } diff --git a/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.html b/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.html new file mode 100644 index 000000000..5ef10571f --- /dev/null +++ b/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.html @@ -0,0 +1,28 @@ + + + +
+ + {{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }} + + +
+ {{'ORG.POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate}} + + + +
+
+ +
+ +
+
\ No newline at end of file diff --git a/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.scss b/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.scss new file mode 100644 index 000000000..3884c0cd5 --- /dev/null +++ b/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.scss @@ -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; + } +} diff --git a/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.spec.ts b/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.spec.ts new file mode 100644 index 000000000..455f9fe36 --- /dev/null +++ b/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [PasswordIamPolicyComponent], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordIamPolicyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.ts b/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.ts new file mode 100644 index 000000000..cf8eb9164 --- /dev/null +++ b/console/src/app/modules/policies/password-iam-policy/password-iam-policy.component.ts @@ -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 { + + 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); + }); + } + } + } +} diff --git a/console/src/app/modules/policies/password-iam-policy/password-iam-policy.module.ts b/console/src/app/modules/policies/password-iam-policy/password-iam-policy.module.ts new file mode 100644 index 000000000..1bf5b378e --- /dev/null +++ b/console/src/app/modules/policies/password-iam-policy/password-iam-policy.module.ts @@ -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 { } diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy-routing.module.ts b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy-routing.module.ts new file mode 100644 index 000000000..61e1cfdec --- /dev/null +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy-routing.module.ts @@ -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 { } diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html new file mode 100644 index 000000000..af366cf47 --- /dev/null +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html @@ -0,0 +1,41 @@ + + + + + +
+ + {{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }} + + +
+ {{'ORG.POLICY.DATA.MAXATTEMPTS' | translate}} + +
+ + {{lockoutData?.maxAttempts}} + +
+
+
+ {{'ORG.POLICY.DATA.SHOWLOCKOUTFAILURES' | translate}} + + + +
+
+ +
+ +
+
\ No newline at end of file diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.scss b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.scss new file mode 100644 index 000000000..3884c0cd5 --- /dev/null +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.scss @@ -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; + } +} diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.spec.ts b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.spec.ts new file mode 100644 index 000000000..02fac869f --- /dev/null +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [PasswordLockoutPolicyComponent], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordLockoutPolicyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts new file mode 100644 index 000000000..61a63833e --- /dev/null +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts @@ -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 { + + 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); + }); + } + } +} diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.module.ts b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.module.ts new file mode 100644 index 000000000..fb0872349 --- /dev/null +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.module.ts @@ -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 { } diff --git a/console/src/app/modules/policies/policy-component-action.enum.ts b/console/src/app/modules/policies/policy-component-action.enum.ts new file mode 100644 index 000000000..9e5b4835c --- /dev/null +++ b/console/src/app/modules/policies/policy-component-action.enum.ts @@ -0,0 +1,5 @@ + +export enum PolicyComponentAction { + CREATE = 'create', + MODIFY = 'modify', +} diff --git a/console/src/app/modules/policies/policy-component-types.enum.ts b/console/src/app/modules/policies/policy-component-types.enum.ts new file mode 100644 index 000000000..dc8a2b567 --- /dev/null +++ b/console/src/app/modules/policies/policy-component-types.enum.ts @@ -0,0 +1,11 @@ +export enum PolicyComponentType { + LOCKOUT = 'lockout', + AGE = 'age', + COMPLEXITY = 'complexity', + IAM = 'iam', + LOGIN = 'login', +} +export enum PolicyComponentServiceType { + MGMT = 'mgmt', + ADMIN = 'admin', +} diff --git a/console/src/app/modules/project-members/project-members.component.html b/console/src/app/modules/project-members/project-members.component.html index e15d6566a..742237293 100644 --- a/console/src/app/modules/project-members/project-members.component.html +++ b/console/src/app/modules/project-members/project-members.component.html @@ -20,7 +20,7 @@
- +
-
diff --git a/console/src/app/pages/iam/failed-events/failed-events.component.html b/console/src/app/pages/iam/failed-events/failed-events.component.html index 1431264b2..fcb8c8ce2 100644 --- a/console/src/app/pages/iam/failed-events/failed-events.component.html +++ b/console/src/app/pages/iam/failed-events/failed-events.component.html @@ -2,7 +2,7 @@ - +
@@ -47,8 +47,7 @@
{{ 'IAM.FAILEDEVENTS.VIEWNAME' | translate }} {{event.viewName}}
- +
\ No newline at end of file diff --git a/console/src/app/pages/iam/iam-members/iam-members.component.html b/console/src/app/pages/iam/iam-members/iam-members.component.html index 82a561457..3ce09d605 100644 --- a/console/src/app/pages/iam/iam-members/iam-members.component.html +++ b/console/src/app/pages/iam/iam-members/iam-members.component.html @@ -19,7 +19,7 @@
- +
- +
diff --git a/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.html b/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.html new file mode 100644 index 000000000..9c9bd23da --- /dev/null +++ b/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.html @@ -0,0 +1,30 @@ +

{{'ORG.POLICY.TITLE' | translate}}

+ +

{{'ORG.POLICY.DESCRIPTION' | translate}}

+ +
+ +
+
+ +
+
+ {{'ORG.POLICY.LOGIN_POLICY.TITLE' | translate}} + +
+ +

+ {{'ORG.POLICY.LOGIN_POLICY.DESCRIPTION' | translate}}

+ + +
+ + + +
+
+
+
\ No newline at end of file diff --git a/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.scss b/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.scss new file mode 100644 index 000000000..39a8f5e65 --- /dev/null +++ b/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.scss @@ -0,0 +1,76 @@ +h1 { + font-size: 1.2rem; +} + +.top-desc { + color: #8795a1; +} + +.row-lyt { + display: flex; + flex-wrap: wrap; + margin: 0 -1rem; + + .p-item { + flex-basis: 300px; + margin: 1rem; + display: flex; + flex-direction: column; + min-height: 200px; + padding: 1rem; + + @media only screen and (max-width: 450px) { + flex-basis: 100%; + } + + .avatar { + height: 60px; + width: 60px; + border-radius: 50%; + background: linear-gradient(40deg, rgb(129, 85, 185) 30%, #8983f7); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: .5rem; + + .icon, + i { + font-size: 2.5rem; + height: 2.5rem; + line-height: 2.5rem; + } + } + + .title { + display: flex; + align-items: center; + + span { + font-size: 1.2rem; + } + + .icon { + margin-left: 1rem; + margin-right: 1rem; + } + } + + .desc { + font-size: .9rem; + color: #8795a1; + } + + .fill-space { + flex: 1; + } + + .btn-wrapper { + display: flex; + } + + button { + margin-right: 1rem; + border-radius: .5rem; + } + } +} diff --git a/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.spec.ts b/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.spec.ts new file mode 100644 index 000000000..c174486a8 --- /dev/null +++ b/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IamPolicyGridComponent } from './iam-policy-grid.component'; + +describe('IamPolicyGridComponent', () => { + let component: IamPolicyGridComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [IamPolicyGridComponent], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(IamPolicyGridComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.ts b/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.ts new file mode 100644 index 000000000..299495bc9 --- /dev/null +++ b/console/src/app/pages/iam/iam-policy-grid/iam-policy-grid.component.ts @@ -0,0 +1,27 @@ +import { Component } from '@angular/core'; +import { PolicyComponentType } from 'src/app/modules/policies/policy-component-types.enum'; +import { DefaultLoginPolicy } from 'src/app/proto/generated/admin_pb'; +import { PolicyState } from 'src/app/proto/generated/management_pb'; +import { AdminService } from 'src/app/services/admin.service'; + +@Component({ + selector: 'app-iam-policy-grid', + templateUrl: './iam-policy-grid.component.html', + styleUrls: ['./iam-policy-grid.component.scss'], +}) +export class IamPolicyGridComponent { + public loginPolicy!: DefaultLoginPolicy.AsObject; + + public PolicyState: any = PolicyState; + public PolicyComponentType: any = PolicyComponentType; + + constructor( + private adminService: AdminService, + ) { + this.getData(); + } + + private getData(): void { + this.adminService.GetDefaultLoginPolicy().then(data => this.loginPolicy = data.toObject()); + } +} diff --git a/console/src/app/pages/iam/iam-routing.module.ts b/console/src/app/pages/iam/iam-routing.module.ts index 416c57391..2607459d1 100644 --- a/console/src/app/pages/iam/iam-routing.module.ts +++ b/console/src/app/pages/iam/iam-routing.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AuthGuard } from 'src/app/guards/auth.guard'; import { RoleGuard } from 'src/app/guards/role.guard'; +import { PolicyComponentServiceType, PolicyComponentType } from 'src/app/modules/policies/policy-component-types.enum'; import { IamComponent } from './iam.component'; @@ -22,6 +23,32 @@ const routes: Routes = [ roles: ['iam.member.read'], }, }, + { + path: 'idp/create', + loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then(m => m.IdpCreateModule), + canActivate: [AuthGuard, RoleGuard], + data: { + roles: ['iam.idp.write'], + serviceType: PolicyComponentServiceType.ADMIN, + }, + }, + { + path: 'idp/:id', + loadChildren: () => import('src/app/modules/idp/idp.module').then(m => m.IdpModule), + canActivate: [AuthGuard, RoleGuard], + data: { + roles: ['iam.idp.read'], + serviceType: PolicyComponentServiceType.ADMIN, + }, + }, + { + path: `policy/${PolicyComponentType.LOGIN}`, + data: { + serviceType: PolicyComponentServiceType.ADMIN, + }, + loadChildren: () => import('src/app/modules/policies/login-policy/login-policy.module') + .then(m => m.LoginPolicyModule), + }, ]; @NgModule({ diff --git a/console/src/app/pages/iam/iam-views/iam-views.component.html b/console/src/app/pages/iam/iam-views/iam-views.component.html index b059adc67..8a45da0af 100644 --- a/console/src/app/pages/iam/iam-views/iam-views.component.html +++ b/console/src/app/pages/iam/iam-views/iam-views.component.html @@ -2,7 +2,7 @@ - +
@@ -40,8 +40,7 @@
{{ 'IAM.VIEWS.VIEWNAME' | translate }} {{view.viewName}}
- +
\ No newline at end of file diff --git a/console/src/app/pages/iam/iam.component.html b/console/src/app/pages/iam/iam.component.html index 346db849a..e4bfd0666 100644 --- a/console/src/app/pages/iam/iam.component.html +++ b/console/src/app/pages/iam/iam.component.html @@ -3,6 +3,16 @@

{{'IAM.DETAIL.TITLE' | translate}}

{{'IAM.DETAIL.DESCRIPTION' | translate}}

+ + + + + + + + + diff --git a/console/src/app/pages/iam/iam.component.ts b/console/src/app/pages/iam/iam.component.ts index 18b880561..5a7e26bbe 100644 --- a/console/src/app/pages/iam/iam.component.ts +++ b/console/src/app/pages/iam/iam.component.ts @@ -4,6 +4,7 @@ import { Router } from '@angular/router'; import { BehaviorSubject, from, Observable, of } from 'rxjs'; import { catchError, finalize, map } from 'rxjs/operators'; import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component'; +import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum'; import { OrgMemberView, UserView } from 'src/app/proto/generated/management_pb'; import { AdminService } from 'src/app/services/admin.service'; import { ToastService } from 'src/app/services/toast.service'; @@ -14,13 +15,14 @@ import { ToastService } from 'src/app/services/toast.service'; styleUrls: ['./iam.component.scss'], }) export class IamComponent { + public PolicyComponentServiceType: any = PolicyComponentServiceType; private loadingSubject: BehaviorSubject = new BehaviorSubject(false); public loading$: Observable = this.loadingSubject.asObservable(); public totalMemberResult: number = 0; public membersSubject: BehaviorSubject = new BehaviorSubject([]); - constructor(private adminService: AdminService, private dialog: MatDialog, private toast: ToastService, + constructor(public adminService: AdminService, private dialog: MatDialog, private toast: ToastService, private router: Router) { this.loadMembers(); } diff --git a/console/src/app/pages/iam/iam.module.ts b/console/src/app/pages/iam/iam.module.ts index 54e427e25..177ed8f89 100644 --- a/console/src/app/pages/iam/iam.module.ts +++ b/console/src/app/pages/iam/iam.module.ts @@ -22,20 +22,23 @@ import { ContributorsModule } from 'src/app/modules/contributors/contributors.mo import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module'; import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module'; import { SharedModule } from 'src/app/modules/shared/shared.module'; +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'; import { IamViewsComponent } from './iam-views/iam-views.component'; import { IamComponent } from './iam.component'; - @NgModule({ - declarations: [IamComponent, IamViewsComponent, FailedEventsComponent], + declarations: [IamComponent, IamViewsComponent, FailedEventsComponent, IamPolicyGridComponent], imports: [ CommonModule, IamRoutingModule, + IdpTableModule, ChangesModule, CardModule, MatAutocompleteModule, @@ -61,6 +64,7 @@ import { IamComponent } from './iam.component'; TimestampToDatePipeModule, SharedModule, RefreshTableModule, + HasRolePipeModule, ], }) export class IamModule { } diff --git a/console/src/app/pages/orgs/org-create/org-create.component.html b/console/src/app/pages/orgs/org-create/org-create.component.html index 64fbe4c61..9802a7b70 100644 --- a/console/src/app/pages/orgs/org-create/org-create.component.html +++ b/console/src/app/pages/orgs/org-create/org-create.component.html @@ -3,9 +3,6 @@ - {{ 'ORG.PAGES.CREATE' | translate }}Step - {{ currentCreateStep }} of - {{ createSteps }} diff --git a/console/src/app/pages/orgs/org-detail/org-detail.component.html b/console/src/app/pages/orgs/org-detail/org-detail.component.html index ff9309b24..8bf43f750 100644 --- a/console/src/app/pages/orgs/org-detail/org-detail.component.html +++ b/console/src/app/pages/orgs/org-detail/org-detail.component.html @@ -27,6 +27,14 @@ + + + + + + + diff --git a/console/src/app/pages/orgs/org-detail/org-detail.component.ts b/console/src/app/pages/orgs/org-detail/org-detail.component.ts index 247998b4d..7983c2370 100644 --- a/console/src/app/pages/orgs/org-detail/org-detail.component.ts +++ b/console/src/app/pages/orgs/org-detail/org-detail.component.ts @@ -9,6 +9,7 @@ import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs'; import { catchError, finalize, map } from 'rxjs/operators'; import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component'; import { ChangeType } from 'src/app/modules/changes/changes.component'; +import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum'; import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component'; import { Org, @@ -33,6 +34,7 @@ import { DomainVerificationComponent } from './domain-verification/domain-verifi }) export class OrgDetailComponent implements OnInit, OnDestroy { public org!: Org.AsObject; + public PolicyComponentServiceType: any = PolicyComponentServiceType; public dataSource: MatTableDataSource = new MatTableDataSource(); public memberResult!: OrgMemberSearchResponse.AsObject; @@ -56,7 +58,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy { constructor( private dialog: MatDialog, public translate: TranslateService, - private mgmtService: ManagementService, + public mgmtService: ManagementService, private toast: ToastService, private router: Router, ) { } diff --git a/console/src/app/pages/orgs/org-members/org-members.component.html b/console/src/app/pages/orgs/org-members/org-members.component.html index 7c6eb5342..87f1d1e8a 100644 --- a/console/src/app/pages/orgs/org-members/org-members.component.html +++ b/console/src/app/pages/orgs/org-members/org-members.component.html @@ -16,7 +16,7 @@
- +
- +
diff --git a/console/src/app/pages/orgs/orgs-routing.module.ts b/console/src/app/pages/orgs/orgs-routing.module.ts index 94d21e1e7..ddef73aa8 100644 --- a/console/src/app/pages/orgs/orgs-routing.module.ts +++ b/console/src/app/pages/orgs/orgs-routing.module.ts @@ -1,11 +1,11 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { RoleGuard } from 'src/app/guards/role.guard'; +import { PolicyComponentServiceType, PolicyComponentType } from 'src/app/modules/policies/policy-component-types.enum'; import { OrgCreateComponent } from './org-create/org-create.component'; import { OrgDetailComponent } from './org-detail/org-detail.component'; import { OrgGridComponent } from './org-grid/org-grid.component'; -import { PasswordPolicyComponent, PolicyComponentAction } from './password-policy/password-policy.component'; const routes: Routes = [ { @@ -18,20 +18,60 @@ const routes: Routes = [ loadChildren: () => import('./org-create/org-create.module').then(m => m.OrgCreateModule), }, { - path: 'policy/:policytype/create', - component: PasswordPolicyComponent, - data: { - action: PolicyComponentAction.CREATE, - }, + path: 'idp', + children: [ + { + path: 'create', + loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then(m => m.IdpCreateModule), + canActivate: [RoleGuard], + data: { + roles: ['org.idp.write'], + }, + }, + { + path: ':id', + loadChildren: () => import('src/app/modules/idp/idp.module').then(m => m.IdpModule), + canActivate: [RoleGuard], + data: { + roles: ['iam.idp.read'], + serviceType: PolicyComponentServiceType.ADMIN, + }, + }, + ], }, - /// TODO: add roleguard for iam policy + { - path: 'policy/:policytype', - component: PasswordPolicyComponent, - data: { - action: PolicyComponentAction.MODIFY, - }, - loadChildren: () => import('./password-policy/password-policy.module').then(m => m.PasswordPolicyModule), + path: 'policy', + children: [ + { + path: PolicyComponentType.AGE, + loadChildren: () => import('src/app/modules/policies/password-age-policy/password-age-policy.module') + .then(m => m.PasswordAgePolicyModule), + }, + { + path: PolicyComponentType.LOCKOUT, + loadChildren: () => import('src/app/modules/policies/password-lockout-policy/password-lockout-policy.module') + .then(m => m.PasswordLockoutPolicyModule), + }, + { + path: PolicyComponentType.COMPLEXITY, + loadChildren: () => import('src/app/modules/policies/password-complexity-policy/password-complexity-policy.module') + .then(m => m.PasswordComplexityPolicyModule), + }, + { + path: PolicyComponentType.IAM, + loadChildren: () => import('src/app/modules/policies/password-iam-policy/password-iam-policy.module') + .then(m => m.PasswordIamPolicyModule), + }, + { + path: PolicyComponentType.LOGIN, + data: { + serviceType: PolicyComponentServiceType.MGMT, + }, + loadChildren: () => import('src/app/modules/policies/login-policy/login-policy.module') + .then(m => m.LoginPolicyModule), + }, + ], }, { path: 'members', diff --git a/console/src/app/pages/orgs/orgs.module.ts b/console/src/app/pages/orgs/orgs.module.ts index bce4e20ab..ec6a710ef 100644 --- a/console/src/app/pages/orgs/orgs.module.ts +++ b/console/src/app/pages/orgs/orgs.module.ts @@ -16,6 +16,7 @@ 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'; @@ -40,6 +41,7 @@ import { PolicyGridComponent } from './policy-grid/policy-grid.component'; MatFormFieldModule, MatInputModule, MatButtonModule, + IdpTableModule, MatDialogModule, CardModule, MatIconModule, diff --git a/console/src/app/pages/orgs/password-policy/password-policy.component.html b/console/src/app/pages/orgs/password-policy/password-policy.component.html deleted file mode 100644 index 84ce2a04c..000000000 --- a/console/src/app/pages/orgs/password-policy/password-policy.component.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - -
- - {{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }} - - -
- {{'ORG.POLICY.DATA.MAXATTEMPTS' | translate}} - -
- - {{lockoutData?.maxAttempts}} - -
-
-
- {{'ORG.POLICY.DATA.SHOWLOCKOUTFAILURES' | translate}} - - - -
-
- -
- - {{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }} - - -
- {{'ORG.POLICY.DATA.MINLENGTH' | translate}} - -
- - {{complexityData?.minLength}} - -
-
-
- {{'ORG.POLICY.DATA.HASNUMBER' | translate}} - - - -
-
- {{'ORG.POLICY.DATA.HASSYMBOL' | translate}} - - - -
-
- {{'ORG.POLICY.DATA.HASLOWERCASE' | translate}} - - - -
-
- {{'ORG.POLICY.DATA.HASUPPERCASE' | translate}} - - - -
-
- -
- - {{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }} - - -
- {{'ORG.POLICY.DATA.EXPIREWARNDAYS' | translate}} - -
- - {{ageData?.expireWarnDays}} - -
-
- -
- {{'ORG.POLICY.DATA.MAXAGEDAYS' | translate}} - -
- - {{ageData?.maxAgeDays}} - -
-
-
- -
- - {{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }} - - -
- {{'ORG.POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate}} - - - -
-
- -
- -
-
\ No newline at end of file diff --git a/console/src/app/pages/orgs/password-policy/password-policy.component.ts b/console/src/app/pages/orgs/password-policy/password-policy.component.ts deleted file mode 100644 index 68896e52d..000000000 --- a/console/src/app/pages/orgs/password-policy/password-policy.component.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { Component, OnDestroy, OnInit } 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 { 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'; - -export enum PolicyComponentAction { - CREATE = 'create', - MODIFY = 'modify', -} - -export enum PolicyComponentType { - LOCKOUT = 'lockout', - AGE = 'age', - COMPLEXITY = 'complexity', - IAM_POLICY = 'iam_policy', -} - -@Component({ - selector: 'app-password-policy', - templateUrl: './password-policy.component.html', - styleUrls: ['./password-policy.component.scss'], -}) -export class PasswordPolicyComponent implements OnInit, OnDestroy { - public title: string = ''; - public desc: string = ''; - - componentAction: PolicyComponentAction = PolicyComponentAction.CREATE; - - public policyType: PolicyComponentType = PolicyComponentType.COMPLEXITY; - - public PolicyComponentType: any = PolicyComponentType; - public PolicyComponentAction: any = PolicyComponentAction; - - public lockoutForm!: FormGroup; - public ageForm!: FormGroup; - - public complexityData!: PasswordComplexityPolicy.AsObject; - public lockoutData!: PasswordLockoutPolicy.AsObject; - public ageData!: PasswordAgePolicy.AsObject; - public iamData!: OrgIamPolicy.AsObject; - - private sub: Subscription = new Subscription(); - - constructor( - private route: ActivatedRoute, - private adminService: AdminService, - private mgmtService: ManagementService, - private router: Router, - private toast: ToastService, - private sessionStorage: StorageService, - ) { - this.sub = this.route.data.pipe(switchMap(data => { - this.componentAction = data.action; - return this.route.params; - })).subscribe(params => { - this.policyType = params.policytype; - - switch (params.policytype) { - case PolicyComponentType.LOCKOUT: - this.title = 'ORG.POLICY.PWD_LOCKOUT.TITLECREATE'; - this.desc = 'ORG.POLICY.PWD_LOCKOUT.DESCRIPTIONCREATE'; - break; - case PolicyComponentType.AGE: - this.title = 'ORG.POLICY.PWD_AGE.TITLECREATE'; - this.desc = 'ORG.POLICY.PWD_AGE.DESCRIPTIONCREATE'; - break; - case PolicyComponentType.COMPLEXITY: - this.title = 'ORG.POLICY.PWD_COMPLEXITY.TITLECREATE'; - this.desc = 'ORG.POLICY.PWD_COMPLEXITY.DESCRIPTIONCREATE'; - break; - case PolicyComponentType.IAM_POLICY: - this.title = 'ORG.POLICY.IAM_POLICY.TITLECREATE'; - this.desc = 'ORG.POLICY.IAM_POLICY.DESCRIPTIONCREATE'; - break; - } - - if (this.componentAction === PolicyComponentAction.MODIFY) { - this.getData(params).then(data => { - if (data) { - switch (this.policyType) { - case PolicyComponentType.LOCKOUT: - this.lockoutData = data.toObject() as PasswordLockoutPolicy.AsObject; - break; - case PolicyComponentType.AGE: - this.ageData = data.toObject() as PasswordAgePolicy.AsObject; - break; - case PolicyComponentType.COMPLEXITY: - this.complexityData = data.toObject() as PasswordComplexityPolicy.AsObject; - break; - case PolicyComponentType.IAM_POLICY: - this.iamData = data.toObject() as OrgIamPolicy.AsObject; - break; - } - } - }); - } - }); - } - - ngOnInit(): void { - } - - public ngOnDestroy(): void { - this.sub.unsubscribe(); - } - - private async getData(params: any): - Promise { - switch (params.policytype) { - case PolicyComponentType.LOCKOUT: - this.title = 'ORG.POLICY.PWD_LOCKOUT.TITLE'; - this.desc = 'ORG.POLICY.PWD_LOCKOUT.DESCRIPTION'; - return this.mgmtService.GetPasswordLockoutPolicy(); - case PolicyComponentType.AGE: - this.title = 'ORG.POLICY.PWD_AGE.TITLE'; - this.desc = 'ORG.POLICY.PWD_AGE.DESCRIPTION'; - return this.mgmtService.GetPasswordAgePolicy(); - case PolicyComponentType.COMPLEXITY: - this.title = 'ORG.POLICY.PWD_COMPLEXITY.TITLE'; - this.desc = 'ORG.POLICY.PWD_COMPLEXITY.DESCRIPTION'; - return this.mgmtService.GetPasswordComplexityPolicy(); - case PolicyComponentType.IAM_POLICY: - this.title = 'ORG.POLICY.IAM_POLICY.TITLECREATE'; - this.desc = 'ORG.POLICY.IAM_POLICY.DESCRIPTIONCREATE'; - return this.mgmtService.GetMyOrgIamPolicy(); - } - } - - public deletePolicy(): void { - switch (this.policyType) { - case PolicyComponentType.LOCKOUT: - this.mgmtService.DeletePasswordLockoutPolicy(this.lockoutData.id).then(() => { - this.toast.showInfo('Successfully deleted'); - }).catch(error => { - this.toast.showError(error); - }); - break; - case PolicyComponentType.AGE: - this.mgmtService.DeletePasswordAgePolicy(this.ageData.id).then(() => { - this.toast.showInfo('Successfully deleted'); - }).catch(error => { - this.toast.showError(error); - }); - break; - case PolicyComponentType.COMPLEXITY: - this.mgmtService.DeletePasswordComplexityPolicy(this.complexityData.id).then(() => { - this.toast.showInfo('Successfully deleted'); - }).catch(error => { - this.toast.showError(error); - }); - break; - } - } - - 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 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 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) { - switch (this.policyType) { - case PolicyComponentType.LOCKOUT: - this.mgmtService.CreatePasswordLockoutPolicy( - this.lockoutData.description, - this.lockoutData.maxAttempts, - this.lockoutData.showLockOutFailures, - ).then(() => { - this.router.navigate(['org']); - }).catch(error => { - this.toast.showError(error); - }); - - break; - case PolicyComponentType.AGE: - this.mgmtService.CreatePasswordAgePolicy( - this.ageData.description, - this.ageData.maxAgeDays, - this.ageData.expireWarnDays, - ).then(() => { - this.router.navigate(['org']); - }).catch(error => { - this.toast.showError(error); - }); - - break; - case PolicyComponentType.COMPLEXITY: - 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); - }); - break; - - case PolicyComponentType.IAM_POLICY: - 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); - }); - } - break; - } - } else if (this.componentAction === PolicyComponentAction.MODIFY) { - switch (this.policyType) { - case PolicyComponentType.LOCKOUT: - this.mgmtService.UpdatePasswordLockoutPolicy( - this.lockoutData.description, - this.lockoutData.maxAttempts, - this.lockoutData.showLockOutFailures, - ).then(() => { - this.router.navigate(['org']); - }).catch(error => { - this.toast.showError(error); - }); - - break; - case PolicyComponentType.AGE: - this.mgmtService.UpdatePasswordAgePolicy( - this.ageData.description, - this.ageData.maxAgeDays, - this.ageData.expireWarnDays, - ).then(() => { - this.router.navigate(['org']); - }).catch(error => { - this.toast.showError(error); - }); - - break; - case PolicyComponentType.COMPLEXITY: - 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); - }); - break; - - case PolicyComponentType.IAM_POLICY: - 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); - }); - } - break; - } - } - } -} diff --git a/console/src/app/pages/orgs/policy-grid/policy-grid.component.html b/console/src/app/pages/orgs/policy-grid/policy-grid.component.html index e3b889843..e45010f83 100644 --- a/console/src/app/pages/orgs/policy-grid/policy-grid.component.html +++ b/console/src/app/pages/orgs/policy-grid/policy-grid.component.html @@ -52,9 +52,39 @@
- - + +
+ + + + +
+
+ +
+
+ {{'ORG.POLICY.LOGIN_POLICY.TITLE' | translate}} + +
+ + + +

+ {{'ORG.POLICY.LOGIN_POLICY.DESCRIPTION' | translate}}

+
+ + +
+ + +
diff --git a/console/src/app/pages/orgs/policy-grid/policy-grid.component.scss b/console/src/app/pages/orgs/policy-grid/policy-grid.component.scss index 18eba04ea..39a8f5e65 100644 --- a/console/src/app/pages/orgs/policy-grid/policy-grid.component.scss +++ b/console/src/app/pages/orgs/policy-grid/policy-grid.component.scss @@ -16,7 +16,7 @@ h1 { margin: 1rem; display: flex; flex-direction: column; - min-height: 250px; + min-height: 200px; padding: 1rem; @media only screen and (max-width: 450px) { @@ -57,6 +57,7 @@ h1 { .desc { font-size: .9rem; + color: #8795a1; } .fill-space { diff --git a/console/src/app/pages/orgs/policy-grid/policy-grid.component.ts b/console/src/app/pages/orgs/policy-grid/policy-grid.component.ts index c442dfd06..b209e89a0 100644 --- a/console/src/app/pages/orgs/policy-grid/policy-grid.component.ts +++ b/console/src/app/pages/orgs/policy-grid/policy-grid.component.ts @@ -1,33 +1,23 @@ import { Component } from '@angular/core'; -import { - OrgIamPolicy, - PasswordAgePolicy, - PasswordComplexityPolicy, - PasswordLockoutPolicy, - PolicyState, -} from 'src/app/proto/generated/management_pb'; -import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; +import { PolicyComponentType } from 'src/app/modules/policies/policy-component-types.enum'; +import { LoginPolicy, OrgIamPolicy, PasswordComplexityPolicy, PolicyState } from 'src/app/proto/generated/management_pb'; import { ManagementService } from 'src/app/services/mgmt.service'; -import { PolicyComponentType } from '../password-policy/password-policy.component'; - @Component({ selector: 'app-policy-grid', templateUrl: './policy-grid.component.html', styleUrls: ['./policy-grid.component.scss'], }) export class PolicyGridComponent { - public lockoutPolicy!: PasswordLockoutPolicy.AsObject; - public agePolicy!: PasswordAgePolicy.AsObject; public complexityPolicy!: PasswordComplexityPolicy.AsObject; public iamPolicy!: OrgIamPolicy.AsObject; + public loginPolicy!: LoginPolicy.AsObject; public PolicyState: any = PolicyState; public PolicyComponentType: any = PolicyComponentType; constructor( private mgmtService: ManagementService, - public authUserService: GrpcAuthService, ) { this.getData(); } @@ -35,5 +25,8 @@ export class PolicyGridComponent { private getData(): void { this.mgmtService.GetPasswordComplexityPolicy().then(data => this.complexityPolicy = data.toObject()); this.mgmtService.GetMyOrgIamPolicy().then(data => this.iamPolicy = data.toObject()); + this.mgmtService.GetLoginPolicy().then(data => { + this.loginPolicy = data.toObject(); + }); } } diff --git a/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.html b/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.html deleted file mode 100644 index 1dee628f3..000000000 --- a/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.html +++ /dev/null @@ -1,25 +0,0 @@ -
- - Organization - - - {{selectedOrg.name}} - cancel - - - - - - - - - - {{org.name}} - - - -
\ No newline at end of file diff --git a/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.scss b/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.scss deleted file mode 100644 index 3db0dee74..000000000 --- a/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.full-width { - width: 100%; -} diff --git a/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.spec.ts b/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.spec.ts deleted file mode 100644 index 2a5f809fd..000000000 --- a/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { - SearchProjectAutocompleteComponent, -} from '../../../modules/search-project-autocomplete/search-project-autocomplete.component'; - -describe('SearchProjectComponent', () => { - let component: SearchProjectAutocompleteComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [SearchProjectAutocompleteComponent], - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SearchProjectAutocompleteComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.ts b/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.ts deleted file mode 100644 index 707af89b1..000000000 --- a/console/src/app/pages/orgs/search-org-autocomplete/search-org-autocomplete.component.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { COMMA, ENTER } from '@angular/cdk/keycodes'; -import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; -import { FormControl } from '@angular/forms'; -import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; -import { MatChipInputEvent } from '@angular/material/chips'; -import { debounceTime, tap } from 'rxjs/operators'; -import { Org } from 'src/app/proto/generated/management_pb'; -import { ManagementService } from 'src/app/services/mgmt.service'; -import { ToastService } from 'src/app/services/toast.service'; - -@Component({ - selector: 'app-search-org-autocomplete', - templateUrl: './search-org-autocomplete.component.html', - styleUrls: ['./search-org-autocomplete.component.scss'], -}) -export class SearchOrgAutocompleteComponent { - public selectable: boolean = true; - public removable: boolean = true; - public addOnBlur: boolean = true; - public separatorKeysCodes: number[] = [ENTER, COMMA]; - public myControl: FormControl = new FormControl(); - public names: string[] = []; - public orgs: Array = []; - public filteredOrgs: Array = []; - public isLoading: boolean = false; - @ViewChild('domainInput') public domainInput!: ElementRef; - @ViewChild('auto') public matAutocomplete!: MatAutocomplete; - @Input() public singleOutput: boolean = false; - @Output() public selectionChanged: EventEmitter = new EventEmitter(); - constructor(private mgmtService: ManagementService, private toast: ToastService) { - this.myControl.valueChanges.pipe(debounceTime(200), tap(() => this.isLoading = true)).subscribe(value => { - return this.mgmtService.getOrgByDomainGlobal(value).then((org) => { - this.isLoading = false; - if (org) { - this.filteredOrgs = [org.toObject()]; - } - }).catch(error => { - this.isLoading = false; - }); - }); - } - - public displayFn(org?: Org.AsObject): string | undefined { - return org ? `${org.name}` : undefined; - } - - public add(event: MatChipInputEvent): void { - if (!this.matAutocomplete.isOpen) { - const input = event.input; - const value = event.value; - - if ((value || '').trim()) { - const index = this.filteredOrgs.findIndex((org) => { - if (org.name) { - return org.name === value; - } - }); - if (index > -1) { - if (this.orgs && this.orgs.length > 0) { - this.orgs.push(this.filteredOrgs[index]); - } else { - this.orgs = [this.filteredOrgs[index]]; - } - } - } - - if (input) { - input.value = ''; - } - } - } - - public remove(org: Org.AsObject): void { - const index = this.orgs.indexOf(org); - - if (index >= 0) { - this.orgs.splice(index, 1); - } - } - - public selected(event: MatAutocompleteSelectedEvent): void { - const index = this.filteredOrgs.findIndex((org) => org === event.option.value); - if (index !== -1) { - if (this.singleOutput) { - this.selectionChanged.emit(this.filteredOrgs[index]); - } else { - if (this.orgs && this.orgs.length > 0) { - this.orgs.push(this.orgs[index]); - this.selectionChanged.emit(this.orgs); - } else { - this.orgs = [this.filteredOrgs[index]]; - } - - this.domainInput.nativeElement.value = ''; - this.myControl.setValue(null); - } - } - } -} diff --git a/console/src/app/pages/projects/apps/app-detail/app-detail.component.html b/console/src/app/pages/projects/apps/app-detail/app-detail.component.html index 2b17afcee..dc7575441 100644 --- a/console/src/app/pages/projects/apps/app-detail/app-detail.component.html +++ b/console/src/app/pages/projects/apps/app-detail/app-detail.component.html @@ -15,10 +15,12 @@
- + {{'APP.PAGES.DETAIL.STATE.'+AppState.APPSTATE_INACTIVE | translate}} - + {{'APP.PAGES.DETAIL.STATE.'+AppState.APPSTATE_ACTIVE | translate}} @@ -41,7 +43,7 @@
+ *ngIf="app?.oidcConfig?.authMethodType !== OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE">
diff --git a/console/src/app/pages/projects/granted-projects/granted-project-list/granted-project-list.component.html b/console/src/app/pages/projects/granted-projects/granted-project-list/granted-project-list.component.html index 805c5a524..2d30526f8 100644 --- a/console/src/app/pages/projects/granted-projects/granted-project-list/granted-project-list.component.html +++ b/console/src/app/pages/projects/granted-projects/granted-project-list/granted-project-list.component.html @@ -13,7 +13,7 @@ [selection]="selection" [loading]="loading$ | async">
- +
-
diff --git a/console/src/app/pages/projects/owned-projects/owned-project-list/owned-project-list.component.html b/console/src/app/pages/projects/owned-projects/owned-project-list/owned-project-list.component.html index ab20d2043..2c39ced12 100644 --- a/console/src/app/pages/projects/owned-projects/owned-project-list/owned-project-list.component.html +++ b/console/src/app/pages/projects/owned-projects/owned-project-list/owned-project-list.component.html @@ -17,7 +17,7 @@
- +
- +
-
+
\ No newline at end of file diff --git a/console/src/app/pages/projects/owned-projects/project-grant-detail/project-grant-members/project-grant-members.component.html b/console/src/app/pages/projects/owned-projects/project-grant-detail/project-grant-members/project-grant-members.component.html index 3a62f519f..6d8705b2b 100644 --- a/console/src/app/pages/projects/owned-projects/project-grant-detail/project-grant-members/project-grant-members.component.html +++ b/console/src/app/pages/projects/owned-projects/project-grant-detail/project-grant-members/project-grant-members.component.html @@ -16,8 +16,7 @@
- +
- + \ No newline at end of file diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.html b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.html index 4346d428f..f84c139a6 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.html +++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.html @@ -1,6 +1,6 @@ - +
diff --git a/console/src/app/pages/users/user-detail/machine-keys/machine-keys.component.html b/console/src/app/pages/users/user-detail/machine-keys/machine-keys.component.html index 6eb948730..68f6d1b28 100644 --- a/console/src/app/pages/users/user-detail/machine-keys/machine-keys.component.html +++ b/console/src/app/pages/users/user-detail/machine-keys/machine-keys.component.html @@ -13,7 +13,7 @@
-
{{ 'USER.MFA.TABLETYPE' | translate }} {{'USER.MFA.TYPE.'+ mfa.type | translate}}
+
- +
\ No newline at end of file diff --git a/console/src/app/pages/users/user-detail/membership-detail/membership-detail.component.html b/console/src/app/pages/users/user-detail/membership-detail/membership-detail.component.html index a7e5bb714..fd2bcf322 100644 --- a/console/src/app/pages/users/user-detail/membership-detail/membership-detail.component.html +++ b/console/src/app/pages/users/user-detail/membership-detail/membership-detail.component.html @@ -9,7 +9,7 @@
- +
- +
diff --git a/console/src/app/pages/users/user-detail/user-detail/user-mfa/user-mfa.component.html b/console/src/app/pages/users/user-detail/user-detail/user-mfa/user-mfa.component.html index 5a2c11e77..f274b677c 100644 --- a/console/src/app/pages/users/user-detail/user-detail/user-mfa/user-mfa.component.html +++ b/console/src/app/pages/users/user-detail/user-detail/user-mfa/user-mfa.component.html @@ -1,6 +1,6 @@ - +
diff --git a/console/src/app/pages/users/user-list/user-table/user-table.component.html b/console/src/app/pages/users/user-list/user-table/user-table.component.html index fa71bb35a..f5ca7e97a 100644 --- a/console/src/app/pages/users/user-list/user-table/user-table.component.html +++ b/console/src/app/pages/users/user-list/user-table/user-table.component.html @@ -17,7 +17,7 @@
-
{{ 'USER.MFA.TABLETYPE' | translate }} {{'USER.MFA.TYPE.'+ mfa.type | translate}}
+
- +
\ No newline at end of file diff --git a/console/src/app/services/admin.service.ts b/console/src/app/services/admin.service.ts index 3b13bae66..90785d7f3 100644 --- a/console/src/app/services/admin.service.ts +++ b/console/src/app/services/admin.service.ts @@ -7,6 +7,7 @@ import { CreateHumanRequest, CreateOrgRequest, CreateUserRequest, + DefaultLoginPolicy, DefaultLoginPolicyView, FailedEventID, FailedEvents, @@ -15,6 +16,18 @@ import { IamMemberSearchQuery, IamMemberSearchRequest, IamMemberSearchResponse, + Idp, + IdpID, + IdpProviderID, + IdpProviderSearchRequest, + IdpProviderSearchResponse, + IdpSearchQuery, + IdpSearchRequest, + IdpSearchResponse, + IdpView, + OidcIdpConfig, + OidcIdpConfigCreate, + OidcIdpConfigUpdate, OrgIamPolicy, OrgIamPolicyID, OrgIamPolicyRequest, @@ -52,11 +65,6 @@ export class AdminService { return this.grpcService.admin.getIamMemberRoles(req); } - public async getDefaultLoginPolicy(): Promise { - const req = new Empty(); - return this.grpcService.admin.getDefaultLoginPolicy(req); - } - public async GetViews(): Promise { const req = new Empty(); return this.grpcService.admin.getViews(req); @@ -82,6 +90,101 @@ export class AdminService { return this.grpcService.admin.removeFailedEvent(req); } + public async GetDefaultLoginPolicy( + ): Promise { + const req = new Empty(); + return this.grpcService.admin.getDefaultLoginPolicy(req); + } + + public async UpdateDefaultLoginPolicy(req: DefaultLoginPolicy): Promise { + return this.grpcService.admin.updateDefaultLoginPolicy(req); + } + + public async AddIdpProviderToDefaultLoginPolicy(configId: string): Promise { + const req = new IdpProviderID(); + req.setIdpConfigId(configId); + return this.grpcService.admin.addIdpProviderToDefaultLoginPolicy(req); + } + + public async RemoveIdpProviderFromDefaultLoginPolicy(configId: string): Promise { + const req = new IdpProviderID(); + req.setIdpConfigId(configId); + return this.grpcService.admin.removeIdpProviderFromDefaultLoginPolicy(req); + } + + public async GetDefaultLoginPolicyIdpProviders(limit?: number, offset?: number): Promise { + const req = new IdpProviderSearchRequest(); + if (limit) { + req.setLimit(limit); + } + if (offset) { + req.setOffset(offset); + } + return this.grpcService.admin.getDefaultLoginPolicyIdpProviders(req); + } + + public async SearchIdps( + limit?: number, + offset?: number, + queryList?: IdpSearchQuery[], + ): Promise { + const req = new IdpSearchRequest(); + if (limit) { + req.setLimit(limit); + } + if (offset) { + req.setOffset(offset); + } + if (queryList) { + req.setQueriesList(queryList); + } + return this.grpcService.admin.searchIdps(req); + } + + public async IdpByID( + id: string, + ): Promise { + const req = new IdpID(); + req.setId(id); + return this.grpcService.admin.idpByID(req); + } + + public async CreateOidcIdp( + req: OidcIdpConfigCreate, + ): Promise { + return this.grpcService.admin.createOidcIdp(req); + } + + public async UpdateOidcIdpConfig( + req: OidcIdpConfigUpdate, + ): Promise { + return this.grpcService.mgmt.updateOidcIdpConfig(req); + } + + public async RemoveIdpConfig( + id: string, + ): Promise { + const req = new IdpID; + req.setId(id); + return this.grpcService.admin.removeIdpConfig(req); + } + + public async DeactivateIdpConfig( + id: string, + ): Promise { + const req = new IdpID; + req.setId(id); + return this.grpcService.admin.deactivateIdpConfig(req); + } + + public async ReactivateIdpConfig( + id: string, + ): Promise { + const req = new IdpID; + req.setId(id); + return this.grpcService.admin.reactivateIdpConfig(req); + } + public async SearchIamMembers( limit: number, offset: number, diff --git a/console/src/app/services/mgmt.service.ts b/console/src/app/services/mgmt.service.ts index 66617fbf9..8dbf83a42 100644 --- a/console/src/app/services/mgmt.service.ts +++ b/console/src/app/services/mgmt.service.ts @@ -24,7 +24,20 @@ import { Gender, GrantedProjectSearchRequest, Iam, + Idp, + IdpID, + IdpProviderAdd, + IdpProviderID, + IdpProviderSearchRequest, + IdpProviderSearchResponse, + IdpProviderType, + IdpSearchQuery, + IdpSearchRequest, + IdpSearchResponse, + IdpView, LoginName, + LoginPolicy, + LoginPolicyView, MachineKeyIDRequest, MachineKeySearchRequest, MachineKeySearchResponse, @@ -35,6 +48,9 @@ import { OIDCApplicationCreate, OIDCConfig, OIDCConfigUpdate, + OidcIdpConfig, + OidcIdpConfigCreate, + OidcIdpConfigUpdate, Org, OrgCreateRequest, OrgDomain, @@ -145,6 +161,105 @@ export type ResponseMapper = (resp: TResp) => TMappedResp; export class ManagementService { constructor(private readonly grpcService: GrpcService) { } + public async SearchIdps( + limit?: number, + offset?: number, + queryList?: IdpSearchQuery[], + ): Promise { + const req = new IdpSearchRequest(); + if (limit) { + req.setLimit(limit); + } + if (offset) { + req.setOffset(offset); + } + if (queryList) { + req.setQueriesList(queryList); + } + return this.grpcService.mgmt.searchIdps(req); + } + + public async GetLoginPolicy(): Promise { + const req = new Empty(); + return this.grpcService.mgmt.getLoginPolicy(req); + } + + public async UpdateLoginPolicy(req: LoginPolicy): Promise { + return this.grpcService.mgmt.updateLoginPolicy(req); + } + + public async RemoveLoginPolicy(): Promise { + return this.grpcService.mgmt.removeLoginPolicy(new Empty()); + } + + public async addIdpProviderToLoginPolicy(configId: string, idpType: IdpProviderType): Promise { + const req = new IdpProviderAdd(); + req.setIdpProviderType(idpType); + req.setIdpConfigId(configId); + return this.grpcService.mgmt.addIdpProviderToLoginPolicy(req); + } + + public async RemoveIdpProviderFromLoginPolicy(configId: string): Promise { + const req = new IdpProviderID(); + req.setIdpConfigId(configId); + return this.grpcService.mgmt.removeIdpProviderFromLoginPolicy(req); + } + + public async GetLoginPolicyIdpProviders(limit?: number, offset?: number): Promise { + const req = new IdpProviderSearchRequest(); + if (limit) { + req.setLimit(limit); + } + if (offset) { + req.setOffset(offset); + } + return this.grpcService.mgmt.getLoginPolicyIdpProviders(req); + } + + public async IdpByID( + id: string, + ): Promise { + const req = new IdpID(); + req.setId(id); + return this.grpcService.mgmt.idpByID(req); + } + + public async CreateOidcIdp( + req: OidcIdpConfigCreate, + ): Promise { + return this.grpcService.mgmt.createOidcIdp(req); + } + + public async UpdateOidcIdpConfig( + req: OidcIdpConfigUpdate, + ): Promise { + return this.grpcService.mgmt.updateOidcIdpConfig(req); + } + + public async RemoveIdpConfig( + id: string, + ): Promise { + const req = new IdpID; + req.setId(id); + return this.grpcService.mgmt.removeIdpConfig(req); + } + + public async DeactivateIdpConfig( + id: string, + ): Promise { + const req = new IdpID; + req.setId(id); + return this.grpcService.mgmt.deactivateIdpConfig(req); + } + + public async ReactivateIdpConfig( + id: string, + ): Promise { + const req = new IdpID; + req.setId(id); + return this.grpcService.mgmt.reactivateIdpConfig(req); + } + public async CreateUserHuman(username: string, user: CreateHumanRequest): Promise { const req = new CreateUserRequest(); diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index cb0931bfc..2ccde96a4 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -260,7 +260,9 @@ "DEACTIVATED":"User deaktiviert!", "SELECTEDREACTIVATED":"Selektierte User reaktiviert!", "SELECTEDDEACTIVATED":"Selektierte Benutzer deaktiviert!", - "SELECTEDKEYSDELETED":"Selektierte Schlüssel gelöscht!" + "SELECTEDKEYSDELETED":"Selektierte Schlüssel gelöscht!", + "KEYADDED":"Schlüssel hinzugefügt!", + "MACHINEADDED":"Service User erstellt!" }, "MEMBERSHIPS": { "TITLE":"Zitadel Manager Rollen", @@ -395,6 +397,12 @@ "TITLECREATE":"IAM Zugangseinstellungen festlegen", "DESCRIPTIONCREATE":"Emails als Benutzername sind nicht erlaubt wenn UserLoginMustBeDomain gesetzt ist." }, + "LOGIN_POLICY": { + "TITLE":"Login Richtlinien", + "DESCRIPTION":"Definiere die Loginmethoden für Benutzer", + "TITLECREATE":"Definiere die Loginmethoden für Benutzer", + "DESCRIPTIONCREATE":"Nutzer können Sich mit den verfügbaren Idps authentifizieren." + }, "BTN_INSTALL":"Installieren", "BTN_EDIT":"Modifizieren", "DATA": { @@ -408,7 +416,10 @@ "MAXATTEMPTS":"Maximale Anzahl an Versuchen", "EXPIREWARNDAYS":"Ablauf Warnung nach Tagen", "MAXAGEDAYS":"Maximale Gültigkeit in Tagen", - "USERLOGINMUSTBEDOMAIN":"User Login must be Domain" + "USERLOGINMUSTBEDOMAIN":"User Login must be Domain", + "ALLOWUSERNAMEPASSWORD":"Benutzername Password erlaubt", + "ALLOWEXTERNALIDP":"Externer IDP erlaubt", + "ALLOWREGISTER":"Registrieren erlaubt" }, "DELETE":"Richtlinie entfernen / zurücksetzen" }, @@ -612,6 +623,51 @@ "DELETED":"Projekt gelöscht!" } }, + "IDP":{ + "LIST": { + "TITLE":"Identity 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" + }, + "TYPES": { + "0":"unknown", + "1":"System", + "2":"Organisation" + }, + "STATES":{ + "0":"aktiv", + "1":"inaktiv" + }, + "TYPE":"Typ", + "NAME":"Name", + "CONFIG":"Konfiguration", + "STATE":"Status", + "LOGOSRC":"Logo Src", + "ISSUER":"Issuer", + "SCOPESLIST":"Scopes List", + "CLIENTID":"Client ID", + "CLIENTSECRET":"Client Secret", + "CREATIONDATE":"Erstelldatum", + "CHANGEDATE":"Letzte Änderung", + "DEACTIVATE":"Deaktivieren", + "ACTIVATE":"Aktivieren", + "DELETE":"Löschen" + }, + "LOGINPOLICY": { + "CREATE": { + "TITLE":"Login Policy", + "DESCRIPTION":"Definieren Sie hier, mit welchen Idps sich Ihre Benutzer anmelden können." + }, + "IDPS":"Identity Providers", + "ADDIDP": { + "TITLE":"Identity Provider hinzufügen", + "DESCRIPTION":"Sie können vordefinierte oder selbsterstellten Provider auswählen", + "SELECTIDPS":"Identity Provider" + } + }, "APP": { "LIST": "Applications", "PAGES": { diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 45425af8b..d842d52fd 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -260,7 +260,9 @@ "DEACTIVATED":"User deactivated", "SELECTEDREACTIVATED":"Selected Users reactivated", "SELECTEDDEACTIVATED":"Selected Users deactivated", - "SELECTEDKEYSDELETED":"Selected Keys deleted!" + "SELECTEDKEYSDELETED":"Selected Keys deleted!", + "KEYADDED":"Key added!", + "MACHINEADDED":"Service User created!" }, "MEMBERSHIPS": { "TITLE":"Zitadel Manager Roles", @@ -395,6 +397,12 @@ "TITLECREATE":"Set IAM Access preferences", "DESCRIPTIONCREATE":"Emails as username is not allowed for enabled UserLoginMustBeDomain" }, + "LOGIN_POLICY": { + "TITLE":"Login Policy", + "DESCRIPTION":"Define how Users can be authenticated", + "TITLECREATE":"Define how Users can be authenticated", + "DESCRIPTIONCREATE":"Users can choose from all of the available identity providers." + }, "BTN_INSTALL":"Setup", "BTN_EDIT":"Modify", "DATA": { @@ -408,7 +416,10 @@ "MAXATTEMPTS":"Max Attempts", "EXPIREWARNDAYS":"Expiration Warning after day", "MAXAGEDAYS":"Max Age in days", - "USERLOGINMUSTBEDOMAIN":"User Login must be Domain" + "USERLOGINMUSTBEDOMAIN":"User Login must be Domain", + "ALLOWUSERNAMEPASSWORD":"Username Password allowed", + "ALLOWEXTERNALIDP":"External IDP allowed", + "ALLOWREGISTER":"Register allowed" }, "DELETE":"Uninstall / Reset Policy" }, @@ -612,6 +623,51 @@ "DELETED":"Deleted Project!" } }, + "IDP":{ + "LIST": { + "TITLE":"Identity Providers", + "DESCRIPTION":"Define additional Identity Providers, which can be used to authenticate in your organisations." + }, + "CREATE": { + "TITLE":"New Identity Provider", + "DESCRIPTION":"Configure the Endpoint of your new service provider." + }, + "TYPES": { + "0":"unknown", + "1":"System", + "2":"Organisation" + }, + "STATES":{ + "0":"active", + "1":"inactive" + }, + "TYPE":"Type", + "NAME":"Name", + "CONFIG":"Configuration", + "STATE":"State", + "LOGOSRC":"Logo Src", + "ISSUER":"Issuer", + "SCOPESLIST":"Scopes List", + "CLIENTID":"Client ID", + "CLIENTSECRET":"Client Secret", + "CREATIONDATE":"Created At", + "CHANGEDATE":"Last Modified", + "DEACTIVATE":"Deactivate", + "ACTIVATE":"Activate", + "DELETE":"Delete" + }, + "LOGINPOLICY": { + "CREATE": { + "TITLE":"Login Policy", + "DESCRIPTION":"Define how your users can be authenticated on your organisation." + }, + "IDPS":"Identity Providers", + "ADDIDP": { + "TITLE":"Add Identity Provider", + "DESCRIPTION":"You can select predefined or selfcreated providers for authentication.", + "SELECTIDPS":"Identity providers" + } + }, "APP": { "LIST": "Applications", "PAGES": { diff --git a/console/src/styles/table.scss b/console/src/styles/table.scss index 27623e180..b6f14c8b0 100644 --- a/console/src/styles/table.scss +++ b/console/src/styles/table.scss @@ -11,10 +11,6 @@ .mat-paginator { background-color: inherit !important; transition: background-color .4s ease-in-out; - - &.background-style { - background-color: inherit !important; - } } /* stylelint-enable */