From 1f3bae922eae2c9eb396c5a178724e2f615c09b1 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 21 Sep 2020 10:53:42 +0300 Subject: [PATCH] UI updated OAuth2 for new endpoint --- ui-ngx/src/app/core/http/admin.service.ts | 20 +- .../admin/oauth2-settings.component.html | 43 ++-- .../pages/admin/oauth2-settings.component.ts | 220 ++++++++++++------ .../app/shared/models/entity-type.models.ts | 3 +- .../src/app/shared/models/settings.models.ts | 10 +- .../assets/locale/locale.constant-en_US.json | 4 +- 6 files changed, 191 insertions(+), 109 deletions(-) diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 38a7c6e5e9..d11a8f373e 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -60,17 +60,27 @@ export class AdminService { defaultHttpOptionsFromConfig(config)); } - public getOAuth2Settings(config?: RequestConfig): Observable { - return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); + public getOAuth2Settings(config?: RequestConfig): Observable> { + return this.http.get>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); } public getOAuth2Template(config?: RequestConfig): Observable> { return this.http.get>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); } - public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, - config?: RequestConfig): Observable { - return this.http.post('/api/oauth2/config', OAuth2Setting, + public saveOAuth2Settings(OAuth2Setting: OAuth2Settings[], + config?: RequestConfig): Observable> { + return this.http.post>('/api/oauth2/config', OAuth2Setting, + defaultHttpOptionsFromConfig(config)); + } + + public deleteOAuth2Domain(OAuth2Domain: string, config?: RequestConfig) { + return this.http.delete(`/api/oauth2/config/domain/${OAuth2Domain}`, + defaultHttpOptionsFromConfig(config)); + } + + public deleteOAuthCclientRegistrationId(clientRegistrationId: string, config?: RequestConfig) { + return this.http.delete(`/api/oauth2/config/${clientRegistrationId}`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index fce4f0339f..4e45e02945 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -30,10 +30,10 @@
- +
- + @@ -75,7 +75,7 @@ class="registration-card mat-elevation-z0"> - {{ registration.get('providerName').value }} + {{ getProviderName(registration) }} @@ -152,7 +155,7 @@ @@ -171,7 +174,7 @@ @@ -185,7 +188,7 @@ @@ -207,7 +210,7 @@ -
+
admin.oauth2.login-button-label @@ -304,7 +307,7 @@ - + admin.oauth2.tenant-name-pattern { if (domain === 'CUSTOM') { - basicGroup.get('tenantNamePattern').setValidators(Validators.required); + basicGroup.get('tenantNamePattern').enable(); } else { - basicGroup.get('tenantNamePattern').clearValidators(); + basicGroup.get('tenantNamePattern').disable(); } })); return basicGroup; } - get formCustomGroup(): FormGroup { + private formCustomGroup(mapperConfigCustom?: MapperConfigCustom): FormGroup { return this.fb.group({ - url: [null, [Validators.required, Validators.pattern(this.URL_REGEXP)]], - username: [null], - password: [null] + url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null, [Validators.required, Validators.pattern(this.URL_REGEXP)]], + username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null], + password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null] }); } private buildOAuth2SettingsForm(): void { this.oauth2SettingsForm = this.fb.group({ - clientsDomainsParams: this.fb.array([], Validators.required) + clientDomains: this.fb.array([]) }); } - private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { - if (oauth2Settings.clientsDomainsParams) { - oauth2Settings.clientsDomainsParams.forEach((domaindomain) => { - this.clientsDomainsParams.push(this.buildSettingsDomain(domaindomain)); + private initOAuth2Settings(oauth2Settings: OAuth2Settings[]): void { + if (oauth2Settings) { + oauth2Settings.forEach((domain) => { + this.clientDomains.push(this.buildSettingsDomain(domain)); }); } } private uniqueDomainValidator(control: AbstractControl): { [key: string]: boolean } | null { - if (control.value !== null && control?.root) { + if (control.root.value.clientDomains?.length > 1) { const listDomainName = []; - control.root.value.clientsDomainsParams.forEach((domain) => { + control.root.value.clientDomains.forEach((domain) => { listDomainName.push(domain.domainName); }); if (listDomainName.indexOf(control.value) > -1) { @@ -183,7 +191,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return null; } - private buildSettingsDomain(domainParams?: DomainParams): FormGroup { + private buildSettingsDomain(domainParams?: OAuth2Settings): FormGroup { let url = this.window.location.protocol + '//' + this.window.location.hostname; const port = this.window.location.port; if (port !== '80' && port !== '443') { @@ -191,8 +199,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } url += '/login/oauth2/code/'; const formDomain = this.fb.group({ - domainName: [null, [Validators.required, Validators.pattern('((?![:/]).)*$'), this.uniqueDomainValidator]], - redirectUriTemplate: [url, [Validators.required, Validators.pattern(this.URL_REGEXP)]], + domainName: [domainParams?.domainName ? domainParams.domainName : this.window.location.hostname, [ + Validators.required, + Validators.pattern('((?![:/]).)*$'), + this.uniqueDomainValidator]], + redirectUriTemplate: [domainParams?.redirectUriTemplate ? domainParams.redirectUriTemplate : url, [ + Validators.required, + Validators.pattern(this.URL_REGEXP)]], clientRegistrations: this.fb.array([], Validators.required) }); @@ -201,7 +214,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha domain = this.window.location.hostname; } const uri = this.window.location.protocol + `//${domain}/login/oauth2/code/`; - formDomain.get('redirectUriTemplate').patchValue(uri); + formDomain.get('redirectUriTemplate').patchValue(uri, {emitEvent: false}); })); if (domainParams) { @@ -216,40 +229,59 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup { + let additionalInfo = null; + if (registrationData?.additionalInfo) { + additionalInfo = JSON.parse(registrationData.additionalInfo); + if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) { + additionalInfo.providerName = 'Custom'; + } + } const clientRegistration = this.fb.group({ - providerName: ['Custom', [Validators.required]], - loginButtonLabel: [null, [Validators.required]], - loginButtonIcon: [null], - clientId: ['', [Validators.required]], - clientSecret: ['', [Validators.required]], - accessTokenUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - authorizationUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - scope: this.fb.array([], [Validators.required]), - jwkSetUri: ['', [Validators.pattern(this.URL_REGEXP)]], - userInfoUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - clientAuthenticationMethod: ['POST', [Validators.required]], - userNameAttributeName: ['email', [Validators.required]], + id: this.fb.group({ + id: [registrationData?.id?.id ? registrationData.id.id : null], + entityType: [registrationData?.id?.entityType ? registrationData.id.entityType : null] + }), + additionalInfo: this.fb.group({ + providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : 'Custom', Validators.required] + }), + loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required], + loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null], + clientId: [registrationData?.clientId ? registrationData.clientId : '', Validators.required], + clientSecret: [registrationData?.clientSecret ? registrationData.clientSecret : '', Validators.required], + accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '', [ + Validators.required, + Validators.pattern(this.URL_REGEXP)]], + authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '', [ + Validators.required, + Validators.pattern(this.URL_REGEXP)]], + scope: this.fb.array(registrationData?.scope ? registrationData.scope : []), + jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], + userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '', [ + Validators.required, + Validators.pattern(this.URL_REGEXP)]], + clientAuthenticationMethod: [ + registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : 'POST', Validators.required], + userNameAttributeName: [ + registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required], mapperConfig: this.fb.group({ - allowUserCreation: [true], - activateUser: [false], - type: ['BASIC', [Validators.required]], - basic: this.formBasicGroup + allowUserCreation: [registrationData?.mapperConfig?.allowUserCreation ? registrationData.mapperConfig.allowUserCreation : true], + activateUser: [registrationData?.mapperConfig?.activateUser ? registrationData.mapperConfig.activateUser : false], + type: [registrationData?.mapperConfig?.type ? registrationData.mapperConfig.type : 'BASIC', Validators.required] } ) }); + if (registrationData) { + this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig); + } else { + this.changeMapperConfigType(clientRegistration, 'BASIC'); + } + this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => { - const mapperConfig = clientRegistration.get('mapperConfig') as FormGroup; - if (value === 'BASIC') { - mapperConfig.removeControl('custom'); - mapperConfig.addControl('basic', this.formBasicGroup); - } else { - mapperConfig.removeControl('basic'); - mapperConfig.addControl('custom', this.formCustomGroup); - } + this.changeMapperConfigType(clientRegistration, value); })); - this.subscriptions.push(clientRegistration.get('providerName').valueChanges.subscribe((provider) => { + this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => { (clientRegistration.get('scope') as FormArray).clear(); if (provider === 'Custom') { clientRegistration.reset(this.defaultProvider, {emitEvent: false}); @@ -259,6 +291,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration.get('userInfoUri').enable(); } else { const template = this.templates.get(provider); + delete template.id; + delete template.additionalInfo; template.scope.forEach(() => { (clientRegistration.get('scope') as FormArray).push(this.fb.control('')); }); @@ -270,20 +304,23 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } })); - if (registrationData) { - registrationData.scope.forEach(() => { - (clientRegistration.get('scope') as FormArray).push(this.fb.control('')); - }); - if (registrationData.mapperConfig.type !== 'BASIC') { - clientRegistration.get('mapperConfig.type').patchValue('CUSTOM'); - } - } - return clientRegistration; } + private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) { + const mapperConfig = control.get('mapperConfig') as FormGroup; + if (type === 'BASIC') { + mapperConfig.removeControl('custom'); + mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); + } else { + mapperConfig.removeControl('basic'); + mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); + } + } + save(): void { - this.adminService.saveOAuth2Settings(this.oauth2SettingsForm.value).subscribe( + const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue().clientDomains); + this.adminService.saveOAuth2Settings(setting).subscribe( (oauth2Settings) => { this.oauth2Settings = oauth2Settings; this.oauth2SettingsForm.markAsPristine(); @@ -292,6 +329,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha ); } + private prepareFormValue(formValue: OAuth2Settings[]): OAuth2Settings[]{ + formValue.forEach((setting) => { + setting.clientRegistrations.forEach((registration) => { + registration.additionalInfo = JSON.stringify(registration.additionalInfo); + if (registration.id.id === null) { + delete registration.id; + } + }); + }); + return formValue; + } + confirmForm(): FormGroup { return this.oauth2SettingsForm; } @@ -315,7 +364,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } addDomain(): void { - this.clientsDomainsParams.push(this.buildSettingsDomain()); + this.clientDomains.push(this.buildSettingsDomain()); } deleteDomain($event: Event, index: number): void { @@ -324,14 +373,21 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha $event.preventDefault(); } - const domainName = this.clientsDomainsParams.at(index).get('domainName').value; + const domainName = this.clientDomains.at(index).get('domainName').value; this.dialogService.confirm( this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}), this.translate.instant('admin.oauth2.delete-domain-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { if (data) { - this.clientsDomainsParams.removeAt(index); + if (index < this.oauth2Settings.length) { + this.adminService.deleteOAuth2Domain(this.oauth2Settings[index].domainName).subscribe(() => { + this.oauth2Settings.splice(index, 1); + this.clientDomains.removeAt(index); + }); + } else { + this.clientDomains.removeAt(index); + } } }); } @@ -344,20 +400,27 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.clientDomainRegistrations(control).push(this.buildSettingsRegistration()); } - deleteRegistration($event: Event, controler: AbstractControl, index: number): void { + deleteRegistration($event: Event, control: AbstractControl, index: number): void { if ($event) { $event.stopPropagation(); $event.preventDefault(); } - const providerName = this.clientDomainRegistrations(controler).at(index).get('providerName').value; + const providerName = this.clientDomainRegistrations(control).at(index).get('additionalInfo.providerName').value; this.dialogService.confirm( this.translate.instant('admin.oauth2.delete-registration-title', {name: providerName || ''}), this.translate.instant('admin.oauth2.delete-registration-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { if (data) { - this.clientDomainRegistrations(controler).removeAt(index); + const registrationId = this.clientDomainRegistrations(control).at(index).get('id.id').value; + if (registrationId) { + this.adminService.deleteOAuthCclientRegistrationId(registrationId).subscribe(() => { + this.clientDomainRegistrations(control).removeAt(index); + }); + } else { + this.clientDomainRegistrations(control).removeAt(index); + } } }); } @@ -385,7 +448,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha overlayRef.dispose(); }); - const redirectURI = this.clientsDomainsParams.at(index).get('redirectUriTemplate').value; + const redirectURI = this.clientDomains.at(index).get('redirectUriTemplate').value; const injectionTokens = new WeakMap([ [EDIT_REDIRECT_URI_PANEL_DATA, { @@ -399,7 +462,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha componentRef.onDestroy(() => { if (componentRef.instance.result !== null) { const attributeValue = componentRef.instance.result; - this.clientsDomainsParams.at(index).get('redirectUriTemplate').patchValue(attributeValue, {emitEvent: true}); + this.clientDomains.at(index).get('redirectUriTemplate').patchValue(attributeValue, {emitEvent: true}); } }); } @@ -407,4 +470,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha toggleEditMode(control: AbstractControl, path: string) { control.get(path).disabled ? control.get(path).enable() : control.get(path).disable(); } + + getProviderName(controller: AbstractControl): string { + return controller.get('additionalInfo.providerName').value; + } + + getHelpLink(controller: AbstractControl): string { + const provider = controller.get('additionalInfo.providerName').value; + return this.templates.get(provider).helpLink; + } } diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 6841a911b7..f3888fe4be 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -45,7 +45,8 @@ export enum EntityType { RULE_NODE = 'RULE_NODE', ENTITY_VIEW = 'ENTITY_VIEW', WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', - WIDGET_TYPE = 'WIDGET_TYPE' + WIDGET_TYPE = 'WIDGET_TYPE', + OAUTH2_CLIENT_REGISTRATION = 'OAUTH2_CLIENT_REGISTRATION' } export enum AliasEntityType { diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 8fe83aee09..f27d80a260 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -69,21 +69,15 @@ export interface UpdateMessage { } export interface OAuth2Settings { - clientsDomainsParams: DomainParams[]; -} - -export interface DomainParams { domainName: string; redirectUriTemplate: string; clientRegistrations: ClientRegistration[]; } export interface ClientProviderTemplated extends ClientRegistration{ - additionalInfo: string; comment: string; createdTime: number; helpLink: string; - id: EntityId; name: string; providerId: string; tenantId: TenantId; @@ -97,11 +91,13 @@ export interface ClientRegistration { accessTokenUri: string; authorizationUri: string; scope: string[]; - jwkSetUri: string; + jwkSetUri?: string; userInfoUri: string; clientAuthenticationMethod: ClientAuthenticationMethod; userNameAttributeName: string; mapperConfig: MapperConfig; + id?: EntityId; + additionalInfo: string; } export interface MapperConfig { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 802575c0ff..50b74e393a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -166,8 +166,8 @@ "url": "URL", "url-required": "URL is required.", "url-pattern": "Invalid URL format.", - "login-button-label": "Login button label", - "login-button-label-required": "Login button label is required.", + "login-button-label": "Label (Login with ${label})", + "login-button-label-required": "Label is required.", "login-button-icon": "Login button icon", "delete-domain-title": "Are you sure you want to delete the domain '{{domainName}}'?", "delete-domain-text": "Be careful, after the confirmation a domain and all registration data will be unavailable.",