diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index d11a8f373e..9423392773 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -60,27 +60,16 @@ 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, - 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}`, + public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, config?: RequestConfig): Observable { + return this.http.post('/api/oauth2/config', OAuth2Setting, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index 189550d1c2..73fef6a32f 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -24,7 +24,6 @@ import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-sett import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; -import { EditRedirectUriPanelComponent } from './edit-redirect-uri-panel.component'; @NgModule({ declarations: @@ -32,8 +31,7 @@ import { EditRedirectUriPanelComponent } from './edit-redirect-uri-panel.compone GeneralSettingsComponent, MailServerComponent, SecuritySettingsComponent, - OAuth2SettingsComponent, - EditRedirectUriPanelComponent + OAuth2SettingsComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html deleted file mode 100644 index 6f4efc061c..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html +++ /dev/null @@ -1,49 +0,0 @@ - -
-
- - admin.oauth2.redirect-uri-template - - - {{ 'admin.oauth2.redirect-uri-template-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
-
- - - -
-
diff --git a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss deleted file mode 100644 index 43b16cf165..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -:host { - background-color: #f9f9f9; -} diff --git a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts deleted file mode 100644 index 44f6bff0f3..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts +++ /dev/null @@ -1,76 +0,0 @@ -/// -/// Copyright © 2016-2020 The Thingsboard Authors -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// - -import { Component, Inject, InjectionToken, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher } from '@angular/material/core'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; -import { PageComponent } from '@shared/components/page.component'; -import { OverlayRef } from '@angular/cdk/overlay'; - -export const EDIT_REDIRECT_URI_PANEL_DATA = new InjectionToken('EditRedirectUriPanelData'); - -export interface EditRedirectUriPanelData { - redirectURI: any; -} - -@Component({ - selector: 'tb-edit-redirect-uri-panel', - templateUrl: './edit-redirect-uri-panel.component.html', - providers: [{provide: ErrorStateMatcher, useExisting: EditRedirectUriPanelComponent}], - styleUrls: ['./edit-redirect-uri-panel.component.scss'] -}) -export class EditRedirectUriPanelComponent extends PageComponent implements OnInit, ErrorStateMatcher { - - private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/; - - redirectURIFormGroup: FormGroup; - - result: any = null; - - submitted = false; - - constructor(protected store: Store, - @Inject(EDIT_REDIRECT_URI_PANEL_DATA) public data: EditRedirectUriPanelData, - @SkipSelf() private errorStateMatcher: ErrorStateMatcher, - public overlayRef: OverlayRef, - public fb: FormBuilder) { - super(store); - } - - ngOnInit(): void { - this.redirectURIFormGroup = this.fb.group({ - value: [this.data.redirectURI, [Validators.required, Validators.pattern(this.URL_REGEXP)]] - }); - } - - isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { - const originalErrorState = this.errorStateMatcher.isErrorState(control, form); - const customErrorState = !!(control && control.invalid && this.submitted); - return originalErrorState || customErrorState; - } - - cancel(): void { - this.overlayRef.dispose(); - } - - update(): void { - this.submitted = true; - this.result = this.redirectURIFormGroup.get('value').value; - this.overlayRef.dispose(); - } -} 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 9a0d9a1858..6e0048612c 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,23 +30,20 @@
- -
- - - - - - {{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.oauth2.new-domain" | translate) }} - - - + + {{ 'admin.oauth2.enable' | translate }} + +
+ +
+ + + + + + {{ domainListTittle(domain) }} + + - - - - - - admin.domain-name - - - {{ 'admin.error-verification-url' | translate }} - - - {{ 'admin.domain-name-unique' | translate }} - - - - -
- - - - {{ getProviderName(registration) }} - - - - - - - -
-
- - admin.oauth2.login-provider - - - {{ provider | uppercase }} + + + + + +
+
+
+
+ + admin.oauth2.protocol + + + {{ domainSchemaTranslations.get(protocol) | translate | uppercase }} -
-
-
- admin.oauth2.client-id - - - {{ 'admin.oauth2.client-id-required' | translate }} + admin.domain-name + + + {{ 'admin.error-verification-url' | translate }} + + + {{ 'admin.domain-name-unique' | translate }} +
+
- admin.oauth2.client-secret - - - {{ 'admin.oauth2.client-secret-required' | translate }} - + admin.oauth2.redirect-uri-template + + + + + + + +
- - - - {{ 'admin.oauth2.custom-setting' | translate }} - - - - - -
- - admin.oauth2.access-token-uri - - - - {{ 'admin.oauth2.access-token-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - +
- - admin.oauth2.authorization-uri - - - - {{ 'admin.oauth2.authorization-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
- -
- - admin.oauth2.jwk-set-uri - - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - +
+ +
+
+
+
+ +
+
+ + +
+ + + + {{ getProviderName(registration) }} + + + + + + + +
+
+ + admin.oauth2.login-provider + + + {{ provider | uppercase }} + + + +
+
+ +
+ + admin.oauth2.client-id + + + {{ 'admin.oauth2.client-id-required' | translate }} + + + + + admin.oauth2.client-secret + + + {{ 'admin.oauth2.client-secret-required' | translate }} + + +
+ + + + + {{ 'admin.oauth2.custom-setting' | translate }} + + + + + +
+ + admin.oauth2.access-token-uri + + + + {{ 'admin.oauth2.access-token-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + - - admin.oauth2.user-info-uri - - - - {{ 'admin.oauth2.user-info-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
- - - admin.oauth2.client-authentication-method - - - {{ clientAuthenticationMethod | uppercase }} - - - - -
- - admin.oauth2.login-button-label - - - {{ 'admin.oauth2.login-button-label-required' | translate }} - - + + admin.oauth2.authorization-uri + + + + {{ 'admin.oauth2.authorization-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + +
- - admin.oauth2.login-button-icon - - -
- -
-
- - {{ 'admin.oauth2.allow-user-creation' | translate }} - - - {{ 'admin.oauth2.activate-user' | translate }} - +
+ + admin.oauth2.jwk-set-uri + + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + + + + admin.oauth2.user-info-uri + + + + {{ 'admin.oauth2.user-info-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + +
-
- - - admin.oauth2.scope - - - {{scope}} - cancel - - - - - - - - - admin.oauth2.user-name-attribute-name - - - {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} - - - -
+ - admin.oauth2.type - - - {{ converterTypeExternalUser }} + admin.oauth2.client-authentication-method + + + {{ clientAuthenticationMethod | uppercase }} -
- - admin.oauth2.email-attribute-key - - - {{ 'admin.oauth2.email-attribute-key-required' | translate }} +
+ + admin.oauth2.login-button-label + + + {{ 'admin.oauth2.login-button-label-required' | translate }} -
- - admin.oauth2.first-name-attribute-key - - + + admin.oauth2.login-button-icon + + +
- - admin.oauth2.last-name-attribute-key - - +
+
+ + {{ 'admin.oauth2.allow-user-creation' | translate }} + + + {{ 'admin.oauth2.activate-user' | translate }} +
+
-
- - admin.oauth2.tenant-name-strategy - - - {{ tenantNameStrategy }} - - - + + admin.oauth2.scope + + + {{scope}} + cancel + + + + + {{ 'admin.oauth2.scope-required' | translate }} + + - - admin.oauth2.tenant-name-pattern - - - {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} - - -
+ + + + admin.oauth2.user-name-attribute-name + + + {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} + + +
- admin.oauth2.customer-name-pattern - + admin.oauth2.type + + + {{ converterTypeExternalUser }} + + -
- - admin.oauth2.default-dashboard-name - +
+ + admin.oauth2.email-attribute-key + + + {{ 'admin.oauth2.email-attribute-key-required' | translate }} + - - {{ 'admin.oauth2.always-fullscreen' | translate}} - -
-
+
+ + admin.oauth2.first-name-attribute-key + + + + + admin.oauth2.last-name-attribute-key + + +
+ +
+ + admin.oauth2.tenant-name-strategy + + + {{ tenantNameStrategy }} + + + + + + admin.oauth2.tenant-name-pattern + + + {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} + + +
-
- - admin.oauth2.url - - - {{ 'admin.oauth2.url-required' | translate }} - - - {{ 'admin.oauth2.url-pattern' | translate }} - - - -
- common.username - + admin.oauth2.customer-name-pattern + - - common.password - +
+ + admin.oauth2.default-dashboard-name + + + + + {{ 'admin.oauth2.always-fullscreen' | translate}} + +
+
+ +
+ + admin.oauth2.url + + + {{ 'admin.oauth2.url-required' | translate }} + + + {{ 'admin.oauth2.url-pattern' | translate }} + -
+ +
+ + common.username + + + + + common.password + + +
+
- -
- - - - - - - + + + + + + + + +
+
+ +
+
- - -
- -
- - - - - - - - -
- - -
+ + + + + + + +
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss index ae7e94a5ac..0ad644f61b 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss @@ -15,11 +15,11 @@ */ :host{ .checkbox-row { - margin-top: 16px; + margin-top: 1em; } .registration-card{ - margin-bottom: 8px; + margin-bottom: 0.5em; border: 1px solid rgba(0, 0, 0, 0.2); .custom-settings{ @@ -31,7 +31,15 @@ } .container{ - margin-bottom: 16px; + margin-bottom: 1em; + + .tb-highlight{ + margin: 0; + } + + .tb-hint{ + padding-bottom: 0; + } } } @@ -39,7 +47,7 @@ .registration-card{ .custom-settings{ .mat-expansion-panel-body{ - padding: 0 2px 16px; + padding: 0 2px 1em; } .mat-tab-label{ text-transform: none; @@ -49,4 +57,10 @@ } } } + .domains-list{ + margin-bottom: 1.5em; + .mat-form-field-suffix .mat-icon-button .mat-icon{ + font-size: 24px; + } + } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index fc0729c3e1..b2ffc2bb6e 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -14,12 +14,16 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, ViewContainerRef } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ClientAuthenticationMethod, ClientProviderTemplated, ClientRegistration, + DomainInfo, + DomainSchema, + domainSchemaTranslations, + DomainsParam, MapperConfig, MapperConfigBasic, MapperConfigCustom, @@ -38,13 +42,7 @@ import { WINDOW } from '@core/services/window.service'; import { forkJoin, Subscription } from 'rxjs'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; -import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; -import { - EDIT_REDIRECT_URI_PANEL_DATA, - EditRedirectUriPanelComponent, - EditRedirectUriPanelData -} from './edit-redirect-uri-panel.component'; +import { isDefined } from '@core/utils'; @Component({ selector: 'tb-oauth2-settings', @@ -60,15 +58,15 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha additionalInfo: { providerName: 'Custom' }, - clientAuthenticationMethod: 'POST', + clientAuthenticationMethod: ClientAuthenticationMethod.POST, userNameAttributeName: 'email', mapperConfig: { allowUserCreation: true, activateUser: false, - type: 'BASIC', + type: MapperConfigType.BASIC, basic: { emailAttributeKey: 'email', - tenantNameStrategy: 'DOMAIN', + tenantNameStrategy: TenantNameStrategy.DOMAIN, alwaysFullScreen: false } } @@ -77,19 +75,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha readonly separatorKeysCodes: number[] = [ENTER, COMMA]; oauth2SettingsForm: FormGroup; - oauth2Settings: OAuth2Settings[]; + oauth2Settings: OAuth2Settings; - clientAuthenticationMethods: ClientAuthenticationMethod[] = ['BASIC', 'POST']; - converterTypesExternalUser: MapperConfigType[] = ['BASIC', 'CUSTOM']; - tenantNameStrategies: TenantNameStrategy[] = ['DOMAIN', 'EMAIL', 'CUSTOM']; + clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod); + converterTypesExternalUser = Object.keys(MapperConfigType); + tenantNameStrategies = Object.keys(TenantNameStrategy); + protocols = Object.keys(DomainSchema); + domainSchemaTranslations = domainSchemaTranslations; templateProvider = ['Custom']; constructor(protected store: Store, private adminService: AdminService, - private overlay: Overlay, - private viewContainerRef: ViewContainerRef, - private cd: ChangeDetectorRef, private fb: FormBuilder, private dialogService: DialogService, private translate: TranslateService, @@ -124,8 +121,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.templateProvider.sort(); } - get clientDomains(): FormArray { - return this.oauth2SettingsForm.get('clientDomains') as FormArray; + get domainsParams(): FormArray { + return this.oauth2SettingsForm.get('domainsParams') as FormArray; } private formBasicGroup(mapperConfigBasic?: MapperConfigBasic): FormGroup { @@ -139,7 +136,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required], firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''], lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''], - tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : 'DOMAIN'], + tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], tenantNamePattern: [tenantNamePattern, Validators.required], customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null], defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null], @@ -167,69 +164,78 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private buildOAuth2SettingsForm(): void { this.oauth2SettingsForm = this.fb.group({ - clientDomains: this.fb.array([]) + domainsParams: this.fb.array([]), + enabled: [false] }); } - private initOAuth2Settings(oauth2Settings: OAuth2Settings[]): void { + private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { if (oauth2Settings) { - oauth2Settings.forEach((domain) => { - this.clientDomains.push(this.buildSettingsDomain(domain)); + this.oauth2SettingsForm.patchValue({enabled: oauth2Settings.enabled}, {emitEvent: false}); + oauth2Settings.domainsParams.forEach((domain) => { + this.domainsParams.push(this.buildDomainsForm(domain)); }); } } private uniqueDomainValidator(control: AbstractControl): { [key: string]: boolean } | null { - if (control.root.value.clientDomains?.length > 1) { - const listDomainName = []; - control.root.value.clientDomains.forEach((domain) => { - listDomainName.push(domain.domainName); - }); - if (listDomainName.indexOf(control.value) > -1) { + if (control.parent?.parent?.value) { + const domain = control.value; + const listProtocols = control.parent.parent.value + .filter((domainInfo) => domainInfo.name === domain) + .map((domainInfo) => domainInfo.scheme); + if (listProtocols.length > 1 && listProtocols.indexOf(DomainSchema.MIXED) > -1) { return {unique: true}; } } return null; } - 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') { - url += ':' + port; + public domainListTittle(control: AbstractControl): string { + const domainInfos = control.get('domainInfos').value as DomainInfo[]; + if (domainInfos.length) { + const domainList = new Set(); + domainInfos.forEach((domain) => { + domainList.add(domain.name); + }); + return Array.from(domainList).join(', '); } - url += '/login/oauth2/code/'; + return this.translate.instant('admin.oauth2.new-domain'); + } + + private buildDomainsForm(domainParams?: DomainsParam): FormGroup { const formDomain = this.fb.group({ - 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)]], + domainInfos: this.fb.array([], Validators.required), clientRegistrations: this.fb.array([], Validators.required) }); - this.subscriptions.push(formDomain.get('domainName').valueChanges.subscribe((domain) => { - if (!domain) { - domain = this.window.location.hostname; - } - const uri = this.window.location.protocol + `//${domain}/login/oauth2/code/`; - formDomain.get('redirectUriTemplate').patchValue(uri, {emitEvent: false}); - })); - if (domainParams) { + domainParams.domainInfos.forEach((domain) => { + this.clientDomainInfos(formDomain).push(this.buildDomainForm(domain)); + }); domainParams.clientRegistrations.forEach((registration) => { - this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration(registration)); + this.clientDomainProviders(formDomain).push(this.buildProviderForm(registration)); }); } else { - this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration()); + this.clientDomainProviders(formDomain).push(this.buildProviderForm()); + this.clientDomainInfos(formDomain).push(this.buildDomainForm()); } return formDomain; } - private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup { + private buildDomainForm(domainInfo?: DomainInfo): FormGroup { + const domain = this.fb.group({ + name: [domainInfo ? domainInfo.name : this.window.location.hostname, [ + Validators.required, + Validators.pattern('((?![:/]).)*$'), + this.uniqueDomainValidator]], + scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required] + }); + return domain; + } + + private buildProviderForm(registrationData?: ClientRegistration): FormGroup { let additionalInfo = null; if (registrationData?.additionalInfo) { additionalInfo = JSON.parse(registrationData.additionalInfo); @@ -237,13 +243,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha additionalInfo.providerName = 'Custom'; } } + let defaultProviderName = 'Custom'; + if (this.templateProvider.indexOf('Google')) { + defaultProviderName = 'Google'; + } + const clientRegistration = this.fb.group({ 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] + providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required] }), loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required], loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null], @@ -255,19 +266,20 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - scope: this.fb.array(registrationData?.scope ? registrationData.scope : []), + scope: this.fb.array(registrationData?.scope ? registrationData.scope : [], Validators.required), 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], + registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : ClientAuthenticationMethod.POST, + Validators.required], userNameAttributeName: [ registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required], mapperConfig: this.fb.group({ 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] + type: [registrationData?.mapperConfig?.type ? registrationData.mapperConfig.type : MapperConfigType.BASIC, Validators.required] } ) }); @@ -275,7 +287,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha if (registrationData) { this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig); } else { - this.changeMapperConfigType(clientRegistration, 'BASIC'); + this.changeMapperConfigType(clientRegistration, MapperConfigType.BASIC); } this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => { @@ -313,7 +325,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) { const mapperConfig = control.get('mapperConfig') as FormGroup; - if (type === 'BASIC') { + if (type === MapperConfigType.BASIC) { mapperConfig.removeControl('custom'); mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); } else { @@ -323,7 +335,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } save(): void { - const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue().clientDomains); + const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue()); this.adminService.saveOAuth2Settings(setting).subscribe( (oauth2Settings) => { this.oauth2Settings = oauth2Settings; @@ -333,20 +345,16 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha ); } - private prepareFormValue(formValue: OAuth2Settings[]): OAuth2Settings[]{ - const prepereValue = []; - formValue.forEach((setting, index) => { - if (this.clientDomains.at(index).dirty) { - setting.clientRegistrations.forEach((registration) => { - registration.additionalInfo = JSON.stringify(registration.additionalInfo); - if (registration.id.id === null) { - delete registration.id; - } - }); - prepereValue.push(setting); - } + private prepareFormValue(formValue: OAuth2Settings): OAuth2Settings{ + formValue.domainsParams.forEach((setting, index) => { + setting.clientRegistrations.forEach((registration) => { + registration.additionalInfo = JSON.stringify(registration.additionalInfo); + if (registration.id.id === null) { + delete registration.id; + } + }); }); - return prepereValue; + return formValue; } confirmForm(): FormGroup { @@ -359,6 +367,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha const controller = control.get('scope') as FormArray; if ((value.trim() !== '')) { controller.push(this.fb.control(value.trim())); + controller.markAsDirty(); } if (input) { @@ -369,10 +378,12 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha removeScope(i: number, control: AbstractControl): void { const controller = control.get('scope') as FormArray; controller.removeAt(i); + controller.markAsTouched(); + controller.markAsDirty(); } addDomain(): void { - this.clientDomains.push(this.buildSettingsDomain()); + this.domainsParams.push(this.buildDomainsForm()); } deleteDomain($event: Event, index: number): void { @@ -381,97 +392,48 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha $event.preventDefault(); } - const domainName = this.clientDomains.at(index).get('domainName').value; + const domainName = this.domainListTittle(this.domainsParams.at(index)); 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) { - 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); - } + this.domainsParams.removeAt(index); + this.domainsParams.markAsTouched(); + this.domainsParams.markAsDirty(); } }); } - clientDomainRegistrations(control: AbstractControl): FormArray { + clientDomainProviders(control: AbstractControl): FormArray { return control.get('clientRegistrations') as FormArray; } - addRegistration(control: AbstractControl): void { - this.clientDomainRegistrations(control).push(this.buildSettingsRegistration()); + clientDomainInfos(control: AbstractControl): FormArray { + return control.get('domainInfos') as FormArray; } - deleteRegistration($event: Event, control: AbstractControl, index: number): void { + addProvider(control: AbstractControl): void { + this.clientDomainProviders(control).push(this.buildProviderForm()); + } + + deleteProvider($event: Event, control: AbstractControl, index: number): void { if ($event) { $event.stopPropagation(); $event.preventDefault(); } - const providerName = this.clientDomainRegistrations(control).at(index).get('additionalInfo.providerName').value; + const providerName = this.clientDomainProviders(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) { - 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); - } - } - }); - } - - editRedirectURI($event: MouseEvent, index: number) { - if ($event) { - $event.stopPropagation(); - $event.preventDefault(); - } - const target = $event.target || $event.currentTarget; - const config = new OverlayConfig(); - config.backdropClass = 'cdk-overlay-transparent-backdrop'; - config.hasBackdrop = true; - const connectedPosition: ConnectedPosition = { - originX: 'end', - originY: 'bottom', - overlayX: 'end', - overlayY: 'top' - }; - config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) - .withPositions([connectedPosition]); - - const overlayRef = this.overlay.create(config); - overlayRef.backdropClick().subscribe(() => { - overlayRef.dispose(); - }); - - const redirectURI = this.clientDomains.at(index).get('redirectUriTemplate').value; - - const injectionTokens = new WeakMap([ - [EDIT_REDIRECT_URI_PANEL_DATA, { - redirectURI - } as EditRedirectUriPanelData], - [OverlayRef, overlayRef] - ]); - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); - const componentRef = overlayRef.attach(new ComponentPortal(EditRedirectUriPanelComponent, - this.viewContainerRef, injector)); - componentRef.onDestroy(() => { - if (componentRef.instance.result !== null) { - const attributeValue = componentRef.instance.result; - this.clientDomains.at(index).get('redirectUriTemplate').patchValue(attributeValue); - this.clientDomains.at(index).get('redirectUriTemplate').markAsDirty(); + this.clientDomainProviders(control).removeAt(index); + this.clientDomainProviders(control).markAsTouched(); + this.clientDomainProviders(control).markAsDirty(); } }); } @@ -491,4 +453,43 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } return this.templates.get(provider).helpLink; } + + addDomainInfo(control: AbstractControl): void { + this.clientDomainInfos(control).push(this.buildDomainForm({ + name: '', + scheme: DomainSchema.HTTPS + })); + } + + removeDomain($event: Event, control: AbstractControl, index: number): void { + if ($event) { + $event.stopPropagation(); + $event.preventDefault(); + } + this.clientDomainInfos(control).removeAt(index); + this.clientDomainInfos(control).markAsTouched(); + this.clientDomainInfos(control).markAsDirty(); + } + + redirectURI(control: AbstractControl, schema?: DomainSchema): string { + const domainInfo = control.value as DomainInfo; + if (domainInfo.name !== '') { + let protocol; + if (isDefined(schema)) { + protocol = schema.toLowerCase(); + } else { + protocol = domainInfo.scheme === DomainSchema.MIXED ? DomainSchema.HTTPS.toLowerCase() : domainInfo.scheme.toLowerCase(); + } + return `${protocol}://${domainInfo.name}/login/oauth2/code/`; + } + return ''; + } + + redirectURIMixed(control: AbstractControl): string { + return this.redirectURI(control, DomainSchema.HTTP); + } + + trackByParams(index: number): number { + return index; + } } diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index f27d80a260..f31dc5ebe8 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -26,10 +26,6 @@ export interface AdminSettings { export declare type SmtpProtocol = 'smtp' | 'smtps'; -export declare type ClientAuthenticationMethod = 'BASIC' | 'POST'; -export declare type MapperConfigType = 'BASIC' | 'CUSTOM'; -export declare type TenantNameStrategy = 'DOMAIN' | 'EMAIL' | 'CUSTOM'; - export interface MailServerSettings { mailFrom: string; smtpProtocol: SmtpProtocol; @@ -69,9 +65,43 @@ export interface UpdateMessage { } export interface OAuth2Settings { - domainName: string; - redirectUriTemplate: string; + enabled: boolean; + domainsParams: DomainsParam[]; +} + +export interface DomainsParam { clientRegistrations: ClientRegistration[]; + domainInfos: DomainInfo[]; +} + +export interface DomainInfo { + name: string; + scheme: DomainSchema; +} + +export enum DomainSchema{ + HTTP = 'HTTP', + HTTPS = 'HTTPS', + MIXED = 'MIXED' +} + +export const domainSchemaTranslations = new Map( + [ + [DomainSchema.HTTP, 'admin.oauth2.domain-schema-http'], + [DomainSchema.HTTPS, 'admin.oauth2.domain-schema-https'], + [DomainSchema.MIXED, 'admin.oauth2.domain-schema-mixed'] + ] +); + +export enum MapperConfigType{ + BASIC = 'BASIC', + CUSTOM = 'CUSTOM' +} + +export enum TenantNameStrategy{ + DOMAIN = 'DOMAIN', + EMAIL = 'EMAIL', + CUSTOM = 'CUSTOM' } export interface ClientProviderTemplated extends ClientRegistration{ @@ -100,6 +130,11 @@ export interface ClientRegistration { additionalInfo: string; } +export enum ClientAuthenticationMethod { + BASIC = 'BASIC', + POST = 'POST' +} + export interface MapperConfig { allowUserCreation: boolean; activateUser: boolean; 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 8cb452e075..6a4fc5e084 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -128,7 +128,9 @@ "access-token-uri-required": "Access token URI is required.", "activate-user": "Activate user", "add-domain": "Add domain", + "delete-domain": "Delete domain", "add-provider": "Add provider", + "delete-provider": "Delete provider", "allow-user-creation": "Allow user creation", "always-fullscreen": "Always fullscreen", "authorization-uri": "Authorization URI", @@ -141,10 +143,10 @@ "custom-setting": "Custom settings", "customer-name-pattern": "Customer name pattern", "default-dashboard-name": "Default dashboard name", - "delete-domain-text": "Be careful, after the confirmation a domain and all registration data will be unavailable.", - "delete-domain-title": "Are you sure you want to delete the domain '{{domainName}}'?", - "delete-registration-text": "Be careful, after the confirmation a registration data will be unavailable.", - "delete-registration-title": "Are you sure you want to delete the registration '{{name}}'?", + "delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.", + "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?", + "delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.", + "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?", "email-attribute-key": "Email attribute key", "email-attribute-key-required": "Email attribute key is required.", "first-name-attribute-key": "First name attribute key", @@ -160,11 +162,12 @@ "new-domain": "New domain", "oauth2": "OAuth2", "redirect-uri-template": "Redirect URI template", - "redirect-uri-template-required": "Redirect URI template is required.", + "copy-redirect-uri": "Copy redirect URI", "registration-id": "Registration ID", "registration-id-required": "Registration ID is required.", "registration-id-unique": "Registration ID need to unique for the system.", "scope": "Scope", + "scope-required": "Scope is required.", "tenant-name-pattern": "Tenant name pattern", "tenant-name-pattern-required": "Tenant name pattern is required.", "tenant-name-strategy": "Tenant name strategy", @@ -176,7 +179,12 @@ "user-info-uri": "User info URI", "user-info-uri-required": "User info URI is required.", "user-name-attribute-name": "User name attribute key", - "user-name-attribute-name-required": "User name attribute key is required" + "user-name-attribute-name-required": "User name attribute key is required", + "protocol": "Protocol", + "domain-schema-http": "HTTP", + "domain-schema-https": "HTTPS", + "domain-schema-mixed": "HTTP+HTTPS", + "enable": "Enable OAuth2 settings" } }, "alarm": {