Browse Source

UI: Added support multiple domains is OAuth2 settings

pull/3557/head
Vladyslav_Prykhodko 6 years ago
parent
commit
fe00a9bd45
  1. 19
      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. 720
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html
  7. 22
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss
  8. 283
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts
  9. 47
      ui-ngx/src/app/shared/models/settings.models.ts
  10. 20
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -60,27 +60,16 @@ export class AdminService {
defaultHttpOptionsFromConfig(config)); defaultHttpOptionsFromConfig(config));
} }
public getOAuth2Settings(config?: RequestConfig): Observable<Array<OAuth2Settings>> { public getOAuth2Settings(config?: RequestConfig): Observable<OAuth2Settings> {
return this.http.get<Array<OAuth2Settings>>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); return this.http.get<OAuth2Settings>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config));
} }
public getOAuth2Template(config?: RequestConfig): Observable<Array<ClientProviderTemplated>> { public getOAuth2Template(config?: RequestConfig): Observable<Array<ClientProviderTemplated>> {
return this.http.get<Array<ClientProviderTemplated>>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); return this.http.get<Array<ClientProviderTemplated>>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config));
} }
public saveOAuth2Settings(OAuth2Setting: OAuth2Settings[], public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, config?: RequestConfig): Observable<OAuth2Settings> {
config?: RequestConfig): Observable<Array<OAuth2Settings>> { return this.http.post<OAuth2Settings>('/api/oauth2/config', OAuth2Setting,
return this.http.post<Array<OAuth2Settings>>('/api/oauth2/config', OAuth2Setting,
defaultHttpOptionsFromConfig(config));
}
public deleteOAuth2Domain(OAuth2Domain: string, config?: RequestConfig) {
return this.http.delete(`/api/oauth2/config/domain/${OAuth2Domain}`,
defaultHttpOptionsFromConfig(config));
}
public deleteOAuthCclientRegistrationId(clientRegistrationId: string, config?: RequestConfig) {
return this.http.delete(`/api/oauth2/config/${clientRegistrationId}`,
defaultHttpOptionsFromConfig(config)); defaultHttpOptionsFromConfig(config));
} }

4
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 { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component';
import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component';
import { EditRedirectUriPanelComponent } from './edit-redirect-uri-panel.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -32,8 +31,7 @@ import { EditRedirectUriPanelComponent } from './edit-redirect-uri-panel.compone
GeneralSettingsComponent, GeneralSettingsComponent,
MailServerComponent, MailServerComponent,
SecuritySettingsComponent, SecuritySettingsComponent,
OAuth2SettingsComponent, OAuth2SettingsComponent
EditRedirectUriPanelComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

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

@ -1,49 +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.
-->
<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

@ -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;
}

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

@ -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<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();
}
}

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

@ -30,23 +30,20 @@
<mat-card-content style="padding-top: 16px;"> <mat-card-content style="padding-top: 16px;">
<form [formGroup]="oauth2SettingsForm" (ngSubmit)="save()"> <form [formGroup]="oauth2SettingsForm" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="isLoading$ | async">
<ng-container formArrayName="clientDomains"> <mat-checkbox formControlName="enabled">
<div class="container"> {{ 'admin.oauth2.enable' | translate }}
<mat-accordion multi> </mat-checkbox>
<ng-container *ngFor="let domain of clientDomains.controls; let i = index; let last = last;"> <section *ngIf="oauth2SettingsForm.get('enabled').value && !(isLoading$ | async)" style="margin-top: 1em;">
<mat-expansion-panel [formGroupName]="i" [expanded]="last"> <ng-container formArrayName="domainsParams">
<mat-expansion-panel-header> <div class="container">
<mat-panel-title fxLayoutAlign="start center"> <mat-accordion multi>
{{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.oauth2.new-domain" | translate) }} <ng-container *ngFor="let domain of domainsParams.controls; let i = index; trackBy: trackByParams">
</mat-panel-title> <mat-expansion-panel [formGroupName]="i">
<mat-panel-description fxLayoutAlign="end center"> <mat-expansion-panel-header>
<button mat-icon-button <mat-panel-title fxLayoutAlign="start center">
type="button" {{ domainListTittle(domain) }}
(click)="editRedirectURI($event, i)" </mat-panel-title>
matTooltip="{{ 'admin.oauth2.redirect-uri-template' | translate }}" <mat-panel-description fxLayoutAlign="end center">
matTooltipPosition="above">
<mat-icon>link</mat-icon>
</button>
<button mat-icon-button <button mat-icon-button
type="button" type="button"
(click)="deleteDomain($event, i)" (click)="deleteDomain($event, i)"
@ -54,357 +51,428 @@
matTooltipPosition="above"> matTooltipPosition="above">
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
</button> </button>
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<mat-form-field class="mat-block"> <ng-container formArrayName="domainInfos">
<mat-label translate>admin.domain-name</mat-label> <section *ngFor="let domainInfo of clientDomainInfos(domain).controls; let n = index; trackBy: trackByParams"
<input matInput formControlName="domainName" required> class="domains-list">
<mat-error *ngIf="domain.get('domainName').hasError('pattern')"> <div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
{{ 'admin.error-verification-url' | translate }} <div fxLayout="row" fxLayout.xs="column" fxFlex fxLayoutGap="8px">
</mat-error> <div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px" fxFlex.gt-xs="50">
<mat-error *ngIf="domain.get('domainName').hasError('unique')"> <mat-form-field fxFlex="30" fxFlex.xs class="mat-block">
{{ 'admin.domain-name-unique' | translate }} <mat-label translate>admin.oauth2.protocol</mat-label>
</mat-error> <mat-select formControlName="scheme">
</mat-form-field> <mat-option *ngFor="let protocol of protocols" [value]="protocol">
{{ domainSchemaTranslations.get(protocol) | translate | uppercase }}
<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">
{{ getProviderName(registration) }}
</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">
<section formGroupName="additionalInfo" fxLayout="row">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.login-provider</mat-label>
<mat-select formControlName="providerName">
<mat-option *ngFor="let provider of templateProvider" [value]="provider">
{{ provider | uppercase }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div [tb-help]="getHelpLink(registration)" *ngIf="getProviderName(registration) !== 'Custom'"></div>
</section>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-id</mat-label> <mat-label translate>admin.domain-name</mat-label>
<input matInput formControlName="clientId" required> <input matInput formControlName="name" required>
<mat-error *ngIf="registration.get('clientId').hasError('required')"> <mat-error *ngIf="domainInfo.get('name').hasError('pattern')">
{{ 'admin.oauth2.client-id-required' | translate }} {{ 'admin.error-verification-url' | translate }}
</mat-error>
<mat-error *ngIf="domainInfo.get('name').hasError('unique')">
{{ 'admin.domain-name-unique' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div>
<div fxFlex>
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-secret</mat-label> <mat-label translate>admin.oauth2.redirect-uri-template</mat-label>
<input matInput formControlName="clientSecret" required> <input matInput [value]="redirectURI(domainInfo)" readonly>
<mat-error *ngIf="registration.get('clientSecret').hasError('required')"> <button mat-icon-button color="primary" matSuffix type="button"
{{ 'admin.oauth2.client-secret-required' | translate }} ngxClipboard cbContent="{{ redirectURI(domainInfo) }}"
</mat-error> matTooltip="{{ 'admin.oauth2.copy-redirect-uri' | translate }}"
matTooltipPosition="above">
<mat-icon class="material-icons" svgIcon="mdi:clipboard-arrow-left"></mat-icon>
</button>
</mat-form-field>
<mat-form-field fxFlex *ngIf="domainInfo.get('scheme').value === 'MIXED'"
class="mat-block">
<mat-label></mat-label>
<input matInput [value]="redirectURIMixed(domainInfo)" readonly>
<button mat-icon-button color="primary" matSuffix type="button"
ngxClipboard cbContent="{{ redirectURIMixed(domainInfo) }}"
matTooltip="{{ 'admin.oauth2.copy-redirect-uri' | translate }}"
matTooltipPosition="above">
<mat-icon class="material-icons" svgIcon="mdi:clipboard-arrow-left"></mat-icon>
</button>
</mat-form-field> </mat-form-field>
</div> </div>
<mat-expansion-panel class="mat-elevation-z0 custom-settings" </div>
[disabled]="getProviderName(registration) === 'Custom'"
[expanded]="getProviderName(registration) === 'Custom'">
<mat-expansion-panel-header [fxHide]="getProviderName(registration) === 'Custom'">
<mat-panel-description fxLayoutAlign="end center">
{{ 'admin.oauth2.custom-setting' | translate }}
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<mat-tab-group dynamicHeight>
<mat-tab label="{{ 'admin.oauth2.general' | translate }}">
<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>
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'accessTokenUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<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"> <div fxLayout="column" fxLayoutAlign="center start">
<mat-label translate>admin.oauth2.authorization-uri</mat-label> <button type="button" mat-icon-button color="primary"
<input matInput formControlName="authorizationUri" required> (click)="removeDomain($event, domain, n)"
<button mat-icon-button matSuffix [disabled]="clientDomainInfos(domain).controls.length < 2"
type="button" matTooltip="{{ 'admin.oauth2.delete-domain' | translate }}"
(click)="toggleEditMode(registration, 'authorizationUri')" matTooltipPosition="above">
*ngIf="getProviderName(registration) !== 'Custom'"> <mat-icon>close</mat-icon>
<mat-icon class="material-icons">create</mat-icon> </button>
</button> </div>
<mat-error *ngIf="registration.get('authorizationUri').hasError('required')"> </div>
{{ 'admin.oauth2.authorization-uri-required' | translate }} </section>
</mat-error> <div fxLayout="row" fxLayoutAlign="end center" style="margin-bottom: 1.25em">
<mat-error *ngIf="registration.get('authorizationUri').hasError('pattern')"> <button mat-button mat-raised-button color="primary"
{{ 'admin.oauth2.uri-pattern-error' | translate }} [disabled]="(isLoading$ | async)"
</mat-error> (click)="addDomainInfo(domain)"
</mat-form-field> type="button">
</div> {{'admin.oauth2.add-domain' | translate}}
</button>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> </div>
<mat-form-field fxFlex class="mat-block" appearance="legacy"> </ng-container>
<mat-label translate>admin.oauth2.jwk-set-uri</mat-label>
<input matInput formControlName="jwkSetUri"> <ng-container formArrayName="clientRegistrations">
<button mat-icon-button matSuffix <div class="container">
type="button" aria-label="Clear" <mat-expansion-panel *ngFor="let registration of clientDomainProviders(domain).controls; let j = index; trackBy: trackByParams"
(click)="toggleEditMode(registration, 'jwkSetUri')" class="registration-card mat-elevation-z0">
*ngIf="getProviderName(registration) !== 'Custom'"> <mat-expansion-panel-header>
<mat-icon class="material-icons">create</mat-icon> <mat-panel-title fxLayoutAlign="start center">
</button> {{ getProviderName(registration) }}
<mat-error *ngIf="registration.get('jwkSetUri').hasError('pattern')"> </mat-panel-title>
{{ 'admin.oauth2.uri-pattern-error' | translate }} <mat-panel-description fxLayoutAlign="end center">
</mat-error> <button mat-icon-button
</mat-form-field> type="button"
[disabled]="clientDomainProviders(domain).controls.length < 2"
(click)="deleteProvider($event, domain, j)"
matTooltip="{{ 'admin.oauth2.delete-provider' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section [formGroupName]="j">
<section formGroupName="additionalInfo" fxLayout="row">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.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 [tb-help]="getHelpLink(registration)" *ngIf="getProviderName(registration) !== 'Custom'"></div>
</section>
<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>
<mat-expansion-panel class="mat-elevation-z0 custom-settings"
[disabled]="getProviderName(registration) === 'Custom'"
[expanded]="getProviderName(registration) === 'Custom'">
<mat-expansion-panel-header [fxHide]="getProviderName(registration) === 'Custom'">
<mat-panel-description fxLayoutAlign="end center">
{{ 'admin.oauth2.custom-setting' | translate }}
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<mat-tab-group dynamicHeight>
<mat-tab label="{{ 'admin.oauth2.general' | translate }}">
<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>
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'accessTokenUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<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-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.user-info-uri</mat-label> <mat-label translate>admin.oauth2.authorization-uri</mat-label>
<input matInput formControlName="userInfoUri" required> <input matInput formControlName="authorizationUri" required>
<button mat-icon-button matSuffix <button mat-icon-button matSuffix
type="button" type="button"
(click)="toggleEditMode(registration, 'userInfoUri')" (click)="toggleEditMode(registration, 'authorizationUri')"
*ngIf="getProviderName(registration) !== 'Custom'"> *ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon> <mat-icon class="material-icons">create</mat-icon>
</button> </button>
<mat-error *ngIf="registration.get('userInfoUri').hasError('required')"> <mat-error *ngIf="registration.get('authorizationUri').hasError('required')">
{{ 'admin.oauth2.user-info-uri-required' | translate }} {{ 'admin.oauth2.authorization-uri-required' | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="registration.get('userInfoUri').hasError('pattern')"> <mat-error *ngIf="registration.get('authorizationUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }} {{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> </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="getProviderName(registration) === 'Custom'">
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>admin.oauth2.login-button-label</mat-label>
<input matInput formControlName="loginButtonLabel" placeholder="{{ 'admin.oauth2.login-button-label-1' | translate }}" 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"> <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-label translate>admin.oauth2.login-button-icon</mat-label> <mat-form-field fxFlex class="mat-block" appearance="legacy">
<input matInput formControlName="loginButtonIcon"> <mat-label translate>admin.oauth2.jwk-set-uri</mat-label>
</mat-form-field> <input matInput formControlName="jwkSetUri">
</div> <button mat-icon-button matSuffix
type="button" aria-label="Clear"
<section formGroupName="mapperConfig"> (click)="toggleEditMode(registration, 'jwkSetUri')"
<div fxLayout="column" fxLayoutGap="8px" style="margin-bottom: 8px;"> *ngIf="getProviderName(registration) !== 'Custom'">
<mat-checkbox formControlName="allowUserCreation"> <mat-icon class="material-icons">create</mat-icon>
{{ 'admin.oauth2.allow-user-creation' | translate }} </button>
</mat-checkbox> <mat-error *ngIf="registration.get('jwkSetUri').hasError('pattern')">
<mat-checkbox formControlName="activateUser"> {{ 'admin.oauth2.uri-pattern-error' | translate }}
{{ 'admin.oauth2.activate-user' | translate }} </mat-error>
</mat-checkbox> </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>
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'userInfoUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<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> </div>
</section>
<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-form-field>
</mat-tab>
<mat-tab label="{{ 'admin.oauth2.mapper' | translate }}">
<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">
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.type</mat-label> <mat-label translate>admin.oauth2.client-authentication-method</mat-label>
<mat-select formControlName="type"> <mat-select formControlName="clientAuthenticationMethod">
<mat-option *ngFor="let converterTypeExternalUser of converterTypesExternalUser" <mat-option *ngFor="let clientAuthenticationMethod of clientAuthenticationMethods"
[value]="converterTypeExternalUser"> [value]="clientAuthenticationMethod">
{{ converterTypeExternalUser }} {{ clientAuthenticationMethod | uppercase }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<section formGroupName="basic" <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" *ngIf="getProviderName(registration) === 'Custom'">
*ngIf="registration.get('mapperConfig.type').value === 'BASIC'"> <mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-form-field class="mat-block"> <mat-label translate>admin.oauth2.login-button-label</mat-label>
<mat-label translate>admin.oauth2.email-attribute-key</mat-label> <input matInput formControlName="loginButtonLabel"
<input matInput formControlName="emailAttributeKey" required> placeholder="{{ 'admin.oauth2.login-button-label-1' | translate }}"
<mat-error required>
*ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('required')"> <mat-error *ngIf="registration.get('loginButtonLabel').hasError('required')">
{{ 'admin.oauth2.email-attribute-key-required' | translate }} {{ 'admin.oauth2.login-button-label-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> <mat-form-field fxFlex class="mat-block">
<mat-form-field fxFlex class="mat-block"> <mat-label translate>admin.oauth2.login-button-icon</mat-label>
<mat-label translate>admin.oauth2.first-name-attribute-key</mat-label> <input matInput formControlName="loginButtonIcon">
<input matInput formControlName="firstNameAttributeKey"> </mat-form-field>
</mat-form-field> </div>
<mat-form-field fxFlex class="mat-block"> <section formGroupName="mapperConfig">
<mat-label translate>admin.oauth2.last-name-attribute-key</mat-label> <div fxLayout="column" fxLayoutGap="8px" style="margin-bottom: 8px;">
<input matInput formControlName="lastNameAttributeKey"> <mat-checkbox formControlName="allowUserCreation">
</mat-form-field> {{ 'admin.oauth2.allow-user-creation' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="activateUser">
{{ 'admin.oauth2.activate-user' | translate }}
</mat-checkbox>
</div> </div>
</section>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> <mat-form-field fxFlex class="mat-block">
<mat-form-field fxFlex class="mat-block"> <mat-label translate>admin.oauth2.scope</mat-label>
<mat-label translate>admin.oauth2.tenant-name-strategy</mat-label> <mat-chip-list #scopeList>
<mat-select formControlName="tenantNameStrategy"> <mat-chip *ngFor="let scope of registration.get('scope').value; let k = index; trackBy: trackByParams"
<mat-option *ngFor="let tenantNameStrategy of tenantNameStrategies" removable (removed)="removeScope(k, registration)">
[value]="tenantNameStrategy"> {{scope}}
{{ tenantNameStrategy }} <mat-icon matChipRemove>cancel</mat-icon>
</mat-option> </mat-chip>
</mat-select> <input [matChipInputFor]="scopeList"
</mat-form-field> [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
matChipInputAddOnBlur
(matChipInputTokenEnd)="addScope($event, registration)">
</mat-chip-list>
<mat-error *ngIf="registration.get('scope').hasError('required')">
{{ 'admin.oauth2.scope-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" [fxShow]="registration.get('mapperConfig.basic.tenantNameStrategy').value === 'CUSTOM'"> </mat-tab>
<mat-label translate>admin.oauth2.tenant-name-pattern</mat-label> <mat-tab label="{{ 'admin.oauth2.mapper' | translate }}">
<input matInput <mat-form-field class="mat-block" style="margin-top: 16px;">
formControlName="tenantNamePattern" <mat-label translate>admin.oauth2.user-name-attribute-name</mat-label>
[required]="registration.get('mapperConfig.basic.tenantNameStrategy').value === 'CUSTOM'"> <input matInput formControlName="userNameAttributeName" required>
<mat-error <mat-error
*ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('required')"> *ngIf="registration.get('userNameAttributeName').hasError('required')">
{{ 'admin.oauth2.tenant-name-pattern-required' | translate }} {{ 'admin.oauth2.user-name-attribute-name-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div>
<section formGroupName="mapperConfig">
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.customer-name-pattern</mat-label> <mat-label translate>admin.oauth2.type</mat-label>
<input matInput formControlName="customerNamePattern"> <mat-select formControlName="type">
<mat-option *ngFor="let converterTypeExternalUser of converterTypesExternalUser"
[value]="converterTypeExternalUser">
{{ converterTypeExternalUser }}
</mat-option>
</mat-select>
</mat-form-field> </mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> <section formGroupName="basic"
<mat-form-field fxFlex class="mat-block"> *ngIf="registration.get('mapperConfig.type').value === 'BASIC'">
<mat-label translate>admin.oauth2.default-dashboard-name</mat-label> <mat-form-field class="mat-block">
<input matInput formControlName="defaultDashboardName"> <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> </mat-form-field>
<mat-checkbox fxFlex formControlName="alwaysFullScreen" class="checkbox-row"> <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
{{ 'admin.oauth2.always-fullscreen' | translate}} <mat-form-field fxFlex class="mat-block">
</mat-checkbox> <mat-label translate>admin.oauth2.first-name-attribute-key</mat-label>
</div> <input matInput formControlName="firstNameAttributeKey">
</section> </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" [fxShow]="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>
<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-form-field fxFlex class="mat-block">
<mat-label translate>common.username</mat-label> <mat-label translate>admin.oauth2.customer-name-pattern</mat-label>
<input matInput formControlName="username" autocomplete="new-username"> <input matInput formControlName="customerNamePattern">
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex class="mat-block"> <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-label translate>common.password</mat-label> <mat-form-field fxFlex class="mat-block">
<input matInput type="password" formControlName="password" autocomplete="new-password"> <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> </mat-form-field>
</div>
<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> </section>
</section> </mat-tab>
</mat-tab> </mat-tab-group>
</mat-tab-group> </ng-template>
</ng-template> </mat-expansion-panel>
</mat-expansion-panel>
</section>
</section> </ng-template>
</ng-template> </mat-expansion-panel>
</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)="addProvider(domain)"
type="button">
{{'admin.oauth2.add-provider' | translate}}
</button>
</div> </div>
</ng-container> </ng-template>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px"> </mat-expansion-panel>
<button mat-button mat-raised-button color="primary" </ng-container>
[disabled]="(isLoading$ | async)" </mat-accordion>
(click)="addRegistration(domain)" </div>
type="button"> </ng-container>
{{'admin.oauth2.add-provider' | translate}} </section>
</button>
</div>
</ng-template>
</mat-expansion-panel>
</ng-container>
</mat-accordion>
</div>
</ng-container>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="addDomain()"
type="button">
{{'admin.oauth2.add-domain' | translate}}
</button>
<button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async) || oauth2SettingsForm.invalid || !oauth2SettingsForm.dirty"
type="submit">
{{'action.save' | translate}}
</button>
</div>
</fieldset> </fieldset>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
<button type="button" mat-raised-button color="primary"
[disabled]="isLoading$ | async"
*ngIf="oauth2SettingsForm.get('enabled').value"
(click)="addDomain()">
<mat-icon>add</mat-icon>
<span translate>action.add</span>
</button>
<button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async) || oauth2SettingsForm.invalid || !oauth2SettingsForm.dirty"
type="submit">
{{'action.save' | translate}}
</button>
</div>
</form> </form>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

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

@ -15,11 +15,11 @@
*/ */
:host{ :host{
.checkbox-row { .checkbox-row {
margin-top: 16px; margin-top: 1em;
} }
.registration-card{ .registration-card{
margin-bottom: 8px; margin-bottom: 0.5em;
border: 1px solid rgba(0, 0, 0, 0.2); border: 1px solid rgba(0, 0, 0, 0.2);
.custom-settings{ .custom-settings{
@ -31,7 +31,15 @@
} }
.container{ .container{
margin-bottom: 16px; margin-bottom: 1em;
.tb-highlight{
margin: 0;
}
.tb-hint{
padding-bottom: 0;
}
} }
} }
@ -39,7 +47,7 @@
.registration-card{ .registration-card{
.custom-settings{ .custom-settings{
.mat-expansion-panel-body{ .mat-expansion-panel-body{
padding: 0 2px 16px; padding: 0 2px 1em;
} }
.mat-tab-label{ .mat-tab-label{
text-transform: none; 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;
}
}
} }

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

@ -14,12 +14,16 @@
/// limitations under the License. /// 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 { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { import {
ClientAuthenticationMethod, ClientAuthenticationMethod,
ClientProviderTemplated, ClientProviderTemplated,
ClientRegistration, ClientRegistration,
DomainInfo,
DomainSchema,
domainSchemaTranslations,
DomainsParam,
MapperConfig, MapperConfig,
MapperConfigBasic, MapperConfigBasic,
MapperConfigCustom, MapperConfigCustom,
@ -38,13 +42,7 @@ import { WINDOW } from '@core/services/window.service';
import { forkJoin, Subscription } from 'rxjs'; import { forkJoin, Subscription } from 'rxjs';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { isDefined } from '@core/utils';
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
import {
EDIT_REDIRECT_URI_PANEL_DATA,
EditRedirectUriPanelComponent,
EditRedirectUriPanelData
} from './edit-redirect-uri-panel.component';
@Component({ @Component({
selector: 'tb-oauth2-settings', selector: 'tb-oauth2-settings',
@ -60,15 +58,15 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
additionalInfo: { additionalInfo: {
providerName: 'Custom' providerName: 'Custom'
}, },
clientAuthenticationMethod: 'POST', clientAuthenticationMethod: ClientAuthenticationMethod.POST,
userNameAttributeName: 'email', userNameAttributeName: 'email',
mapperConfig: { mapperConfig: {
allowUserCreation: true, allowUserCreation: true,
activateUser: false, activateUser: false,
type: 'BASIC', type: MapperConfigType.BASIC,
basic: { basic: {
emailAttributeKey: 'email', emailAttributeKey: 'email',
tenantNameStrategy: 'DOMAIN', tenantNameStrategy: TenantNameStrategy.DOMAIN,
alwaysFullScreen: false alwaysFullScreen: false
} }
} }
@ -77,19 +75,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
readonly separatorKeysCodes: number[] = [ENTER, COMMA]; readonly separatorKeysCodes: number[] = [ENTER, COMMA];
oauth2SettingsForm: FormGroup; oauth2SettingsForm: FormGroup;
oauth2Settings: OAuth2Settings[]; oauth2Settings: OAuth2Settings;
clientAuthenticationMethods: ClientAuthenticationMethod[] = ['BASIC', 'POST']; clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod);
converterTypesExternalUser: MapperConfigType[] = ['BASIC', 'CUSTOM']; converterTypesExternalUser = Object.keys(MapperConfigType);
tenantNameStrategies: TenantNameStrategy[] = ['DOMAIN', 'EMAIL', 'CUSTOM']; tenantNameStrategies = Object.keys(TenantNameStrategy);
protocols = Object.keys(DomainSchema);
domainSchemaTranslations = domainSchemaTranslations;
templateProvider = ['Custom']; templateProvider = ['Custom'];
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private adminService: AdminService, private adminService: AdminService,
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private cd: ChangeDetectorRef,
private fb: FormBuilder, private fb: FormBuilder,
private dialogService: DialogService, private dialogService: DialogService,
private translate: TranslateService, private translate: TranslateService,
@ -124,8 +121,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
this.templateProvider.sort(); this.templateProvider.sort();
} }
get clientDomains(): FormArray { get domainsParams(): FormArray {
return this.oauth2SettingsForm.get('clientDomains') as FormArray; return this.oauth2SettingsForm.get('domainsParams') as FormArray;
} }
private formBasicGroup(mapperConfigBasic?: MapperConfigBasic): FormGroup { 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], emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required],
firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''], firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''],
lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''], lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''],
tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : 'DOMAIN'], tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN],
tenantNamePattern: [tenantNamePattern, Validators.required], tenantNamePattern: [tenantNamePattern, Validators.required],
customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null], customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null],
defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null], defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null],
@ -167,69 +164,78 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
private buildOAuth2SettingsForm(): void { private buildOAuth2SettingsForm(): void {
this.oauth2SettingsForm = this.fb.group({ 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) { if (oauth2Settings) {
oauth2Settings.forEach((domain) => { this.oauth2SettingsForm.patchValue({enabled: oauth2Settings.enabled}, {emitEvent: false});
this.clientDomains.push(this.buildSettingsDomain(domain)); oauth2Settings.domainsParams.forEach((domain) => {
this.domainsParams.push(this.buildDomainsForm(domain));
}); });
} }
} }
private uniqueDomainValidator(control: AbstractControl): { [key: string]: boolean } | null { private uniqueDomainValidator(control: AbstractControl): { [key: string]: boolean } | null {
if (control.root.value.clientDomains?.length > 1) { if (control.parent?.parent?.value) {
const listDomainName = []; const domain = control.value;
control.root.value.clientDomains.forEach((domain) => { const listProtocols = control.parent.parent.value
listDomainName.push(domain.domainName); .filter((domainInfo) => domainInfo.name === domain)
}); .map((domainInfo) => domainInfo.scheme);
if (listDomainName.indexOf(control.value) > -1) { if (listProtocols.length > 1 && listProtocols.indexOf(DomainSchema.MIXED) > -1) {
return {unique: true}; return {unique: true};
} }
} }
return null; return null;
} }
private buildSettingsDomain(domainParams?: OAuth2Settings): FormGroup { public domainListTittle(control: AbstractControl): string {
let url = this.window.location.protocol + '//' + this.window.location.hostname; const domainInfos = control.get('domainInfos').value as DomainInfo[];
const port = this.window.location.port; if (domainInfos.length) {
if (port !== '80' && port !== '443') { const domainList = new Set<string>();
url += ':' + port; 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({ const formDomain = this.fb.group({
domainName: [domainParams?.domainName ? domainParams.domainName : this.window.location.hostname, [ domainInfos: this.fb.array([], Validators.required),
Validators.required,
Validators.pattern('((?![:/]).)*$'),
this.uniqueDomainValidator]],
redirectUriTemplate: [domainParams?.redirectUriTemplate ? domainParams.redirectUriTemplate : url, [
Validators.required,
Validators.pattern(this.URL_REGEXP)]],
clientRegistrations: this.fb.array([], Validators.required) 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) { if (domainParams) {
domainParams.domainInfos.forEach((domain) => {
this.clientDomainInfos(formDomain).push(this.buildDomainForm(domain));
});
domainParams.clientRegistrations.forEach((registration) => { domainParams.clientRegistrations.forEach((registration) => {
this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration(registration)); this.clientDomainProviders(formDomain).push(this.buildProviderForm(registration));
}); });
} else { } else {
this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration()); this.clientDomainProviders(formDomain).push(this.buildProviderForm());
this.clientDomainInfos(formDomain).push(this.buildDomainForm());
} }
return formDomain; 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; let additionalInfo = null;
if (registrationData?.additionalInfo) { if (registrationData?.additionalInfo) {
additionalInfo = JSON.parse(registrationData.additionalInfo); additionalInfo = JSON.parse(registrationData.additionalInfo);
@ -237,13 +243,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
additionalInfo.providerName = 'Custom'; additionalInfo.providerName = 'Custom';
} }
} }
let defaultProviderName = 'Custom';
if (this.templateProvider.indexOf('Google')) {
defaultProviderName = 'Google';
}
const clientRegistration = this.fb.group({ const clientRegistration = this.fb.group({
id: this.fb.group({ id: this.fb.group({
id: [registrationData?.id?.id ? registrationData.id.id : null], id: [registrationData?.id?.id ? registrationData.id.id : null],
entityType: [registrationData?.id?.entityType ? registrationData.id.entityType : null] entityType: [registrationData?.id?.entityType ? registrationData.id.entityType : null]
}), }),
additionalInfo: this.fb.group({ 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], loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required],
loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null], loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null],
@ -255,19 +266,20 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '', authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '',
[Validators.required, [Validators.required,
Validators.pattern(this.URL_REGEXP)]], 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)], jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)],
userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '', userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '',
[Validators.required, [Validators.required,
Validators.pattern(this.URL_REGEXP)]], Validators.pattern(this.URL_REGEXP)]],
clientAuthenticationMethod: [ clientAuthenticationMethod: [
registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : 'POST', Validators.required], registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : ClientAuthenticationMethod.POST,
Validators.required],
userNameAttributeName: [ userNameAttributeName: [
registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required], registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required],
mapperConfig: this.fb.group({ mapperConfig: this.fb.group({
allowUserCreation: [registrationData?.mapperConfig?.allowUserCreation ? registrationData.mapperConfig.allowUserCreation : true], allowUserCreation: [registrationData?.mapperConfig?.allowUserCreation ? registrationData.mapperConfig.allowUserCreation : true],
activateUser: [registrationData?.mapperConfig?.activateUser ? registrationData.mapperConfig.activateUser : false], 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) { if (registrationData) {
this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig); this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig);
} else { } else {
this.changeMapperConfigType(clientRegistration, 'BASIC'); this.changeMapperConfigType(clientRegistration, MapperConfigType.BASIC);
} }
this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => { 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) { private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) {
const mapperConfig = control.get('mapperConfig') as FormGroup; const mapperConfig = control.get('mapperConfig') as FormGroup;
if (type === 'BASIC') { if (type === MapperConfigType.BASIC) {
mapperConfig.removeControl('custom'); mapperConfig.removeControl('custom');
mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic));
} else { } else {
@ -323,7 +335,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
} }
save(): void { save(): void {
const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue().clientDomains); const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue());
this.adminService.saveOAuth2Settings(setting).subscribe( this.adminService.saveOAuth2Settings(setting).subscribe(
(oauth2Settings) => { (oauth2Settings) => {
this.oauth2Settings = oauth2Settings; this.oauth2Settings = oauth2Settings;
@ -333,20 +345,16 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
); );
} }
private prepareFormValue(formValue: OAuth2Settings[]): OAuth2Settings[]{ private prepareFormValue(formValue: OAuth2Settings): OAuth2Settings{
const prepereValue = []; formValue.domainsParams.forEach((setting, index) => {
formValue.forEach((setting, index) => { setting.clientRegistrations.forEach((registration) => {
if (this.clientDomains.at(index).dirty) { registration.additionalInfo = JSON.stringify(registration.additionalInfo);
setting.clientRegistrations.forEach((registration) => { if (registration.id.id === null) {
registration.additionalInfo = JSON.stringify(registration.additionalInfo); delete registration.id;
if (registration.id.id === null) { }
delete registration.id; });
}
});
prepereValue.push(setting);
}
}); });
return prepereValue; return formValue;
} }
confirmForm(): FormGroup { confirmForm(): FormGroup {
@ -359,6 +367,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
const controller = control.get('scope') as FormArray; const controller = control.get('scope') as FormArray;
if ((value.trim() !== '')) { if ((value.trim() !== '')) {
controller.push(this.fb.control(value.trim())); controller.push(this.fb.control(value.trim()));
controller.markAsDirty();
} }
if (input) { if (input) {
@ -369,10 +378,12 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
removeScope(i: number, control: AbstractControl): void { removeScope(i: number, control: AbstractControl): void {
const controller = control.get('scope') as FormArray; const controller = control.get('scope') as FormArray;
controller.removeAt(i); controller.removeAt(i);
controller.markAsTouched();
controller.markAsDirty();
} }
addDomain(): void { addDomain(): void {
this.clientDomains.push(this.buildSettingsDomain()); this.domainsParams.push(this.buildDomainsForm());
} }
deleteDomain($event: Event, index: number): void { deleteDomain($event: Event, index: number): void {
@ -381,97 +392,48 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
$event.preventDefault(); $event.preventDefault();
} }
const domainName = this.clientDomains.at(index).get('domainName').value; const domainName = this.domainListTittle(this.domainsParams.at(index));
this.dialogService.confirm( this.dialogService.confirm(
this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}), this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}),
this.translate.instant('admin.oauth2.delete-domain-text'), null, this.translate.instant('admin.oauth2.delete-domain-text'), null,
this.translate.instant('action.delete') this.translate.instant('action.delete')
).subscribe((data) => { ).subscribe((data) => {
if (data) { if (data) {
if (index < this.oauth2Settings.length) { this.domainsParams.removeAt(index);
this.adminService.deleteOAuth2Domain(this.oauth2Settings[index].domainName).subscribe(() => { this.domainsParams.markAsTouched();
this.oauth2Settings.splice(index, 1); this.domainsParams.markAsDirty();
this.clientDomains.removeAt(index);
});
} else {
this.clientDomains.removeAt(index);
}
} }
}); });
} }
clientDomainRegistrations(control: AbstractControl): FormArray { clientDomainProviders(control: AbstractControl): FormArray {
return control.get('clientRegistrations') as FormArray; return control.get('clientRegistrations') as FormArray;
} }
addRegistration(control: AbstractControl): void { clientDomainInfos(control: AbstractControl): FormArray {
this.clientDomainRegistrations(control).push(this.buildSettingsRegistration()); 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) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
$event.preventDefault(); $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.dialogService.confirm(
this.translate.instant('admin.oauth2.delete-registration-title', {name: providerName || ''}), this.translate.instant('admin.oauth2.delete-registration-title', {name: providerName || ''}),
this.translate.instant('admin.oauth2.delete-registration-text'), null, this.translate.instant('admin.oauth2.delete-registration-text'), null,
this.translate.instant('action.delete') this.translate.instant('action.delete')
).subscribe((data) => { ).subscribe((data) => {
if (data) { if (data) {
const registrationId = this.clientDomainRegistrations(control).at(index).get('id.id').value; this.clientDomainProviders(control).removeAt(index);
if (registrationId) { this.clientDomainProviders(control).markAsTouched();
this.adminService.deleteOAuthCclientRegistrationId(registrationId).subscribe(() => { this.clientDomainProviders(control).markAsDirty();
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<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.clientDomains.at(index).get('redirectUriTemplate').patchValue(attributeValue);
this.clientDomains.at(index).get('redirectUriTemplate').markAsDirty();
} }
}); });
} }
@ -491,4 +453,43 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
} }
return this.templates.get(provider).helpLink; 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;
}
} }

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

@ -26,10 +26,6 @@ export interface AdminSettings<T> {
export declare type SmtpProtocol = 'smtp' | 'smtps'; 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 { export interface MailServerSettings {
mailFrom: string; mailFrom: string;
smtpProtocol: SmtpProtocol; smtpProtocol: SmtpProtocol;
@ -69,9 +65,43 @@ export interface UpdateMessage {
} }
export interface OAuth2Settings { export interface OAuth2Settings {
domainName: string; enabled: boolean;
redirectUriTemplate: string; domainsParams: DomainsParam[];
}
export interface DomainsParam {
clientRegistrations: ClientRegistration[]; 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, string>(
[
[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{ export interface ClientProviderTemplated extends ClientRegistration{
@ -100,6 +130,11 @@ export interface ClientRegistration {
additionalInfo: string; additionalInfo: string;
} }
export enum ClientAuthenticationMethod {
BASIC = 'BASIC',
POST = 'POST'
}
export interface MapperConfig { export interface MapperConfig {
allowUserCreation: boolean; allowUserCreation: boolean;
activateUser: boolean; activateUser: boolean;

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

@ -128,7 +128,9 @@
"access-token-uri-required": "Access token URI is required.", "access-token-uri-required": "Access token URI is required.",
"activate-user": "Activate user", "activate-user": "Activate user",
"add-domain": "Add domain", "add-domain": "Add domain",
"delete-domain": "Delete domain",
"add-provider": "Add provider", "add-provider": "Add provider",
"delete-provider": "Delete provider",
"allow-user-creation": "Allow user creation", "allow-user-creation": "Allow user creation",
"always-fullscreen": "Always fullscreen", "always-fullscreen": "Always fullscreen",
"authorization-uri": "Authorization URI", "authorization-uri": "Authorization URI",
@ -141,10 +143,10 @@
"custom-setting": "Custom settings", "custom-setting": "Custom settings",
"customer-name-pattern": "Customer name pattern", "customer-name-pattern": "Customer name pattern",
"default-dashboard-name": "Default dashboard name", "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-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 the domain '{{domainName}}'?", "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?",
"delete-registration-text": "Be careful, after the confirmation a registration data will be unavailable.", "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 registration '{{name}}'?", "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?",
"email-attribute-key": "Email attribute key", "email-attribute-key": "Email attribute key",
"email-attribute-key-required": "Email attribute key is required.", "email-attribute-key-required": "Email attribute key is required.",
"first-name-attribute-key": "First name attribute key", "first-name-attribute-key": "First name attribute key",
@ -160,11 +162,12 @@
"new-domain": "New domain", "new-domain": "New domain",
"oauth2": "OAuth2", "oauth2": "OAuth2",
"redirect-uri-template": "Redirect URI template", "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": "Registration ID",
"registration-id-required": "Registration ID is required.", "registration-id-required": "Registration ID is required.",
"registration-id-unique": "Registration ID need to unique for the system.", "registration-id-unique": "Registration ID need to unique for the system.",
"scope": "Scope", "scope": "Scope",
"scope-required": "Scope is required.",
"tenant-name-pattern": "Tenant name pattern", "tenant-name-pattern": "Tenant name pattern",
"tenant-name-pattern-required": "Tenant name pattern is required.", "tenant-name-pattern-required": "Tenant name pattern is required.",
"tenant-name-strategy": "Tenant name strategy", "tenant-name-strategy": "Tenant name strategy",
@ -176,7 +179,12 @@
"user-info-uri": "User info URI", "user-info-uri": "User info URI",
"user-info-uri-required": "User info URI is required.", "user-info-uri-required": "User info URI is required.",
"user-name-attribute-name": "User name attribute key", "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": { "alarm": {

Loading…
Cancel
Save