Browse Source

Refactoring layout oauth2 templated

pull/3557/head
Vladyslav_Prykhodko 6 years ago
parent
commit
edcaebe9ac
  1. 6
      ui-ngx/src/app/core/http/admin.service.ts
  2. 4
      ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
  3. 49
      ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html
  4. 18
      ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss
  5. 76
      ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts
  6. 570
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html
  7. 1
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss
  8. 112
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts
  9. 5
      ui-ngx/src/app/shared/models/settings.models.ts
  10. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
ui-ngx/src/app/core/http/admin.service.ts

@ -63,8 +63,12 @@ export class AdminService {
return this.http.get<OAuth2Settings>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config));
}
public getOAuth2Template(config?: RequestConfig): Observable<any> {
return this.http.get<any>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config));
}
public saveOAuth2Settings(OAuth2Setting: OAuth2Settings,
config?: RequestConfig): Observable<OAuth2Settings> {
config?: RequestConfig): Observable<OAuth2Settings> {
return this.http.post<OAuth2Settings>('/api/oauth2/config', OAuth2Setting,
defaultHttpOptionsFromConfig(config));
}

4
ui-ngx/src/app/modules/home/pages/admin/admin.module.ts

@ -24,6 +24,7 @@ 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:
@ -31,7 +32,8 @@ import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settin
GeneralSettingsComponent,
MailServerComponent,
SecuritySettingsComponent,
OAuth2SettingsComponent
OAuth2SettingsComponent,
EditRedirectUriPanelComponent
],
imports: [
CommonModule,

49
ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html

@ -0,0 +1,49 @@
<!--
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.
-->
<form class="mat-elevation-z4"
[formGroup]="redirectURIFormGroup" (ngSubmit)="update()" style="width: 350px; padding: 12px 8px 0;">
<fieldset [disabled]="isLoading$ | async">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.redirect-uri-template</mat-label>
<input matInput formControlName="value" required>
<mat-error
*ngIf="redirectURIFormGroup.get('value').hasError('required')">
{{ 'admin.oauth2.redirect-uri-template-required' | translate }}
</mat-error>
<mat-error
*ngIf="redirectURIFormGroup.get('value').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
</fieldset>
<div fxLayout="row" class="tb-panel-actions">
<span fxFlex></span>
<button mat-button color="primary"
style="margin-right: 20px;"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || redirectURIFormGroup.invalid || !redirectURIFormGroup.dirty">
{{ 'action.update' | translate }}
</button>
</div>
</form>

18
ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss

@ -0,0 +1,18 @@
/**
* 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;
}

76
ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts

@ -0,0 +1,76 @@
///
/// 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<any>('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<AppState>,
@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();
}
}

570
ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html

@ -40,300 +40,316 @@
{{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.new-domain" | translate) }}
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center">
<button mat-icon-button (click)="deleteDomain($event, i)">
<mat-icon>delete</mat-icon>
</button>
<button mat-icon-button
type="button"
(click)="editRedirectURI($event, i)"
matTooltip="{{ 'admin.oauth2.redirect-uri-template' | translate }}"
matTooltipPosition="above">
<mat-icon>link</mat-icon>
</button>
<button mat-icon-button
type="button"
(click)="deleteDomain($event, i)"
matTooltip="{{ 'action.delete' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</mat-panel-description>
</mat-expansion-panel-header>
<mat-form-field class="mat-block">
<mat-label translate>admin.domain-name</mat-label>
<input matInput formControlName="domainName" required>
<mat-error *ngIf="domain.get('domainName').hasError('pattern')">
{{ 'admin.error-verification-url' | translate }}
</mat-error>
<mat-error *ngIf="domain.get('domainName').hasError('unique')">
{{ 'admin.domain-name-unique' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.redirect-uri-template</mat-label>
<input matInput formControlName="redirectUriTemplate" required>
<mat-error
*ngIf="domain.get('redirectUriTemplate').hasError('required')">
{{ 'admin.oauth2.redirect-uri-template-required' | translate }}
</mat-error>
<mat-error
*ngIf="domain.get('redirectUriTemplate').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
<ng-container formArrayName="clientRegistrations">
<div class="container">
<mat-card *ngFor="let registration of clientDomainRegistrations(domain).controls; let j = index;"
class="registration-card">
<section [formGroupName]="j">
<div fxLayoutAlign="end center">
<button mat-icon-button (click)="deleteRegistration($event, domain, j)">
<mat-icon>delete</mat-icon>
</button>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.registration-id</mat-label>
<input matInput formControlName="registrationId" required>
<mat-error *ngIf="registration.get('registrationId').hasError('required')">
{{ 'admin.oauth2.registration-id-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('registrationId').hasError('unique')">
{{ 'admin.oauth2.registration-id-unique' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-id</mat-label>
<input matInput formControlName="clientId" required>
<mat-error *ngIf="registration.get('clientId').hasError('required')">
{{ 'admin.oauth2.client-id-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-secret</mat-label>
<input matInput formControlName="clientSecret" required>
<mat-error *ngIf="registration.get('clientSecret').hasError('required')">
{{ 'admin.oauth2.client-secret-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.access-token-uri</mat-label>
<input matInput formControlName="accessTokenUri" required>
<mat-error *ngIf="registration.get('accessTokenUri').hasError('required')">
{{ 'admin.oauth2.access-token-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('accessTokenUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.authorization-uri</mat-label>
<input matInput formControlName="authorizationUri" required>
<mat-error *ngIf="registration.get('authorizationUri').hasError('required')">
{{ 'admin.oauth2.authorization-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('authorizationUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.scope</mat-label>
<mat-chip-list #scopeList>
<mat-chip *ngFor="let scope of registration.get('scope').value; let k = index;"
removable (removed)="removeScope(k, registration)">
{{scope}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input [matChipInputFor]="scopeList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
matChipInputAddOnBlur
(matChipInputTokenEnd)="addScope($event, registration)">
</mat-chip-list>
<mat-error *ngIf="registration.get('scope').hasError('required')">
{{ 'admin.oauth2.jwk-set-uri-required' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.jwk-set-uri</mat-label>
<input matInput formControlName="jwkSetUri" required>
<mat-error *ngIf="registration.get('jwkSetUri').hasError('required')">
{{ 'admin.oauth2.jwk-set-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('jwkSetUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.user-info-uri</mat-label>
<input matInput formControlName="userInfoUri" required>
<mat-error *ngIf="registration.get('userInfoUri').hasError('required')">
{{ 'admin.oauth2.user-info-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('userInfoUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-authentication-method</mat-label>
<mat-select formControlName="clientAuthenticationMethod">
<mat-option *ngFor="let clientAuthenticationMethod of clientAuthenticationMethods"
[value]="clientAuthenticationMethod">
{{ clientAuthenticationMethod | uppercase }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>admin.oauth2.user-name-attribute-name</mat-label>
<input matInput formControlName="userNameAttributeName" required>
<mat-error *ngIf="registration.get('userNameAttributeName').hasError('required')">
{{ 'admin.oauth2.user-name-attribute-name-required' | translate }}
</mat-error>
</mat-form-field>
<section formGroupName="mapperConfig">
<div fxLayout="column" fxLayoutGap="8px">
<mat-checkbox formControlName="allowUserCreation">
{{ 'admin.oauth2.allow-user-creation' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="activateUser">
{{ 'admin.oauth2.activate-user' | translate }}
</mat-checkbox>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.type</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let converterTypeExternalUser of converterTypesExternalUser"
[value]="converterTypeExternalUser">
{{ converterTypeExternalUser }}
</mat-option>
</mat-select>
</mat-form-field>
<section formGroupName="basic"
*ngIf="registration.get('mapperConfig.type').value === 'BASIC'">
<mat-form-field class="mat-block">
<mat-label translate>admin.oauth2.email-attribute-key</mat-label>
<input matInput formControlName="emailAttributeKey" required>
<mat-error
*ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('required')">
{{ 'admin.oauth2.email-attribute-key-required' | translate }}
</mat-error>
<ng-template matExpansionPanelContent>
<mat-form-field class="mat-block">
<mat-label translate>admin.domain-name</mat-label>
<input matInput formControlName="domainName" required>
<mat-error *ngIf="domain.get('domainName').hasError('pattern')">
{{ 'admin.error-verification-url' | translate }}
</mat-error>
<mat-error *ngIf="domain.get('domainName').hasError('unique')">
{{ 'admin.domain-name-unique' | translate }}
</mat-error>
</mat-form-field>
<ng-container formArrayName="clientRegistrations">
<div class="container">
<mat-expansion-panel *ngFor="let registration of clientDomainRegistrations(domain).controls; let j = index;"
class="registration-card mat-elevation-z0">
<mat-expansion-panel-header>
<mat-panel-title fxLayoutAlign="start center">
{{ registration.get('providerName').value }}
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center">
<button mat-icon-button
type="button"
(click)="deleteRegistration($event, domain, j)"
matTooltip="{{ 'action.delete' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section [formGroupName]="j">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>Login Provider</mat-label>
<mat-select formControlName="providerName">
<mat-option *ngFor="let provider of templateProvider" [value]="provider">
{{ provider | uppercase }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.first-name-attribute-key</mat-label>
<input matInput formControlName="firstNameAttributeKey">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.last-name-attribute-key</mat-label>
<input matInput formControlName="lastNameAttributeKey">
</mat-form-field>
</div>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.tenant-name-strategy</mat-label>
<mat-select formControlName="tenantNameStrategy">
<mat-option *ngFor="let tenantNameStrategy of tenantNameStrategies"
[value]="tenantNameStrategy">
{{ tenantNameStrategy }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.tenant-name-pattern</mat-label>
<input matInput
formControlName="tenantNamePattern"
[required]="registration.get('mapperConfig.basic.tenantNameStrategy').value === 'CUSTOM'">
<mat-error
*ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('required')">
{{ 'admin.oauth2.tenant-name-pattern-required' | translate }}
<mat-label translate>admin.oauth2.client-id</mat-label>
<input matInput formControlName="clientId" required>
<mat-error *ngIf="registration.get('clientId').hasError('required')">
{{ 'admin.oauth2.client-id-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.customer-name-pattern</mat-label>
<input matInput formControlName="customerNamePattern">
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.default-dashboard-name</mat-label>
<input matInput formControlName="defaultDashboardName">
<mat-label translate>admin.oauth2.client-secret</mat-label>
<input matInput formControlName="clientSecret" required>
<mat-error *ngIf="registration.get('clientSecret').hasError('required')">
{{ 'admin.oauth2.client-secret-required' | translate }}
</mat-error>
</mat-form-field>
<mat-checkbox fxFlex formControlName="alwaysFullScreen" class="checkbox-row">
{{ 'admin.oauth2.always-fullscreen' | translate}}
</mat-checkbox>
</div>
</section>
<section formGroupName="custom"
*ngIf="registration.get('mapperConfig.type').value === 'CUSTOM'">
<mat-form-field class="mat-block">
<mat-label translate>admin.oauth2.url</mat-label>
<input matInput formControlName="url" required>
<mat-error
*ngIf="registration.get('mapperConfig.custom.url').hasError('required')">
{{ 'admin.oauth2.url-required' | translate }}
</mat-error>
<mat-error
*ngIf="registration.get('mapperConfig.custom.url').hasError('pattern')">
{{ 'admin.oauth2.url-pattern' | translate }}
</mat-error>
</mat-form-field>
<mat-tab-group dynamicHeight>
<mat-tab label="General">
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" style="margin-top: 16px;">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.access-token-uri</mat-label>
<input matInput formControlName="accessTokenUri" required>
<mat-error *ngIf="registration.get('accessTokenUri').hasError('required')">
{{ 'admin.oauth2.access-token-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('accessTokenUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.authorization-uri</mat-label>
<input matInput formControlName="authorizationUri" required>
<mat-error *ngIf="registration.get('authorizationUri').hasError('required')">
{{ 'admin.oauth2.authorization-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('authorizationUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.jwk-set-uri</mat-label>
<input matInput formControlName="jwkSetUri" required>
<mat-error *ngIf="registration.get('jwkSetUri').hasError('required')">
{{ 'admin.oauth2.jwk-set-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('jwkSetUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.user-info-uri</mat-label>
<input matInput formControlName="userInfoUri" required>
<mat-error *ngIf="registration.get('userInfoUri').hasError('required')">
{{ 'admin.oauth2.user-info-uri-required' | translate }}
</mat-error>
<mat-error *ngIf="registration.get('userInfoUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-authentication-method</mat-label>
<mat-select formControlName="clientAuthenticationMethod">
<mat-option *ngFor="let clientAuthenticationMethod of clientAuthenticationMethods"
[value]="clientAuthenticationMethod">
{{ clientAuthenticationMethod | uppercase }}
</mat-option>
</mat-select>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" *ngIf="registration.get('providerName').value === 'Custom'">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.login-button-label</mat-label>
<input matInput formControlName="loginButtonLabel" required>
<mat-error
*ngIf="registration.get('loginButtonLabel').hasError('required')">
{{ 'admin.oauth2.login-button-label-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.login-button-icon</mat-label>
<input matInput formControlName="loginButtonIcon">
</mat-form-field>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.scope</mat-label>
<mat-chip-list #scopeList>
<mat-chip *ngFor="let scope of registration.get('scope').value; let k = index;"
removable (removed)="removeScope(k, registration)">
{{scope}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input [matChipInputFor]="scopeList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
matChipInputAddOnBlur
(matChipInputTokenEnd)="addScope($event, registration)">
</mat-chip-list>
<mat-error *ngIf="registration.get('scope').hasError('required')">
{{ 'admin.oauth2.jwk-set-uri-required' | translate }}
</mat-error>
</mat-form-field>
</mat-tab>
<mat-tab label="Mapper">
<mat-form-field class="mat-block" style="margin-top: 16px;">
<mat-label translate>admin.oauth2.user-name-attribute-name</mat-label>
<input matInput formControlName="userNameAttributeName" required>
<mat-error *ngIf="registration.get('userNameAttributeName').hasError('required')">
{{ 'admin.oauth2.user-name-attribute-name-required' | translate }}
</mat-error>
</mat-form-field>
<section formGroupName="mapperConfig">
<div fxLayout="column" fxLayoutGap="8px">
<mat-checkbox formControlName="allowUserCreation">
{{ 'admin.oauth2.allow-user-creation' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="activateUser">
{{ 'admin.oauth2.activate-user' | translate }}
</mat-checkbox>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.type</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let converterTypeExternalUser of converterTypesExternalUser"
[value]="converterTypeExternalUser">
{{ converterTypeExternalUser }}
</mat-option>
</mat-select>
</mat-form-field>
<section formGroupName="basic"
*ngIf="registration.get('mapperConfig.type').value === 'BASIC'">
<mat-form-field class="mat-block">
<mat-label translate>admin.oauth2.email-attribute-key</mat-label>
<input matInput formControlName="emailAttributeKey" required>
<mat-error
*ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('required')">
{{ 'admin.oauth2.email-attribute-key-required' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.first-name-attribute-key</mat-label>
<input matInput formControlName="firstNameAttributeKey">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.last-name-attribute-key</mat-label>
<input matInput formControlName="lastNameAttributeKey">
</mat-form-field>
</div>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.tenant-name-strategy</mat-label>
<mat-select formControlName="tenantNameStrategy">
<mat-option *ngFor="let tenantNameStrategy of tenantNameStrategies"
[value]="tenantNameStrategy">
{{ tenantNameStrategy }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" *ngIf="registration.get('mapperConfig.basic.tenantNameStrategy').value === 'CUSTOM'">
<mat-label translate>admin.oauth2.tenant-name-pattern</mat-label>
<input matInput
formControlName="tenantNamePattern"
[required]="registration.get('mapperConfig.basic.tenantNameStrategy').value === 'CUSTOM'">
<mat-error
*ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('required')">
{{ 'admin.oauth2.tenant-name-pattern-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.customer-name-pattern</mat-label>
<input matInput formControlName="customerNamePattern">
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.default-dashboard-name</mat-label>
<input matInput formControlName="defaultDashboardName">
</mat-form-field>
<mat-checkbox fxFlex formControlName="alwaysFullScreen" class="checkbox-row">
{{ 'admin.oauth2.always-fullscreen' | translate}}
</mat-checkbox>
</div>
</section>
<section formGroupName="custom"
*ngIf="registration.get('mapperConfig.type').value === 'CUSTOM'">
<mat-form-field class="mat-block">
<mat-label translate>admin.oauth2.url</mat-label>
<input matInput formControlName="url" required>
<mat-error
*ngIf="registration.get('mapperConfig.custom.url').hasError('required')">
{{ 'admin.oauth2.url-required' | translate }}
</mat-error>
<mat-error
*ngIf="registration.get('mapperConfig.custom.url').hasError('pattern')">
{{ 'admin.oauth2.url-pattern' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>common.username</mat-label>
<input matInput formControlName="username" autocomplete="new-username">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>common.password</mat-label>
<input matInput type="password" formControlName="password" autocomplete="new-password">
</mat-form-field>
</div>
</section>
</section>
</mat-tab>
</mat-tab-group>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>common.username</mat-label>
<input matInput formControlName="username" autocomplete="new-username">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>common.password</mat-label>
<input matInput type="password" formControlName="password" autocomplete="new-password">
</mat-form-field>
</div>
</section>
</section>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.login-button-label</mat-label>
<input matInput formControlName="loginButtonLabel" required>
<mat-error
*ngIf="registration.get('loginButtonLabel').hasError('required')">
{{ 'admin.oauth2.login-button-label-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.login-button-icon</mat-label>
<input matInput formControlName="loginButtonIcon">
</mat-form-field>
</div>
</section>
</mat-card>
</ng-template>
</mat-expansion-panel>
</div>
</ng-container>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="addRegistration(domain)"
type="button">
{{'admin.add-provider' | translate}}
</button>
</div>
</ng-container>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="addRegistration(domain)"
type="button">
{{'admin.add-registration' | translate}}
</button>
</div>
</ng-template>
</mat-expansion-panel>
</ng-container>

1
ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss

@ -20,6 +20,7 @@
.registration-card{
margin-bottom: 8px;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.container{

112
ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, ViewContainerRef } from '@angular/core';
import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import {
ClientAuthenticationMethod,
@ -32,9 +32,16 @@ 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 { 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';
@Component({
selector: 'tb-oauth2-settings',
@ -45,6 +52,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/;
private subscriptions: Subscription[] = [];
private templates = [];
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
@ -55,8 +63,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
converterTypesExternalUser: MapperConfigType[] = ['BASIC', 'CUSTOM'];
tenantNameStrategies: TenantNameStrategy[] = ['DOMAIN', 'EMAIL', 'CUSTOM'];
templateProvider = ['Custom'];
constructor(protected store: Store<AppState>,
private adminService: AdminService,
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private cd: ChangeDetectorRef,
private fb: FormBuilder,
private dialogService: DialogService,
private translate: TranslateService,
@ -66,8 +79,12 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
ngOnInit(): void {
this.buildOAuth2SettingsForm();
this.adminService.getOAuth2Settings().subscribe(
(oauth2Settings) => {
forkJoin([
this.adminService.getOAuth2Template(),
this.adminService.getOAuth2Settings()
]).subscribe(
([templates, oauth2Settings]) => {
this.initTemplates(templates);
this.oauth2Settings = oauth2Settings;
this.initOAuth2Settings(this.oauth2Settings);
this.oauth2SettingsForm.reset(this.oauth2Settings);
@ -79,7 +96,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
super.ngOnDestroy();
this.subscriptions.forEach((subscription) => {
subscription.unsubscribe();
})
});
}
private initTemplates(templates: any): void {
this.templates = templates;
templates.map(provider => this.templateProvider.push(provider.name));
this.templateProvider.sort();
}
get clientsDomainsParams(): FormArray {
@ -114,7 +137,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
url: [null, [Validators.required, Validators.pattern(this.URL_REGEXP)]],
username: [null],
password: [null]
})
});
}
private buildOAuth2SettingsForm(): void {
@ -136,7 +159,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
const listDomainName = [];
control.root.value.clientsDomainsParams.forEach((domain) => {
listDomainName.push(domain.domainName);
})
});
if (listDomainName.indexOf(control.value) > -1) {
return {unique: true};
}
@ -144,21 +167,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
return null;
}
private uniqueRegistrationIdValidator(control: AbstractControl): { [key: string]: boolean } | null {
if (control.value !== null && control?.root) {
const listRegistration = [];
control.root.value.clientsDomainsParams.forEach((domain) => {
domain.clientRegistrations.forEach((client) => {
listRegistration.push(client.registrationId);
})
})
if (listRegistration.indexOf(control.value) > -1) {
return {unique: true};
}
}
return null;
}
private buildSettingsDomain(domainParams?: DomainParams): FormGroup {
let url = this.window.location.protocol + '//' + this.window.location.hostname;
const port = this.window.location.port;
@ -174,7 +182,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
this.subscriptions.push(formDomain.get('domainName').valueChanges.subscribe((domain) => {
if (!domain) {
domain = this.window.location.hostname
domain = this.window.location.hostname;
}
const uri = this.window.location.protocol + `//${domain}/login/oauth2/code/`;
formDomain.get('redirectUriTemplate').patchValue(uri);
@ -183,7 +191,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
if (domainParams) {
domainParams.clientRegistrations.forEach((registration) => {
this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration(registration));
})
});
} else {
this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration());
}
@ -193,7 +201,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup {
const clientRegistration = this.fb.group({
registrationId: [null, [Validators.required, this.uniqueRegistrationIdValidator]],
providerName: ['Custom', [Validators.required]],
loginButtonLabel: [null, [Validators.required]],
loginButtonIcon: [null],
clientId: ['', [Validators.required]],
@ -227,8 +235,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
if (registrationData) {
registrationData.scope.forEach(() => {
(clientRegistration.get('scope') as FormArray).push(this.fb.control(''))
})
(clientRegistration.get('scope') as FormArray).push(this.fb.control(''));
});
if (registrationData.mapperConfig.type !== 'BASIC') {
clientRegistration.get('mapperConfig.type').patchValue('CUSTOM');
}
@ -288,7 +296,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
if (data) {
this.clientsDomainsParams.removeAt(index);
}
})
});
}
clientDomainRegistrations(control: AbstractControl): FormArray {
@ -305,15 +313,57 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
$event.preventDefault();
}
const registrationId = this.clientDomainRegistrations(controler).at(index).get('registrationId').value;
const providerName = this.clientDomainRegistrations(controler).at(index).get('providerName').value;
this.dialogService.confirm(
this.translate.instant('admin.oauth2.delete-registration-title', {name: registrationId || ''}),
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);
}
})
});
}
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.clientsDomainsParams.at(index).get('redirectUriTemplate').value;
const injectionTokens = new WeakMap<any, any>([
[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.clientsDomainsParams.at(index).get('redirectUriTemplate').patchValue(attributeValue, {emitEvent: true});
}
});
}
}

5
ui-ngx/src/app/shared/models/settings.models.ts

@ -76,7 +76,6 @@ export interface DomainParams {
}
export interface ClientRegistration {
registrationId: string;
loginButtonLabel: string;
loginButtonIcon: string;
clientId: string;
@ -86,9 +85,9 @@ export interface ClientRegistration {
scope: string[];
jwkSetUri: string;
userInfoUri: string;
clientAuthenticationMethod: ClientAuthenticationMethod
clientAuthenticationMethod: ClientAuthenticationMethod;
userNameAttributeName: string;
mapperConfig: MapperConfig
mapperConfig: MapperConfig;
}
export interface MapperConfig {

2
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -125,7 +125,7 @@
"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",
"add-provider": "Add provider",
"settings": "Settings",
"oauth2": {
"settings": "OAuth2 Settings",

Loading…
Cancel
Save