Browse Source

UI: refactoring OAuth2/Login Page and added oauth mapperType GITHUB

pull/3575/head
Vladyslav_Prykhodko 6 years ago
parent
commit
1f7623352a
  1. 9
      ui-ngx/src/app/core/auth/auth.service.ts
  2. 22
      ui-ngx/src/app/core/http/admin.service.ts
  3. 44
      ui-ngx/src/app/core/http/oauth2.service.ts
  4. 1
      ui-ngx/src/app/core/http/public-api.ts
  5. 35
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html
  6. 11
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss
  7. 158
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts
  8. 28
      ui-ngx/src/app/modules/login/pages/login/login.component.html
  9. 13
      ui-ngx/src/app/modules/login/pages/login/login.component.scss
  10. 4
      ui-ngx/src/app/modules/login/pages/login/login.component.ts
  11. 6
      ui-ngx/src/app/shared/models/login.models.ts
  12. 119
      ui-ngx/src/app/shared/models/oauth2.models.ts
  13. 1
      ui-ngx/src/app/shared/models/public-api.ts
  14. 99
      ui-ngx/src/app/shared/models/settings.models.ts

9
ui-ngx/src/app/core/auth/auth.service.ts

@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http';
import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs';
import { catchError, map, mergeMap, tap } from 'rxjs/operators';
import { LoginRequest, LoginResponse, OAuth2Client, PublicLoginRequest } from '@shared/models/login.models';
import { LoginRequest, LoginResponse, PublicLoginRequest } from '@shared/models/login.models';
import { ActivatedRoute, Router, UrlTree } from '@angular/router';
import { defaultHttpOptions } from '../http/http-utils';
import { UserService } from '../http/user.service';
@ -44,6 +44,7 @@ import { AdminService } from '@core/http/admin.service';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { OAuth2ClientInfo } from '@shared/models/oauth2.models';
@Injectable({
providedIn: 'root'
@ -67,7 +68,7 @@ export class AuthService {
}
redirectUrl: string;
oauth2Clients: Array<OAuth2Client> = null;
oauth2Clients: Array<OAuth2ClientInfo> = null;
private refreshTokenSubject: ReplaySubject<LoginResponse> = null;
private jwtHelper = new JwtHelperService();
@ -197,8 +198,8 @@ export class AuthService {
});
}
public loadOAuth2Clients(): Observable<Array<OAuth2Client>> {
return this.http.post<Array<OAuth2Client>>(`/api/noauth/oauth2Clients`,
public loadOAuth2Clients(): Observable<Array<OAuth2ClientInfo>> {
return this.http.post<Array<OAuth2ClientInfo>>(`/api/noauth/oauth2Clients`,
null, defaultHttpOptions()).pipe(
tap((OAuth2Clients) => {
this.oauth2Clients = OAuth2Clients;

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

@ -18,14 +18,7 @@ import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import {
AdminSettings,
ClientProviderTemplated,
MailServerSettings,
OAuth2Settings,
SecuritySettings,
UpdateMessage
} from '@shared/models/settings.models';
import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models';
@Injectable({
providedIn: 'root'
@ -60,19 +53,6 @@ export class AdminService {
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<OAuth2Settings> {
return this.http.post<OAuth2Settings>('/api/oauth2/config', OAuth2Setting,
defaultHttpOptionsFromConfig(config));
}
public checkUpdates(config?: RequestConfig): Observable<UpdateMessage> {
return this.http.get<UpdateMessage>(`/api/admin/updates`, defaultHttpOptionsFromConfig(config));
}

44
ui-ngx/src/app/core/http/oauth2.service.ts

@ -0,0 +1,44 @@
///
/// 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 { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs';
import { OAuth2ClientRegistrationTemplate, OAuth2ClientsParams } from '@shared/models/oauth2.models';
@Injectable({
providedIn: 'root'
})
export class OAuth2Service {
constructor(
private http: HttpClient
) { }
public getOAuth2Settings(config?: RequestConfig): Observable<OAuth2ClientsParams> {
return this.http.get<OAuth2ClientsParams>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config));
}
public getOAuth2Template(config?: RequestConfig): Observable<Array<OAuth2ClientRegistrationTemplate>> {
return this.http.get<Array<OAuth2ClientRegistrationTemplate>>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config));
}
public saveOAuth2Settings(OAuth2Setting: OAuth2ClientsParams, config?: RequestConfig): Observable<OAuth2ClientsParams> {
return this.http.post<OAuth2ClientsParams>('/api/oauth2/config', OAuth2Setting,
defaultHttpOptionsFromConfig(config));
}
}

1
ui-ngx/src/app/core/http/public-api.ts

@ -28,6 +28,7 @@ export * from './entity-relation.service';
export * from './entity-view.service';
export * from './event.service';
export * from './http-utils';
export * from './oauth2.service';
export * from './queue.service';
export * from './rule-chain.service';
export * from './tenant.service';

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

@ -33,7 +33,7 @@
<mat-checkbox formControlName="enabled">
{{ 'admin.oauth2.enable' | translate }}
</mat-checkbox>
<section *ngIf="oauth2SettingsForm.get('enabled').value && !(isLoading$ | async)" style="margin-top: 1em;">
<section *ngIf="oauth2SettingsForm.get('enabled').value" style="margin-top: 1em;">
<ng-container formArrayName="domainsParams">
<div class="container">
<mat-accordion multi>
@ -59,7 +59,7 @@
<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" fxLayoutGap="8px">
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
<div fxLayout="column" fxFlex.sm="60" fxFlex.gt-sm="50">
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
<mat-form-field fxFlex="30" fxFlex.xs class="mat-block">
@ -160,7 +160,7 @@
</mat-option>
</mat-select>
</mat-form-field>
<div [tb-help]="getHelpLink(registration)" *ngIf="getProviderName(registration) !== 'Custom'"></div>
<div [tb-help]="getHelpLink(registration)" *ngIf="!isCustomProvider(registration)"></div>
</section>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
@ -182,9 +182,9 @@
</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'">
[disabled]="isCustomProvider(registration)"
[expanded]="isCustomProvider(registration)">
<mat-expansion-panel-header [fxHide]="isCustomProvider(registration)">
<mat-panel-description fxLayoutAlign="end center">
{{ 'admin.oauth2.custom-setting' | translate }}
</mat-panel-description>
@ -199,7 +199,7 @@
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'accessTokenUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
*ngIf="!isCustomProvider(registration)">
<mat-icon class="material-icons">create</mat-icon>
</button>
<mat-error *ngIf="registration.get('accessTokenUri').hasError('required')">
@ -216,7 +216,7 @@
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'authorizationUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
*ngIf="!isCustomProvider(registration)">
<mat-icon class="material-icons">create</mat-icon>
</button>
<mat-error *ngIf="registration.get('authorizationUri').hasError('required')">
@ -235,7 +235,7 @@
<button mat-icon-button matSuffix
type="button" aria-label="Clear"
(click)="toggleEditMode(registration, 'jwkSetUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
*ngIf="!isCustomProvider(registration)">
<mat-icon class="material-icons">create</mat-icon>
</button>
<mat-error *ngIf="registration.get('jwkSetUri').hasError('pattern')">
@ -249,7 +249,7 @@
<button mat-icon-button matSuffix
type="button"
(click)="toggleEditMode(registration, 'userInfoUri')"
*ngIf="getProviderName(registration) !== 'Custom'">
*ngIf="!isCustomProvider(registration)">
<mat-icon class="material-icons">create</mat-icon>
</button>
<mat-error *ngIf="registration.get('userInfoUri').hasError('required')">
@ -271,7 +271,7 @@
</mat-select>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" *ngIf="getProviderName(registration) === 'Custom'">
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" *ngIf="isCustomProvider(registration)">
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>admin.oauth2.login-button-label</mat-label>
<input matInput formControlName="loginButtonLabel"
@ -332,16 +332,17 @@
<mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.type</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let converterTypeExternalUser of converterTypesExternalUser"
[value]="converterTypeExternalUser">
{{ converterTypeExternalUser }}
<mat-option *ngFor="let mapperConfigType of mapperConfigTypes"
[value]="mapperConfigType">
{{ mapperConfigType }}
</mat-option>
</mat-select>
</mat-form-field>
<section formGroupName="basic"
*ngIf="registration.get('mapperConfig.type').value === 'BASIC'">
<mat-form-field class="mat-block">
*ngIf="registration.get('mapperConfig.type').value !== mapperConfigType.CUSTOM">
<mat-form-field class="mat-block"
*ngIf="registration.get('mapperConfig.type').value !== mapperConfigType.GITHUB">
<mat-label translate>admin.oauth2.email-attribute-key</mat-label>
<input matInput formControlName="emailAttributeKey" required>
<mat-error
@ -403,7 +404,7 @@
</section>
<section formGroupName="custom"
*ngIf="registration.get('mapperConfig.type').value === 'CUSTOM'">
*ngIf="registration.get('mapperConfig.type').value === mapperConfigType.CUSTOM">
<mat-form-field class="mat-block">
<mat-label translate>admin.oauth2.url</mat-label>
<input matInput formControlName="url" required>

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

@ -41,6 +41,14 @@
padding-bottom: 0;
}
}
.mat-expansion-panel {
.mat-expansion-panel-header {
&.mat-expanded {
height: 48px;
}
}
}
}
:host ::ng-deep{
@ -49,9 +57,6 @@
.mat-expansion-panel-body{
padding: 0 2px 1em;
}
.mat-tab-label{
text-transform: none;
}
.mat-form-field-suffix .mat-icon-button .mat-icon{
font-size: 18px;
}

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

@ -18,22 +18,21 @@ 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,
MapperConfigType,
OAuth2Settings,
OAuth2ClientRegistrationTemplate,
OAuth2ClientsDomainParams,
OAuth2ClientsParams,
TenantNameStrategy
} from '@shared/models/settings.models';
} from '@shared/models/oauth2.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AdminService } from '@core/http/admin.service';
import { PageComponent } from '@shared/components/page.component';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
@ -42,7 +41,8 @@ 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 { isDefined } from '@core/utils';
import { isDefined, isDefinedAndNotNull } from '@core/utils';
import { OAuth2Service } from '@core/http/oauth2.service';
@Component({
selector: 'tb-oauth2-settings',
@ -53,7 +53,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/;
private subscriptions: Subscription[] = [];
private templates = new Map<string, ClientProviderTemplated>();
private templates = new Map<string, OAuth2ClientRegistrationTemplate>();
private defaultProvider = {
additionalInfo: {
providerName: 'Custom'
@ -75,10 +75,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
oauth2SettingsForm: FormGroup;
oauth2Settings: OAuth2Settings;
auth2ClientsParams: OAuth2ClientsParams;
clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod);
converterTypesExternalUser = Object.keys(MapperConfigType);
mapperConfigType = MapperConfigType;
mapperConfigTypes = Object.keys(MapperConfigType);
tenantNameStrategies = Object.keys(TenantNameStrategy);
protocols = Object.keys(DomainSchema);
domainSchemaTranslations = domainSchemaTranslations;
@ -86,7 +87,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
templateProvider = ['Custom'];
constructor(protected store: Store<AppState>,
private adminService: AdminService,
private oauth2Service: OAuth2Service,
private fb: FormBuilder,
private dialogService: DialogService,
private translate: TranslateService,
@ -97,13 +98,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
ngOnInit(): void {
this.buildOAuth2SettingsForm();
forkJoin([
this.adminService.getOAuth2Template(),
this.adminService.getOAuth2Settings()
this.oauth2Service.getOAuth2Template(),
this.oauth2Service.getOAuth2Settings()
]).subscribe(
([templates, oauth2Settings]) => {
([templates, auth2ClientsParams]) => {
this.initTemplates(templates);
this.oauth2Settings = oauth2Settings;
this.initOAuth2Settings(this.oauth2Settings);
this.auth2ClientsParams = auth2ClientsParams;
this.initOAuth2Settings(this.auth2ClientsParams);
}
);
}
@ -115,8 +116,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
});
}
private initTemplates(templates: ClientProviderTemplated[]): void {
templates.map(provider => this.templates.set(provider.name, provider));
private initTemplates(templates: OAuth2ClientRegistrationTemplate[]): void {
templates.map(provider => {
delete provider.additionalInfo;
this.templates.set(provider.name, provider);
});
this.templateProvider.push(...Array.from(this.templates.keys()));
this.templateProvider.sort();
}
@ -169,10 +173,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
});
}
private initOAuth2Settings(oauth2Settings: OAuth2Settings): void {
if (oauth2Settings) {
this.oauth2SettingsForm.patchValue({enabled: oauth2Settings.enabled}, {emitEvent: false});
oauth2Settings.domainsParams.forEach((domain) => {
private initOAuth2Settings(auth2ClientsParams: OAuth2ClientsParams): void {
if (auth2ClientsParams) {
this.oauth2SettingsForm.patchValue({enabled: auth2ClientsParams.enabled}, {emitEvent: false});
auth2ClientsParams.domainsParams.forEach((domain) => {
this.domainsParams.push(this.buildDomainsForm(domain));
});
}
@ -204,17 +208,17 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
return this.translate.instant('admin.oauth2.new-domain');
}
private buildDomainsForm(domainParams?: DomainsParam): FormGroup {
private buildDomainsForm(auth2ClientsDomainParams?: OAuth2ClientsDomainParams): FormGroup {
const formDomain = this.fb.group({
domainInfos: this.fb.array([], Validators.required),
clientRegistrations: this.fb.array([], Validators.required)
});
if (domainParams) {
domainParams.domainInfos.forEach((domain) => {
if (auth2ClientsDomainParams) {
auth2ClientsDomainParams.domainInfos.forEach((domain) => {
this.clientDomainInfos(formDomain).push(this.buildDomainForm(domain));
});
domainParams.clientRegistrations.forEach((registration) => {
auth2ClientsDomainParams.clientRegistrations.forEach((registration) => {
this.clientDomainProviders(formDomain).push(this.buildProviderForm(registration));
});
} else {
@ -235,10 +239,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
return domain;
}
private buildProviderForm(registrationData?: ClientRegistration): FormGroup {
private buildProviderForm(clientRegistration?: ClientRegistration): FormGroup {
let additionalInfo = null;
if (registrationData?.additionalInfo) {
additionalInfo = JSON.parse(registrationData.additionalInfo);
if (isDefinedAndNotNull(clientRegistration?.additionalInfo)) {
additionalInfo = clientRegistration.additionalInfo;
if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) {
additionalInfo.providerName = 'Custom';
}
@ -248,73 +252,70 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
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]
}),
const clientRegistrationFormGroup = this.fb.group({
additionalInfo: this.fb.group({
providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required]
}),
loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required],
loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null],
clientId: [registrationData?.clientId ? registrationData.clientId : '', Validators.required],
clientSecret: [registrationData?.clientSecret ? registrationData.clientSecret : '', Validators.required],
accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '',
loginButtonLabel: [clientRegistration?.loginButtonLabel ? clientRegistration.loginButtonLabel : null, Validators.required],
loginButtonIcon: [clientRegistration?.loginButtonIcon ? clientRegistration.loginButtonIcon : null],
clientId: [clientRegistration?.clientId ? clientRegistration.clientId : '', Validators.required],
clientSecret: [clientRegistration?.clientSecret ? clientRegistration.clientSecret : '', Validators.required],
accessTokenUri: [clientRegistration?.accessTokenUri ? clientRegistration.accessTokenUri : '',
[Validators.required,
Validators.pattern(this.URL_REGEXP)]],
authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '',
authorizationUri: [clientRegistration?.authorizationUri ? clientRegistration.authorizationUri : '',
[Validators.required,
Validators.pattern(this.URL_REGEXP)]],
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 : '',
scope: this.fb.array(clientRegistration?.scope ? clientRegistration.scope : [], Validators.required),
jwkSetUri: [clientRegistration?.jwkSetUri ? clientRegistration.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)],
userInfoUri: [clientRegistration?.userInfoUri ? clientRegistration.userInfoUri : '',
[Validators.required,
Validators.pattern(this.URL_REGEXP)]],
clientAuthenticationMethod: [
registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : ClientAuthenticationMethod.POST,
clientRegistration?.clientAuthenticationMethod ? clientRegistration.clientAuthenticationMethod : ClientAuthenticationMethod.POST,
Validators.required],
userNameAttributeName: [
registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required],
clientRegistration?.userNameAttributeName ? clientRegistration.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 : MapperConfigType.BASIC, Validators.required]
allowUserCreation: [
clientRegistration?.mapperConfig?.allowUserCreation ? clientRegistration.mapperConfig.allowUserCreation : true
],
activateUser: [clientRegistration?.mapperConfig?.activateUser ? clientRegistration.mapperConfig.activateUser : false],
type: [
clientRegistration?.mapperConfig?.type ? clientRegistration.mapperConfig.type : MapperConfigType.BASIC, Validators.required
]
}
)
});
if (registrationData) {
this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig);
if (clientRegistration) {
this.changeMapperConfigType(clientRegistrationFormGroup, clientRegistration.mapperConfig.type, clientRegistration.mapperConfig);
} else {
this.changeMapperConfigType(clientRegistration, MapperConfigType.BASIC);
this.setProviderDefaultValue(defaultProviderName, clientRegistration);
this.changeMapperConfigType(clientRegistrationFormGroup, MapperConfigType.BASIC);
this.setProviderDefaultValue(defaultProviderName, clientRegistrationFormGroup);
}
this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => {
this.changeMapperConfigType(clientRegistration, value);
this.subscriptions.push(clientRegistrationFormGroup.get('mapperConfig.type').valueChanges.subscribe((value) => {
this.changeMapperConfigType(clientRegistrationFormGroup, value);
}));
this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => {
(clientRegistration.get('scope') as FormArray).clear();
this.setProviderDefaultValue(provider, clientRegistration);
this.subscriptions.push(clientRegistrationFormGroup.get('additionalInfo.providerName').valueChanges.subscribe((provider) => {
(clientRegistrationFormGroup.get('scope') as FormArray).clear();
this.setProviderDefaultValue(provider, clientRegistrationFormGroup);
}));
return clientRegistration;
return clientRegistrationFormGroup;
}
private setProviderDefaultValue(provider: string, clientRegistration: FormGroup) {
if (provider === 'Custom') {
const defaultSettings = {...this.defaultProvider, ...{id: clientRegistration.get('id').value}};
clientRegistration.reset(defaultSettings, {emitEvent: false});
clientRegistration.reset(this.defaultProvider, {emitEvent: false});
clientRegistration.get('accessTokenUri').enable();
clientRegistration.get('authorizationUri').enable();
clientRegistration.get('jwkSetUri').enable();
clientRegistration.get('userInfoUri').enable();
} else {
const template = this.templates.get(provider);
delete template.id;
delete template.additionalInfo;
template.clientId = '';
template.clientSecret = '';
template.scope.forEach(() => {
@ -324,44 +325,33 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
clientRegistration.get('authorizationUri').disable();
clientRegistration.get('jwkSetUri').disable();
clientRegistration.get('userInfoUri').disable();
clientRegistration.patchValue(template, {emitEvent: false});
clientRegistration.patchValue(template);
}
}
private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) {
const mapperConfig = control.get('mapperConfig') as FormGroup;
if (type === MapperConfigType.BASIC) {
mapperConfig.removeControl('custom');
mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic));
} else {
if (type === MapperConfigType.CUSTOM) {
mapperConfig.removeControl('basic');
mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
} else {
mapperConfig.removeControl('custom');
mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic));
}
}
save(): void {
const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue());
this.adminService.saveOAuth2Settings(setting).subscribe(
const setting = this.oauth2SettingsForm.getRawValue();
this.oauth2Service.saveOAuth2Settings(setting).subscribe(
(oauth2Settings) => {
this.oauth2Settings = oauth2Settings;
this.oauth2SettingsForm.markAsPristine();
this.auth2ClientsParams = oauth2Settings;
this.oauth2SettingsForm.patchValue(this.oauth2SettingsForm, {emitEvent: false});
this.oauth2SettingsForm.markAsUntouched();
this.oauth2SettingsForm.markAsPristine();
}
);
}
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 formValue;
}
confirmForm(): FormGroup {
return this.oauth2SettingsForm;
}
@ -451,6 +441,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
return controller.get('additionalInfo.providerName').value;
}
isCustomProvider(controller: AbstractControl): boolean {
return this.getProviderName(controller) === 'Custom';
}
getHelpLink(controller: AbstractControl): string {
const provider = controller.get('additionalInfo.providerName').value;
if (provider === null || provider === 'Custom') {

28
ui-ngx/src/app/modules/login/pages/login/login.component.html

@ -28,6 +28,19 @@
<span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span>
<div tb-toast fxLayout="column" class="layout-padding">
<span style="height: 50px;"></span>
<div class="oauth-container tb-default" fxLayout="column" fxLayoutGap="16px" *ngIf="oauth2Clients?.length">
<ng-container *ngFor="let oauth2Client of oauth2Clients">
<a mat-raised-button class="login-with-button" href="{{ oauth2Client.url }}">
<mat-icon svgIcon="{{ oauth2Client.icon }}"></mat-icon>
{{ 'login.login-with' | translate: {name: oauth2Client.name} }}
</a>
</ng-container>
<div class="container-divider">
<div class="line"><mat-divider></mat-divider></div>
<div class="text mat-typography">{{ "login.or" | translate | uppercase }}</div>
<div class="line"><mat-divider></mat-divider></div>
</div>
</div>
<mat-form-field>
<mat-label translate>login.username</mat-label>
<input id="username-input" matInput type="email" autofocus formControlName="username" email required/>
@ -41,7 +54,7 @@
<input id="password-input" matInput type="password" formControlName="password"/>
<mat-icon matPrefix>lock</mat-icon>
</mat-form-field>
<div fxLayoutAlign="end center">
<div fxLayoutAlign="end center" class="forgot-password">
<button class="tb-reset-password" mat-button type="button" routerLink="/login/resetPasswordRequest">{{ 'login.forgot-password' | translate }}
</button>
</div>
@ -49,19 +62,6 @@
<button mat-raised-button color="accent" [disabled]="(isLoading$ | async)"
type="submit">{{ 'login.login' | translate }}</button>
</div>
<div class="oauth-container" fxLayout="column" fxLayoutGap="16px" *ngIf="oauth2Clients?.length">
<div class="container-divider">
<div class="line"><mat-divider></mat-divider></div>
<div class="text mat-typography">{{ "login.or" | translate | uppercase }}</div>
<div class="line"><mat-divider></mat-divider></div>
</div>
<ng-container *ngFor="let oauth2Client of oauth2Clients">
<a mat-raised-button color="warn" class="centered" href="{{ oauth2Client.url }}">
<mat-icon svgIcon="{{ oauth2Client.icon }}"></mat-icon>
{{ 'login.login-with' | translate: {name: oauth2Client.name} }}
</a>
</ng-container>
</div>
</div>
</fieldset>
</form>

13
ui-ngx/src/app/modules/login/pages/login/login.component.scss

@ -27,8 +27,11 @@
width: 550px !important;
}
.tb-reset-password{
padding: 0 6px;
.forgot-password {
padding: 0 0.5em 1em;
.tb-reset-password {
padding: 0 6px;
}
}
.tb-action-button{
@ -65,9 +68,11 @@
min-width: 20px;
}
a.centered {
a.login-with-button {
color: rgba(black, 0.87);
&:hover {
border-bottom: 0px;
border-bottom: 0;
}
}

4
ui-ngx/src/app/modules/login/pages/login/login.component.ts

@ -23,7 +23,7 @@ import { FormBuilder } from '@angular/forms';
import { HttpErrorResponse } from '@angular/common/http';
import { Constants } from '@shared/models/constants';
import { Router } from '@angular/router';
import { OAuth2Client } from '@shared/models/login.models';
import { OAuth2ClientInfo } from '@shared/models/oauth2.models';
@Component({
selector: 'tb-login',
@ -36,7 +36,7 @@ export class LoginComponent extends PageComponent implements OnInit {
username: '',
password: ''
});
oauth2Clients: Array<OAuth2Client> = null;
oauth2Clients: Array<OAuth2ClientInfo> = null;
constructor(protected store: Store<AppState>,
private authService: AuthService,

6
ui-ngx/src/app/shared/models/login.models.ts

@ -27,9 +27,3 @@ export interface LoginResponse {
token: string;
refreshToken: string;
}
export interface OAuth2Client {
name: string;
icon?: string;
url: string;
}

119
ui-ngx/src/app/shared/models/oauth2.models.ts

@ -0,0 +1,119 @@
///
/// 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 { HasUUID } from '@shared/models/id/has-uuid';
export interface OAuth2ClientsParams {
enabled: boolean;
domainsParams: OAuth2ClientsDomainParams[];
}
export interface OAuth2ClientsDomainParams {
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',
GITHUB = 'GITHUB'
}
export enum TenantNameStrategy{
DOMAIN = 'DOMAIN',
EMAIL = 'EMAIL',
CUSTOM = 'CUSTOM'
}
export interface OAuth2ClientRegistrationTemplate extends ClientRegistration{
comment: string;
createdTime: number;
helpLink: string;
name: string;
providerId: string;
id: HasUUID;
}
export interface ClientRegistration {
loginButtonLabel: string;
loginButtonIcon: string;
clientId: string;
clientSecret: string;
accessTokenUri: string;
authorizationUri: string;
scope: string[];
jwkSetUri?: string;
userInfoUri: string;
clientAuthenticationMethod: ClientAuthenticationMethod;
userNameAttributeName: string;
mapperConfig: MapperConfig;
additionalInfo: string;
}
export enum ClientAuthenticationMethod {
BASIC = 'BASIC',
POST = 'POST'
}
export interface MapperConfig {
allowUserCreation: boolean;
activateUser: boolean;
type: MapperConfigType;
basic?: MapperConfigBasic;
custom?: MapperConfigCustom;
}
export interface MapperConfigBasic {
emailAttributeKey: string;
firstNameAttributeKey?: string;
lastNameAttributeKey?: string;
tenantNameStrategy: TenantNameStrategy;
tenantNamePattern?: string;
customerNamePattern?: string;
defaultDashboardName?: string;
alwaysFullScreen?: boolean;
}
export interface MapperConfigCustom {
url: string;
username?: string;
password?: string;
}
export interface OAuth2ClientInfo {
name: string;
icon?: string;
url: string;
}

1
ui-ngx/src/app/shared/models/public-api.ts

@ -36,6 +36,7 @@ export * from './error.models';
export * from './event.models';
export * from './login.models';
export * from './material.models';
export * from './oauth2.models';
export * from './queue.models';
export * from './relation.models';
export * from './rule-chain.models';

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

@ -14,9 +14,6 @@
/// limitations under the License.
///
import { EntityId } from '@shared/models/id/entity-id';
import { TenantId } from '@shared/models/id/tenant-id';
export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
export interface AdminSettings<T> {
@ -63,99 +60,3 @@ export interface UpdateMessage {
message: string;
updateAvailable: boolean;
}
export interface OAuth2Settings {
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{
comment: string;
createdTime: number;
helpLink: string;
name: string;
providerId: string;
tenantId: TenantId;
}
export interface ClientRegistration {
loginButtonLabel: string;
loginButtonIcon: string;
clientId: string;
clientSecret: string;
accessTokenUri: string;
authorizationUri: string;
scope: string[];
jwkSetUri?: string;
userInfoUri: string;
clientAuthenticationMethod: ClientAuthenticationMethod;
userNameAttributeName: string;
mapperConfig: MapperConfig;
id?: EntityId;
additionalInfo: string;
}
export enum ClientAuthenticationMethod {
BASIC = 'BASIC',
POST = 'POST'
}
export interface MapperConfig {
allowUserCreation: boolean;
activateUser: boolean;
type: MapperConfigType;
basic?: MapperConfigBasic;
custom?: MapperConfigCustom;
}
export interface MapperConfigBasic {
emailAttributeKey: string;
firstNameAttributeKey?: string;
lastNameAttributeKey?: string;
tenantNameStrategy: TenantNameStrategy;
tenantNamePattern?: string;
customerNamePattern?: string;
defaultDashboardName?: string;
alwaysFullScreen?: boolean;
}
export interface MapperConfigCustom {
url: string;
username?: string;
password?: string;
}

Loading…
Cancel
Save