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));
}
public getOAuth2Settings(config?: RequestConfig): Observable<Array<OAuth2Settings>> {
return this.http.get<Array<OAuth2Settings>>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config));
public getOAuth2Settings(config?: RequestConfig): Observable<OAuth2Settings> {
return this.http.get<OAuth2Settings>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config));
}
public getOAuth2Template(config?: RequestConfig): Observable<Array<ClientProviderTemplated>> {
return this.http.get<Array<ClientProviderTemplated>>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config));
}
public saveOAuth2Settings(OAuth2Setting: OAuth2Settings[],
config?: RequestConfig): Observable<Array<OAuth2Settings>> {
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}`,
public saveOAuth2Settings(OAuth2Setting: 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,7 +24,6 @@ import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-sett
import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component';
import { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component';
import { EditRedirectUriPanelComponent } from './edit-redirect-uri-panel.component';
@NgModule({
declarations:
@ -32,8 +31,7 @@ import { EditRedirectUriPanelComponent } from './edit-redirect-uri-panel.compone
GeneralSettingsComponent,
MailServerComponent,
SecuritySettingsComponent,
OAuth2SettingsComponent,
EditRedirectUriPanelComponent
OAuth2SettingsComponent
],
imports: [
CommonModule,

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;">
<form [formGroup]="oauth2SettingsForm" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async">
<ng-container formArrayName="clientDomains">
<div class="container">
<mat-accordion multi>
<ng-container *ngFor="let domain of clientDomains.controls; let i = index; let last = last;">
<mat-expansion-panel [formGroupName]="i" [expanded]="last">
<mat-expansion-panel-header>
<mat-panel-title fxLayoutAlign="start center">
{{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.oauth2.new-domain" | translate) }}
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center">
<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>
<mat-checkbox formControlName="enabled">
{{ 'admin.oauth2.enable' | translate }}
</mat-checkbox>
<section *ngIf="oauth2SettingsForm.get('enabled').value && !(isLoading$ | async)" style="margin-top: 1em;">
<ng-container formArrayName="domainsParams">
<div class="container">
<mat-accordion multi>
<ng-container *ngFor="let domain of domainsParams.controls; let i = index; trackBy: trackByParams">
<mat-expansion-panel [formGroupName]="i">
<mat-expansion-panel-header>
<mat-panel-title fxLayoutAlign="start center">
{{ domainListTittle(domain) }}
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center">
<button mat-icon-button
type="button"
(click)="deleteDomain($event, i)"
@ -54,357 +51,428 @@
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</mat-panel-description>
</mat-expansion-panel-header>
<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">
{{ 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-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<ng-container formArrayName="domainInfos">
<section *ngFor="let domainInfo of clientDomainInfos(domain).controls; let n = index; trackBy: trackByParams"
class="domains-list">
<div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
<div fxLayout="row" fxLayout.xs="column" fxFlex fxLayoutGap="8px">
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px" fxFlex.gt-xs="50">
<mat-form-field fxFlex="30" fxFlex.xs class="mat-block">
<mat-label translate>admin.oauth2.protocol</mat-label>
<mat-select formControlName="scheme">
<mat-option *ngFor="let protocol of protocols" [value]="protocol">
{{ domainSchemaTranslations.get(protocol) | translate | 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-label translate>admin.domain-name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="domainInfo.get('name').hasError('pattern')">
{{ 'admin.error-verification-url' | translate }}
</mat-error>
<mat-error *ngIf="domainInfo.get('name').hasError('unique')">
{{ 'admin.domain-name-unique' | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxFlex>
<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-label translate>admin.oauth2.redirect-uri-template</mat-label>
<input matInput [value]="redirectURI(domainInfo)" readonly>
<button mat-icon-button color="primary" matSuffix type="button"
ngxClipboard cbContent="{{ redirectURI(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 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>
</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>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.authorization-uri</mat-label>
<input matInput formControlName="authorizationUri" required>
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'authorizationUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<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" appearance="legacy">
<mat-label translate>admin.oauth2.jwk-set-uri</mat-label>
<input matInput formControlName="jwkSetUri">
<button mat-icon-button matSuffix
type="button" aria-label="Clear"
(click)="toggleEditMode(registration, 'jwkSetUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<mat-error *ngIf="registration.get('jwkSetUri').hasError('pattern')">
{{ 'admin.oauth2.uri-pattern-error' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="column" fxLayoutAlign="center start">
<button type="button" mat-icon-button color="primary"
(click)="removeDomain($event, domain, n)"
[disabled]="clientDomainInfos(domain).controls.length < 2"
matTooltip="{{ 'admin.oauth2.delete-domain' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</section>
<div fxLayout="row" fxLayoutAlign="end center" style="margin-bottom: 1.25em">
<button mat-button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="addDomainInfo(domain)"
type="button">
{{'admin.oauth2.add-domain' | translate}}
</button>
</div>
</ng-container>
<ng-container formArrayName="clientRegistrations">
<div class="container">
<mat-expansion-panel *ngFor="let registration of clientDomainProviders(domain).controls; let j = index; trackBy: trackByParams"
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"
[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-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>
<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">
<mat-label translate>admin.oauth2.authorization-uri</mat-label>
<input matInput formControlName="authorizationUri" required>
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'authorizationUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<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.login-button-icon</mat-label>
<input matInput formControlName="loginButtonIcon">
</mat-form-field>
</div>
<section formGroupName="mapperConfig">
<div fxLayout="column" fxLayoutGap="8px" style="margin-bottom: 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 fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block" appearance="legacy">
<mat-label translate>admin.oauth2.jwk-set-uri</mat-label>
<input matInput formControlName="jwkSetUri">
<button mat-icon-button matSuffix
type="button" aria-label="Clear"
(click)="toggleEditMode(registration, 'jwkSetUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
<mat-icon class="material-icons">create</mat-icon>
</button>
<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>
<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>
</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-label translate>admin.oauth2.type</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let converterTypeExternalUser of converterTypesExternalUser"
[value]="converterTypeExternalUser">
{{ converterTypeExternalUser }}
<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>
<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 }}
<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>
<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.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.last-name-attribute-key</mat-label>
<input matInput formControlName="lastNameAttributeKey">
</mat-form-field>
<section formGroupName="mapperConfig">
<div fxLayout="column" fxLayoutGap="8px" style="margin-bottom: 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>
</section>
<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.scope</mat-label>
<mat-chip-list #scopeList>
<mat-chip *ngFor="let scope of registration.get('scope').value; let k = index; trackBy: trackByParams"
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.scope-required' | translate }}
</mat-error>
</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>
</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-label translate>admin.oauth2.customer-name-pattern</mat-label>
<input matInput formControlName="customerNamePattern">
<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>
<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">
<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>
<mat-checkbox fxFlex formControlName="alwaysFullScreen" class="checkbox-row">
{{ 'admin.oauth2.always-fullscreen' | translate}}
</mat-checkbox>
</div>
</section>
<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" [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-label translate>common.username</mat-label>
<input matInput formControlName="username" autocomplete="new-username">
<mat-label translate>admin.oauth2.customer-name-pattern</mat-label>
<input matInput formControlName="customerNamePattern">
</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">
<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>
<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>
</mat-tab>
</mat-tab-group>
</ng-template>
</mat-expansion-panel>
</section>
</ng-template>
</mat-expansion-panel>
</mat-tab>
</mat-tab-group>
</ng-template>
</mat-expansion-panel>
</section>
</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)="addProvider(domain)"
type="button">
{{'admin.oauth2.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.oauth2.add-provider' | translate}}
</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>
</ng-template>
</mat-expansion-panel>
</ng-container>
</mat-accordion>
</div>
</ng-container>
</section>
</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>
</mat-card-content>
</mat-card>

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

@ -15,11 +15,11 @@
*/
:host{
.checkbox-row {
margin-top: 16px;
margin-top: 1em;
}
.registration-card{
margin-bottom: 8px;
margin-bottom: 0.5em;
border: 1px solid rgba(0, 0, 0, 0.2);
.custom-settings{
@ -31,7 +31,15 @@
}
.container{
margin-bottom: 16px;
margin-bottom: 1em;
.tb-highlight{
margin: 0;
}
.tb-hint{
padding-bottom: 0;
}
}
}
@ -39,7 +47,7 @@
.registration-card{
.custom-settings{
.mat-expansion-panel-body{
padding: 0 2px 16px;
padding: 0 2px 1em;
}
.mat-tab-label{
text-transform: none;
@ -49,4 +57,10 @@
}
}
}
.domains-list{
margin-bottom: 1.5em;
.mat-form-field-suffix .mat-icon-button .mat-icon{
font-size: 24px;
}
}
}

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

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

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 ClientAuthenticationMethod = 'BASIC' | 'POST';
export declare type MapperConfigType = 'BASIC' | 'CUSTOM';
export declare type TenantNameStrategy = 'DOMAIN' | 'EMAIL' | 'CUSTOM';
export interface MailServerSettings {
mailFrom: string;
smtpProtocol: SmtpProtocol;
@ -69,9 +65,43 @@ export interface UpdateMessage {
}
export interface OAuth2Settings {
domainName: string;
redirectUriTemplate: string;
enabled: boolean;
domainsParams: DomainsParam[];
}
export interface DomainsParam {
clientRegistrations: ClientRegistration[];
domainInfos: DomainInfo[];
}
export interface DomainInfo {
name: string;
scheme: DomainSchema;
}
export enum DomainSchema{
HTTP = 'HTTP',
HTTPS = 'HTTPS',
MIXED = 'MIXED'
}
export const domainSchemaTranslations = new Map<DomainSchema, 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{
@ -100,6 +130,11 @@ export interface ClientRegistration {
additionalInfo: string;
}
export enum ClientAuthenticationMethod {
BASIC = 'BASIC',
POST = 'POST'
}
export interface MapperConfig {
allowUserCreation: 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.",
"activate-user": "Activate user",
"add-domain": "Add domain",
"delete-domain": "Delete domain",
"add-provider": "Add provider",
"delete-provider": "Delete provider",
"allow-user-creation": "Allow user creation",
"always-fullscreen": "Always fullscreen",
"authorization-uri": "Authorization URI",
@ -141,10 +143,10 @@
"custom-setting": "Custom settings",
"customer-name-pattern": "Customer name pattern",
"default-dashboard-name": "Default dashboard name",
"delete-domain-text": "Be careful, after the confirmation a domain and all registration data will be unavailable.",
"delete-domain-title": "Are you sure you want to delete the domain '{{domainName}}'?",
"delete-registration-text": "Be careful, after the confirmation a registration data will be unavailable.",
"delete-registration-title": "Are you sure you want to delete the registration '{{name}}'?",
"delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.",
"delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?",
"delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.",
"delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?",
"email-attribute-key": "Email attribute key",
"email-attribute-key-required": "Email attribute key is required.",
"first-name-attribute-key": "First name attribute key",
@ -160,11 +162,12 @@
"new-domain": "New domain",
"oauth2": "OAuth2",
"redirect-uri-template": "Redirect URI template",
"redirect-uri-template-required": "Redirect URI template is required.",
"copy-redirect-uri": "Copy redirect URI",
"registration-id": "Registration ID",
"registration-id-required": "Registration ID is required.",
"registration-id-unique": "Registration ID need to unique for the system.",
"scope": "Scope",
"scope-required": "Scope is required.",
"tenant-name-pattern": "Tenant name pattern",
"tenant-name-pattern-required": "Tenant name pattern is required.",
"tenant-name-strategy": "Tenant name strategy",
@ -176,7 +179,12 @@
"user-info-uri": "User info URI",
"user-info-uri-required": "User info URI is required.",
"user-name-attribute-name": "User name attribute key",
"user-name-attribute-name-required": "User name attribute key is required"
"user-name-attribute-name-required": "User name attribute key is required",
"protocol": "Protocol",
"domain-schema-http": "HTTP",
"domain-schema-https": "HTTPS",
"domain-schema-mixed": "HTTP+HTTPS",
"enable": "Enable OAuth2 settings"
}
},
"alarm": {

Loading…
Cancel
Save