Browse Source

Merge pull request #3575 from vvlladd28/improvement/oauth2

Improvement OAuth2
pull/3580/head
Igor Kulikov 6 years ago
committed by GitHub
parent
commit
da7d88470e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      application/src/main/data/json/system/oauth2_config_templates/facebook_config.json
  2. 9
      application/src/main/data/json/system/oauth2_config_templates/github_config.json
  3. 15
      application/src/main/data/json/system/oauth2_config_templates/google_config.json
  4. 6
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java
  5. 50
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java
  6. 8
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java
  7. 10
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java
  8. 21
      ui-ngx/src/app/app.component.ts
  9. 18
      ui-ngx/src/app/core/auth/auth.service.ts
  10. 22
      ui-ngx/src/app/core/http/admin.service.ts
  11. 44
      ui-ngx/src/app/core/http/oauth2.service.ts
  12. 1
      ui-ngx/src/app/core/http/public-api.ts
  13. 41
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html
  14. 11
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss
  15. 170
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts
  16. 28
      ui-ngx/src/app/modules/login/pages/login/login.component.html
  17. 19
      ui-ngx/src/app/modules/login/pages/login/login.component.scss
  18. 4
      ui-ngx/src/app/modules/login/pages/login/login.component.ts
  19. 6
      ui-ngx/src/app/shared/models/login.models.ts
  20. 119
      ui-ngx/src/app/shared/models/oauth2.models.ts
  21. 1
      ui-ngx/src/app/shared/models/public-api.ts
  22. 99
      ui-ngx/src/app/shared/models/settings.models.ts
  23. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

15
application/src/main/data/json/system/oauth2_config_templates/facebook_config.json

@ -7,14 +7,17 @@
"userInfoUri": "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email",
"clientAuthenticationMethod": "BASIC",
"userNameAttributeName": "email",
"basic": {
"emailAttributeKey": "email",
"firstNameAttributeKey": "first_name",
"lastNameAttributeKey": "last_name",
"tenantNameStrategy": "DOMAIN"
"mapperConfig": {
"type": "BASIC",
"basic": {
"emailAttributeKey": "email",
"firstNameAttributeKey": "first_name",
"lastNameAttributeKey": "last_name",
"tenantNameStrategy": "DOMAIN"
}
},
"comment": null,
"loginButtonIcon": "mdi:facebook",
"loginButtonIcon": "facebook-logo",
"loginButtonLabel": "Facebook",
"helpLink": "https://developers.facebook.com/docs/facebook-login/web#logindialog"
}

9
application/src/main/data/json/system/oauth2_config_templates/github_config.json

@ -7,11 +7,14 @@
"userInfoUri": "https://api.github.com/user",
"clientAuthenticationMethod": "BASIC",
"userNameAttributeName": "login",
"basic": {
"tenantNameStrategy": "DOMAIN"
"mapperConfig": {
"type": "GITHUB",
"basic": {
"tenantNameStrategy": "DOMAIN"
}
},
"comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to <a href=\"https://docs.github.com/en/rest/reference/users#list-email-addresses-for-the-authenticated-user\">Github Documentation</a>",
"loginButtonIcon": "mdi:github",
"loginButtonIcon": "github-logo",
"loginButtonLabel": "Github",
"helpLink": "https://docs.github.com/en/developers/apps/creating-an-oauth-app"
}

15
application/src/main/data/json/system/oauth2_config_templates/google_config.json

@ -8,14 +8,17 @@
"userInfoUri": "https://openidconnect.googleapis.com/v1/userinfo",
"clientAuthenticationMethod": "BASIC",
"userNameAttributeName": "email",
"basic": {
"emailAttributeKey": "email",
"firstNameAttributeKey": "given_name",
"lastNameAttributeKey": "family_name",
"tenantNameStrategy": "DOMAIN"
"mapperConfig": {
"type": "BASIC",
"basic": {
"emailAttributeKey": "email",
"firstNameAttributeKey": "given_name",
"lastNameAttributeKey": "family_name",
"tenantNameStrategy": "DOMAIN"
}
},
"comment": null,
"loginButtonIcon": "mdi:google",
"loginButtonIcon": "google-logo",
"loginButtonLabel": "Google",
"helpLink": "https://developers.google.com/adwords/api/docs/guides/authentication"
}

6
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java

@ -34,8 +34,7 @@ import java.util.List;
public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo<OAuth2ClientRegistrationTemplateId> implements HasName {
private String providerId;
private MapperType mapperType;
private OAuth2BasicMapperConfig basic;
private OAuth2MapperConfig mapperConfig;
private String authorizationUri;
private String accessTokenUri;
private List<String> scope;
@ -51,8 +50,7 @@ public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditio
public OAuth2ClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
super(clientRegistrationTemplate);
this.providerId = clientRegistrationTemplate.providerId;
this.mapperType = clientRegistrationTemplate.mapperType;
this.basic = clientRegistrationTemplate.basic;
this.mapperConfig = clientRegistrationTemplate.mapperConfig;
this.authorizationUri = clientRegistrationTemplate.authorizationUri;
this.accessTokenUri = clientRegistrationTemplate.accessTokenUri;
this.scope = clientRegistrationTemplate.scope;

50
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java

@ -109,17 +109,20 @@ public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity<OAuth2
this.loginButtonLabel = clientRegistrationTemplate.getLoginButtonLabel();
this.helpLink = clientRegistrationTemplate.getHelpLink();
this.additionalInfo = clientRegistrationTemplate.getAdditionalInfo();
this.type = clientRegistrationTemplate.getMapperType();
OAuth2BasicMapperConfig basicConfig = clientRegistrationTemplate.getBasic();
if (basicConfig != null) {
this.emailAttributeKey = basicConfig.getEmailAttributeKey();
this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey();
this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey();
this.tenantNameStrategy = basicConfig.getTenantNameStrategy();
this.tenantNamePattern = basicConfig.getTenantNamePattern();
this.customerNamePattern = basicConfig.getCustomerNamePattern();
this.defaultDashboardName = basicConfig.getDefaultDashboardName();
this.alwaysFullScreen = basicConfig.isAlwaysFullScreen();
OAuth2MapperConfig mapperConfig = clientRegistrationTemplate.getMapperConfig();
if (mapperConfig != null){
this.type = mapperConfig.getType();
OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic();
if (basicConfig != null) {
this.emailAttributeKey = basicConfig.getEmailAttributeKey();
this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey();
this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey();
this.tenantNameStrategy = basicConfig.getTenantNameStrategy();
this.tenantNamePattern = basicConfig.getTenantNamePattern();
this.customerNamePattern = basicConfig.getCustomerNamePattern();
this.defaultDashboardName = basicConfig.getDefaultDashboardName();
this.alwaysFullScreen = basicConfig.isAlwaysFullScreen();
}
}
}
@ -130,18 +133,21 @@ public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity<OAuth2
clientRegistrationTemplate.setCreatedTime(createdTime);
clientRegistrationTemplate.setAdditionalInfo(additionalInfo);
clientRegistrationTemplate.setMapperType(type);
clientRegistrationTemplate.setProviderId(providerId);
clientRegistrationTemplate.setBasic(
OAuth2BasicMapperConfig.builder()
.emailAttributeKey(emailAttributeKey)
.firstNameAttributeKey(firstNameAttributeKey)
.lastNameAttributeKey(lastNameAttributeKey)
.tenantNameStrategy(tenantNameStrategy)
.tenantNamePattern(tenantNamePattern)
.customerNamePattern(customerNamePattern)
.defaultDashboardName(defaultDashboardName)
.alwaysFullScreen(alwaysFullScreen)
clientRegistrationTemplate.setMapperConfig(
OAuth2MapperConfig.builder()
.type(type)
.basic(OAuth2BasicMapperConfig.builder()
.emailAttributeKey(emailAttributeKey)
.firstNameAttributeKey(firstNameAttributeKey)
.lastNameAttributeKey(lastNameAttributeKey)
.tenantNameStrategy(tenantNameStrategy)
.tenantNamePattern(tenantNamePattern)
.customerNamePattern(customerNamePattern)
.defaultDashboardName(defaultDashboardName)
.alwaysFullScreen(alwaysFullScreen)
.build()
)
.build()
);
clientRegistrationTemplate.setAuthorizationUri(authorizationUri);

8
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java

@ -104,7 +104,13 @@ public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService imple
if (StringUtils.isEmpty(clientRegistrationTemplate.getProviderId())) {
throw new DataValidationException("Provider ID should be specified!");
}
if (clientRegistrationTemplate.getBasic() == null) {
if (clientRegistrationTemplate.getMapperConfig() == null) {
throw new DataValidationException("Mapper config should be specified!");
}
if (clientRegistrationTemplate.getMapperConfig().getType() == null) {
throw new DataValidationException("Mapper type should be specified!");
}
if (clientRegistrationTemplate.getMapperConfig().getBasic() == null) {
throw new DataValidationException("Basic mapper config should be specified!");
}
}

10
dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.MapperType;
import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
@ -106,9 +107,9 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
OAuth2ClientRegistrationTemplate clientRegistrationTemplate = new OAuth2ClientRegistrationTemplate();
clientRegistrationTemplate.setProviderId(providerId);
clientRegistrationTemplate.setAdditionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString()));
clientRegistrationTemplate.setMapperType(MapperType.BASIC);
clientRegistrationTemplate.setBasic(
OAuth2BasicMapperConfig.builder()
clientRegistrationTemplate.setMapperConfig(OAuth2MapperConfig.builder()
.type(MapperType.BASIC)
.basic(OAuth2BasicMapperConfig.builder()
.firstNameAttributeKey("firstName")
.lastNameAttributeKey("lastName")
.emailAttributeKey("email")
@ -116,7 +117,8 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
.defaultDashboardName("Test")
.alwaysFullScreen(true)
.build()
);
)
.build());
clientRegistrationTemplate.setAuthorizationUri("authorizationUri");
clientRegistrationTemplate.setAccessTokenUri("tokenUri");
clientRegistrationTemplate.setScope(Arrays.asList("scope1", "scope2"));

21
ui-ngx/src/app/app.component.ts

@ -68,6 +68,27 @@ export class AppComponent implements OnInit {
)
);
this.matIconRegistry.addSvgIconLiteral(
'google-logo',
this.domSanitizer.bypassSecurityTrustHtml(
'<svg viewBox="0 0 48 48"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/><path fill="none" d="M0 0h48v48H0z"/></svg>'
)
);
this.matIconRegistry.addSvgIconLiteral(
'github-logo',
this.domSanitizer.bypassSecurityTrustHtml(
'<svg viewBox="0 0 32.7 32.7"><path d="M16.3 0C7.3 0 0 7.3 0 16.3c0 7.2 4.7 13.3 11.1 15.5.8.1 1.1-.4 1.1-.8v-2.8c-4.5 1-5.5-2.2-5.5-2.2-.7-1.9-1.8-2.4-1.8-2.4-1.5-1 .1-1 .1-1 1.6.1 2.5 1.7 2.5 1.7 1.5 2.5 3.8 1.8 4.7 1.4.1-1.1.6-1.8 1-2.2-3.6-.4-7.4-1.8-7.4-8.1 0-1.8.6-3.2 1.7-4.4-.2-.4-.7-2.1.2-4.3 0 0 1.4-.4 4.5 1.7 1.3-.4 2.7-.5 4.1-.5s2.8.2 4.1.5c3.1-2.1 4.5-1.7 4.5-1.7.9 2.2.3 3.9.2 4.3 1 1.1 1.7 2.6 1.7 4.4 0 6.3-3.8 7.6-7.4 8 .6.5 1.1 1.5 1.1 3v4.5c0 .4.3.9 1.1.8 6.5-2.2 11.1-8.3 11.1-15.5C32.6 7.3 25.3 0 16.3 0z" fill="#211c19"/></svg>'
)
);
this.matIconRegistry.addSvgIconLiteral(
'facebook-logo',
this.domSanitizer.bypassSecurityTrustHtml(
'<svg viewBox="0 0 263 263"><path d="M263 131.5C263 58.9 204.1 0 131.5 0S0 58.9 0 131.5c0 65.6 48.1 120 110.9 129.9v-91.9H77.5v-38h33.4v-29c0-33 19.6-51.2 49.7-51.2 14.4 0 29.4 2.6 29.4 2.6v32.4h-16.5c-16.3 0-21.4 10.1-21.4 20.5v24.7h36.4l-5.8 38h-30.6v91.9c62.8-9.9 110.9-64.3 110.9-129.9z" fill="#1877f2"/><path d="M182.7 169.5l5.8-38H152v-24.7c0-10.4 5.1-20.5 21.4-20.5H190V53.9s-15-2.6-29.4-2.6c-30 0-49.7 18.2-49.7 51.2v29H77.5v38h33.4v91.9c6.7 1.1 13.6 1.6 20.5 1.6s13.9-.5 20.5-1.6v-91.9h30.8z" fill="#fff"/></svg>'
)
);
this.storageService.testLocalStorage();
this.setupTranslate();

18
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,13 +198,14 @@ 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;
})
);
catchError(err => of([])),
tap((OAuth2Clients) => {
this.oauth2Clients = OAuth2Clients;
})
);
}
private forceDefaultPlace(authState?: AuthState, path?: string, params?: any): boolean {

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';

41
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,11 +271,11 @@
</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"
placeholder="{{ 'admin.oauth2.login-button-label-1' | translate }}"
placeholder="{{ 'admin.oauth2.login-button-label-placeholder' | translate }}"
required>
<mat-error *ngIf="registration.get('loginButtonLabel').hasError('required')">
{{ 'admin.oauth2.login-button-label-required' | translate }}
@ -316,6 +316,10 @@
{{ 'admin.oauth2.scope-required' | translate }}
</mat-error>
</mat-form-field>
<tb-error style="display: block; margin-top: -24px;"
[error]="registration.get('scope').hasError('required')
? ('admin.oauth2.scope-required' | translate) : ''">
</tb-error>
</mat-tab>
<mat-tab label="{{ 'admin.oauth2.mapper' | translate }}">
@ -332,16 +336,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 +408,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;
}

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

@ -15,25 +15,24 @@
///
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AbstractControl, FormArray, FormBuilder, FormGroup, ValidationErrors, 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,80 @@ 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 : [], this.validateScope),
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 validateScope (control: AbstractControl): ValidationErrors | null {
const scope: string[] = control.value;
if (!scope || !scope.length) {
return {
required: true
};
}
return null;
}
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 +335,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 +451,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 class="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>

19
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,17 @@
min-width: 20px;
}
a.centered {
a.login-with-button {
color: rgba(black, 0.87);
&:hover {
border-bottom: 0px;
border-bottom: 0;
}
.icon{
height: 20px;
width: 20px;
vertical-align: sub;
}
}

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

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

@ -158,7 +158,7 @@
"last-name-attribute-key": "Last name attribute key",
"login-button-icon": "Login button icon",
"login-button-label": "Provider label",
"login-button-label-1": "Login with $(Provider label)",
"login-button-label-placeholder": "Login with $(Provider label)",
"login-button-label-required": "Label is required.",
"login-provider": "Login provider",
"mapper": "Mapper",

Loading…
Cancel
Save