From 7af3fe0050a674b46888cd72fc28c88622e398c7 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 9 Jul 2020 13:47:35 +0300 Subject: [PATCH] Add system admin OAuth2 settings --- ui-ngx/src/app/core/http/admin.service.ts | 18 +- ui-ngx/src/app/core/services/menu.service.ts | 8 +- .../home/pages/admin/admin-routing.module.ts | 14 + .../modules/home/pages/admin/admin.module.ts | 4 +- .../admin/oauth2-settings.component.html | 364 ++++++++++++++++++ .../admin/oauth2-settings.component.scss | 28 ++ .../pages/admin/oauth2-settings.component.ts | 284 ++++++++++++++ .../home/pages/tenant/tenant.component.html | 3 + .../home/pages/tenant/tenant.component.ts | 11 +- ui-ngx/src/app/shared/models/constants.ts | 3 +- .../src/app/shared/models/settings.models.ts | 56 +++ .../assets/locale/locale.constant-en_US.json | 61 ++- 12 files changed, 845 insertions(+), 9 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index f575f56906..ddc2069d4a 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -18,7 +18,13 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models'; +import { + AdminSettings, + MailServerSettings, + OAuth2Settings, + SecuritySettings, + UpdateMessage +} from '@shared/models/settings.models'; @Injectable({ providedIn: 'root' @@ -53,6 +59,16 @@ export class AdminService { defaultHttpOptionsFromConfig(config)); } + public getOAuth2Settings(config?: RequestConfig): Observable { + return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); + } + + public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, + config?: RequestConfig): Observable { + return this.http.post('/api/oauth2/config', OAuth2Setting, + defaultHttpOptionsFromConfig(config)); + } + public checkUpdates(config?: RequestConfig): Observable { return this.http.get(`/api/admin/updates`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 6032bf24f3..ea04968f71 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -95,7 +95,7 @@ export class MenuService { name: 'admin.system-settings', type: 'toggle', path: '/settings', - height: '120px', + height: '160px', icon: 'settings', pages: [ { @@ -115,6 +115,12 @@ export class MenuService { type: 'link', path: '/settings/security-settings', icon: 'security' + }, + { + name: 'admin.oauth2.settings', + type: 'link', + path: '/settings/oauth2-settings', + icon: 'security' } ] } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 47f1a5261b..32c25c6e7d 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -22,6 +22,7 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { Authority } from '@shared/models/authority.enum'; import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; +import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; const routes: Routes = [ { @@ -77,6 +78,19 @@ const routes: Routes = [ icon: 'security' } } + }, + { + path: 'oauth2-settings', + component: OAuth2SettingsComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.oauth2.settings', + breadcrumb: { + label: 'admin.oauth2.settings', + icon: 'security' + } + } } ] } 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 48bcf56fd5..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 @@ -23,13 +23,15 @@ import { MailServerComponent } from '@modules/home/pages/admin/mail-server.compo import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; 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'; @NgModule({ declarations: [ GeneralSettingsComponent, MailServerComponent, - SecuritySettingsComponent + SecuritySettingsComponent, + OAuth2SettingsComponent ], imports: [ CommonModule, 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 new file mode 100644 index 0000000000..87f1871ec2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -0,0 +1,364 @@ + +
+ + +
+ admin.oauth2.settings + +
+
+
+ + +
+ +
+
+ +
+ + + + + + {{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.new-domain" | translate) }} + + + + + + + + admin.domain-name + + + {{ 'admin.error-verification-url' | translate }} + + + + + admin.oauth2.redirect-uri-template + + + {{ 'admin.oauth2.redirect-uri-template-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + + + +
+ +
+
+ +
+
+ + admin.oauth2.registration-id + + + {{ 'admin.oauth2.registration-id-required' | translate }} + + + + + admin.oauth2.client-name + + + {{ 'admin.oauth2.client-name-required' | translate }} + + +
+ +
+ + admin.oauth2.client-id + + + {{ 'admin.oauth2.client-id-required' | translate }} + + + + + admin.oauth2.client-secret + + + {{ 'admin.oauth2.client-secret-required' | 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.scope + + + {{scope}} + cancel + + + + + +
+ + admin.oauth2.jwk-set-uri + + + {{ 'admin.oauth2.jwk-set-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.user-name-attribute-name + + + {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} + + + +
+
+ + {{ 'admin.oauth2.allow-user-creation' | translate }} + + + {{ 'admin.oauth2.activate-user' | translate }} + +
+ + + admin.oauth2.type + + + {{ converterTypeExternalUser }} + + + + +
+ + admin.oauth2.email-attribute-key + + + {{ 'admin.oauth2.email-attribute-key-required' | 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.customer-name-pattern + + + +
+ + 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 + + +
+
+
+ +
+ + admin.oauth2.login-button-label + + + {{ 'admin.oauth2.login-button-label-required' | translate }} + + + + + admin.oauth2.login-button-icon + + +
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ + +
+
+
+
+
+
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 new file mode 100644 index 0000000000..95f47b7023 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss @@ -0,0 +1,28 @@ +/** + * 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{ + .checkbox-row { + margin-top: 16px; + } + + .registration-card{ + margin-bottom: 8px; + } + + .container{ + margin-bottom: 16px; + } +} 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 new file mode 100644 index 0000000000..d128ede633 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -0,0 +1,284 @@ +/// +/// 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, OnDestroy, OnInit } from '@angular/core'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ClientRegistration, DomainParams, OAuth2Settings } from '@shared/models/settings.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { AdminService } from '@core/http/admin.service'; +import { PageComponent } from '@shared/components/page.component'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { COMMA, ENTER } from '@angular/cdk/keycodes'; +import { MatChipInputEvent } from '@angular/material/chips'; +import { WINDOW } from '@core/services/window.service'; +import { Subscription } from 'rxjs'; +import { DialogService } from '@core/services/dialog.service'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'tb-oauth2-settings', + templateUrl: './oauth2-settings.component.html', + styleUrls: ['./oauth2-settings.component.scss', './settings-card.scss'] +}) +export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { + + private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/; + private subscriptions: Subscription[] = []; + + readonly separatorKeysCodes: number[] = [ENTER, COMMA]; + + oauth2SettingsForm: FormGroup; + oauth2Settings: OAuth2Settings; + + clientAuthenticationMethods = ['basic', 'post']; + converterTypesExternalUser = ['BASIC', 'CUSTOM']; + tenantNameStrategies = ['DOMAIN', 'EMAIL', 'CUSTOM']; + + constructor(protected store: Store, + private adminService: AdminService, + private fb: FormBuilder, + private dialogService: DialogService, + private translate: TranslateService, + @Inject(WINDOW) private window: Window) { + super(store); + } + + ngOnInit(): void { + this.buildOAuth2SettingsForm(); + this.adminService.getOAuth2Settings().subscribe( + (oauth2Settings) => { + this.oauth2Settings = oauth2Settings; + this.initOAuth2Settings(this.oauth2Settings); + this.oauth2SettingsForm.reset(this.oauth2Settings); + } + ); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }) + } + + get clientsDomainsParams(): FormArray { + return this.oauth2SettingsForm.get('clientsDomainsParams') as FormArray; + } + + private get formBasicGroup(): FormGroup { + const basicGroup = this.fb.group({ + emailAttributeKey: ['email', [Validators.required]], + firstNameAttributeKey: [''], + lastNameAttributeKey: [''], + tenantNameStrategy: ['DOMAIN'], + tenantNamePattern: [null], + customerNamePattern: [null], + defaultDashboardName: [null], + alwaysFullScreen: [false], + }); + + this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { + if (domain === 'CUSTOM') { + basicGroup.get('tenantNamePattern').setValidators(Validators.required); + } else { + basicGroup.get('tenantNamePattern').clearValidators(); + } + })); + + return basicGroup; + } + + get formCustomGroup(): FormGroup { + return this.fb.group({ + url: [null, [Validators.required, Validators.pattern(this.URL_REGEXP)]], + username: [null], + password: [null] + }) + } + + private buildOAuth2SettingsForm(): void { + this.oauth2SettingsForm = this.fb.group({ + clientsDomainsParams: this.fb.array([]) + }); + } + + private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { + oauth2Settings.clientsDomainsParams.forEach((domaindomain) => { + this.clientsDomainsParams.push(this.buildSettingsDomain(domaindomain)); + }); + } + + private buildSettingsDomain(domainParams?: DomainParams): FormGroup { + let url = this.window.location.protocol + '//' + this.window.location.hostname; + const port = this.window.location.port; + if (port !== '80' && port !== '443') { + url += ':' + port; + } + url += '/login/oauth2/code/'; + const formDomain = this.fb.group({ + domainName: ['', [Validators.required, Validators.pattern('((?![:/]).)*$')]], + redirectUriTemplate: [url, [Validators.required, Validators.pattern(this.URL_REGEXP)]], + clientRegistrations: this.fb.array([]) + }); + + 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); + })); + + if(domainParams){ + domainParams.clientRegistrations.forEach((registration) => { + this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration(registration)); + }) + } else { + this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration()); + } + + return formDomain; + } + + private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup { + const clientRegistration = this.fb.group({ + registrationId: [null, [Validators.required]], + clientName: [null, [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.required, Validators.pattern(this.URL_REGEXP)]], + userInfoUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], + clientAuthenticationMethod: ['post', [Validators.required]], + userNameAttributeName: ['email', [Validators.required]], + mapperConfig: this.fb.group({ + allowUserCreation: [true], + activateUser: [false], + type: ['BASIC', [Validators.required]], + basic: this.formBasicGroup + } + ) + }); + + 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); + } + })); + + 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; + } + + save(): void { + console.log(this.oauth2SettingsForm.value); + this.adminService.saveOAuth2Settings(this.oauth2SettingsForm.value).subscribe( + (oauth2Settings) => { + this.oauth2Settings = oauth2Settings; + this.oauth2SettingsForm.markAsPristine(); + this.oauth2SettingsForm.markAsUntouched(); + } + ); + } + + confirmForm(): FormGroup { + return this.oauth2SettingsForm; + } + + addScope(event: MatChipInputEvent, control: AbstractControl): void { + const input = event.input; + const value = event.value; + const controller = control.get('scope') as FormArray; + if ((value.trim() !== '')) { + controller.push(this.fb.control(value.trim())); + } + + if (input) { + input.value = ''; + } + } + + removeScope(i: number, control: AbstractControl): void { + const controller = control.get('scope') as FormArray; + controller.removeAt(i); + } + + addDomain(): void { + this.clientsDomainsParams.push(this.buildSettingsDomain()); + } + + deleteDomain($event: Event, index: number): void { + if ($event) { + $event.stopPropagation(); + $event.preventDefault(); + } + + const domainName = this.clientsDomainsParams.at(index).get('domainName').value; + this.dialogService.confirm( + this.translate.instant('admin.oauth2.delete-domain-title', {domainName}), + this.translate.instant('admin.oauth2.delete-domain-text'), null, + this.translate.instant('action.delete') + ).subscribe((data) => { + if (data) { + this.clientsDomainsParams.removeAt(index); + } + }) + } + + clientDomainRegistrations(control: AbstractControl): FormArray { + return control.get('clientRegistrations') as FormArray; + } + + addRegistration(control: AbstractControl): void { + this.clientDomainRegistrations(control).push(this.buildSettingsRegistration()); + } + + deleteRegistration($event: Event, controler: AbstractControl, index: number): void { + if ($event) { + $event.stopPropagation(); + $event.preventDefault(); + } + + const registrationId = this.clientDomainRegistrations(controler).at(index).get('registrationId').value; + this.dialogService.confirm( + this.translate.instant('admin.oauth2.delete-registration-title', {name: registrationId}), + this.translate.instant('admin.oauth2.delete-registration-text'), null, + this.translate.instant('action.delete') + ).subscribe((data) => { + if (data) { + this.clientDomainRegistrations(controler).removeAt(index); + } + }) + } +} diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html index 335a7d3c9a..f13809ffa6 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html @@ -54,6 +54,9 @@ tenant.description + + {{ 'tenant.allow-oauth2-configuration' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts index 8a76830093..0d55936039 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts @@ -23,6 +23,7 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti import { TranslateService } from '@ngx-translate/core'; import { ContactBasedComponent } from '../../components/entity/contact-based.component'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { isDefined } from '@core/utils'; @Component({ selector: 'tb-tenant', @@ -55,7 +56,9 @@ export class TenantComponent extends ContactBasedComponent { isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], additionalInfo: this.fb.group( { - description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + allowOAuth2Configuration: [isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ? + entity.additionalInfo.allowOAuth2Configuration : true] } ) } @@ -66,7 +69,11 @@ export class TenantComponent extends ContactBasedComponent { this.entityForm.patchValue({title: entity.title}); this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); - this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + this.entityForm.patchValue({additionalInfo: { + description: entity.additionalInfo ? entity.additionalInfo.description : '', + allowOAuth2Configuration: isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ? + entity.additionalInfo.allowOAuth2Configuration : true + }}); } updateFormState() { diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 4c9612489a..1b4570d848 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -58,6 +58,7 @@ export const HelpLinks = { linksMap: { outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', + oauth2Settings: helpBaseUrl + '/docs/user-guide/oauth-2-support/', ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/', ruleNodeCheckRelation: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-relation-filter-node', ruleNodeCheckExistenceFields: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-existence-fields-node', @@ -108,7 +109,7 @@ export const HelpLinks = { widgetsConfigLatest: helpBaseUrl + '/docs/user-guide/ui/dashboards#latest', widgetsConfigRpc: helpBaseUrl + '/docs/user-guide/ui/dashboards#rpc', widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/ui/dashboards#alarm', - widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static' + widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static', } }; diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index f71ac1bb2e..c52c759719 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -23,6 +23,10 @@ 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; @@ -60,3 +64,55 @@ export interface UpdateMessage { message: string; updateAvailable: boolean; } + +export interface OAuth2Settings { + clientsDomainsParams: DomainParams[]; +} + +export interface DomainParams { + domainName: string; + redirectUriTemplate: string; + clientRegistrations: ClientRegistration[]; +} + +export interface ClientRegistration { + registrationId: string; + clientName: string; + loginButtonLabel: string; + loginButtonIcon: string; + clientId: string; + clientSecret: string; + accessTokenUri: string; + authorizationUri: string; + scope: string[]; + jwkSetUri: string; + userInfoUri: string; + clientAuthenticationMethod: ClientAuthenticationMethod + userNameAttributeName: string; + mapperConfig: MapperConfig +} + +export interface MapperConfig { + allowUserCreation: boolean; + activateUser: boolean; + type: MapperConfigType; + basic?: MapperConfigBasic; + custom?: MapperConfigCustom; +} + +export interface MapperConfigBasic { + emailAttributeKey: string; + firstNameAttributeKey?: string; + lastNameAttributeKey?: string; + tenantNameStrategy: TenantNameStrategy; + tenantNamePattern?: string; + customerNamePattern?: string; + defaultDashboardName?: string; + alwaysFullScreen?: boolean; +} + +export interface MapperConfigCustom { + url: string; + username?: string; + password?: string; +} 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 0d4925d89f..d1817e89ea 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -119,8 +119,62 @@ "general-policy": "General policy", "max-failed-login-attempts": "Maximum number of failed login attempts, before account is locked", "minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative", - "user-lockout-notification-email": "In case user account lockout, send notification to email" - }, + "user-lockout-notification-email": "In case user account lockout, send notification to email", + "domain-name": "Domain name", + "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io", + "add-domain": "Add domain", + "new-domain": "New domain", + "add-registration": "Add registration", + "oauth2": { + "settings": "OAuth2 settings", + "registration-id": "Registration ID", + "registration-id-required": "Registration ID is required.", + "client-name": "Client name", + "client-name-required": "Client name is required.", + "client-id": "Client ID", + "client-id-required": "Client ID is required.", + "client-secret": "Client secret", + "client-secret-required": "Client secret is required.", + "access-token-uri": "Access token URI", + "access-token-uri-required": "Access token URI is required.", + "authorization-uri": "Authorization URI", + "authorization-uri-required": "Authorization URI is required.", + "uri-pattern-error": "Invalid URI format.", + "scope": "Scope", + "redirect-uri-template": "Redirect URI template", + "redirect-uri-template-required": "Redirect URI template is required.", + "jwk-set-uri": "JSON Web Key URI", + "jwk-set-uri-required": "JSON Web Key URI is required.", + "user-info-uri": "User info URI", + "user-info-uri-required": "User info URI is required.", + "client-authentication-method": "Client authentication method", + "user-name-attribute-name": "User name attribute key", + "user-name-attribute-name-required": "User name attribute key is required", + "allow-user-creation": "Allow user creation", + "activate-user": "Activate user", + "type": "Mapper type", + "email-attribute-key": "Email attribute key", + "email-attribute-key-required": "Email attribute key is required.", + "first-name-attribute-key": "First name attribute key", + "last-name-attribute-key": "Last name attribute key", + "tenant-name-strategy": "Tenant name strategy", + "tenant-name-pattern": "Tenant name pattern", + "tenant-name-pattern-required": "Tenant name pattern is required.", + "customer-name-pattern": "Customer name pattern", + "default-dashboard-name": "Default dashboard name", + "always-fullscreen": "Always fullscreen", + "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-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.", + "delete-registration-title": "Are you sure you want to delete the registration '{{name}}'?", + "delete-registration-text": "Be careful, after the confirmation a registration data will be unavailable." + } + }, "alarm": { "alarm": "Alarm", "alarms": "Alarms", @@ -1561,7 +1615,8 @@ "isolated-tb-core": "Processing in isolated ThingsBoard Core container", "isolated-tb-rule-engine": "Processing in isolated ThingsBoard Rule Engine container", "isolated-tb-core-details": "Requires separate microservice(s) per isolated Tenant", - "isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant" + "isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant", + "allow-oauth2-configuration": "Allow OAuth2 configuration" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",