Commit da7d88470e415c95265205a4ebdcc1297ee5591c

Authored by Igor Kulikov
Committed by GitHub
2 parents 4596e416 6ef7e813

Merge pull request #3575 from vvlladd28/improvement/oauth2

Improvement OAuth2
Showing 23 changed files with 414 additions and 305 deletions
... ... @@ -7,14 +7,17 @@
7 7 "userInfoUri": "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email",
8 8 "clientAuthenticationMethod": "BASIC",
9 9 "userNameAttributeName": "email",
10   - "basic": {
11   - "emailAttributeKey": "email",
12   - "firstNameAttributeKey": "first_name",
13   - "lastNameAttributeKey": "last_name",
14   - "tenantNameStrategy": "DOMAIN"
  10 + "mapperConfig": {
  11 + "type": "BASIC",
  12 + "basic": {
  13 + "emailAttributeKey": "email",
  14 + "firstNameAttributeKey": "first_name",
  15 + "lastNameAttributeKey": "last_name",
  16 + "tenantNameStrategy": "DOMAIN"
  17 + }
15 18 },
16 19 "comment": null,
17   - "loginButtonIcon": "mdi:facebook",
  20 + "loginButtonIcon": "facebook-logo",
18 21 "loginButtonLabel": "Facebook",
19 22 "helpLink": "https://developers.facebook.com/docs/facebook-login/web#logindialog"
20 23 }
... ...
... ... @@ -7,11 +7,14 @@
7 7 "userInfoUri": "https://api.github.com/user",
8 8 "clientAuthenticationMethod": "BASIC",
9 9 "userNameAttributeName": "login",
10   - "basic": {
11   - "tenantNameStrategy": "DOMAIN"
  10 + "mapperConfig": {
  11 + "type": "GITHUB",
  12 + "basic": {
  13 + "tenantNameStrategy": "DOMAIN"
  14 + }
12 15 },
13 16 "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>",
14   - "loginButtonIcon": "mdi:github",
  17 + "loginButtonIcon": "github-logo",
15 18 "loginButtonLabel": "Github",
16 19 "helpLink": "https://docs.github.com/en/developers/apps/creating-an-oauth-app"
17 20 }
... ...
... ... @@ -8,14 +8,17 @@
8 8 "userInfoUri": "https://openidconnect.googleapis.com/v1/userinfo",
9 9 "clientAuthenticationMethod": "BASIC",
10 10 "userNameAttributeName": "email",
11   - "basic": {
12   - "emailAttributeKey": "email",
13   - "firstNameAttributeKey": "given_name",
14   - "lastNameAttributeKey": "family_name",
15   - "tenantNameStrategy": "DOMAIN"
  11 + "mapperConfig": {
  12 + "type": "BASIC",
  13 + "basic": {
  14 + "emailAttributeKey": "email",
  15 + "firstNameAttributeKey": "given_name",
  16 + "lastNameAttributeKey": "family_name",
  17 + "tenantNameStrategy": "DOMAIN"
  18 + }
16 19 },
17 20 "comment": null,
18   - "loginButtonIcon": "mdi:google",
  21 + "loginButtonIcon": "google-logo",
19 22 "loginButtonLabel": "Google",
20 23 "helpLink": "https://developers.google.com/adwords/api/docs/guides/authentication"
21 24 }
... ...
... ... @@ -34,8 +34,7 @@ import java.util.List;
34 34 public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo<OAuth2ClientRegistrationTemplateId> implements HasName {
35 35
36 36 private String providerId;
37   - private MapperType mapperType;
38   - private OAuth2BasicMapperConfig basic;
  37 + private OAuth2MapperConfig mapperConfig;
39 38 private String authorizationUri;
40 39 private String accessTokenUri;
41 40 private List<String> scope;
... ... @@ -51,8 +50,7 @@ public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditio
51 50 public OAuth2ClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
52 51 super(clientRegistrationTemplate);
53 52 this.providerId = clientRegistrationTemplate.providerId;
54   - this.mapperType = clientRegistrationTemplate.mapperType;
55   - this.basic = clientRegistrationTemplate.basic;
  53 + this.mapperConfig = clientRegistrationTemplate.mapperConfig;
56 54 this.authorizationUri = clientRegistrationTemplate.authorizationUri;
57 55 this.accessTokenUri = clientRegistrationTemplate.accessTokenUri;
58 56 this.scope = clientRegistrationTemplate.scope;
... ...
... ... @@ -109,17 +109,20 @@ public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity<OAuth2
109 109 this.loginButtonLabel = clientRegistrationTemplate.getLoginButtonLabel();
110 110 this.helpLink = clientRegistrationTemplate.getHelpLink();
111 111 this.additionalInfo = clientRegistrationTemplate.getAdditionalInfo();
112   - this.type = clientRegistrationTemplate.getMapperType();
113   - OAuth2BasicMapperConfig basicConfig = clientRegistrationTemplate.getBasic();
114   - if (basicConfig != null) {
115   - this.emailAttributeKey = basicConfig.getEmailAttributeKey();
116   - this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey();
117   - this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey();
118   - this.tenantNameStrategy = basicConfig.getTenantNameStrategy();
119   - this.tenantNamePattern = basicConfig.getTenantNamePattern();
120   - this.customerNamePattern = basicConfig.getCustomerNamePattern();
121   - this.defaultDashboardName = basicConfig.getDefaultDashboardName();
122   - this.alwaysFullScreen = basicConfig.isAlwaysFullScreen();
  112 + OAuth2MapperConfig mapperConfig = clientRegistrationTemplate.getMapperConfig();
  113 + if (mapperConfig != null){
  114 + this.type = mapperConfig.getType();
  115 + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic();
  116 + if (basicConfig != null) {
  117 + this.emailAttributeKey = basicConfig.getEmailAttributeKey();
  118 + this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey();
  119 + this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey();
  120 + this.tenantNameStrategy = basicConfig.getTenantNameStrategy();
  121 + this.tenantNamePattern = basicConfig.getTenantNamePattern();
  122 + this.customerNamePattern = basicConfig.getCustomerNamePattern();
  123 + this.defaultDashboardName = basicConfig.getDefaultDashboardName();
  124 + this.alwaysFullScreen = basicConfig.isAlwaysFullScreen();
  125 + }
123 126 }
124 127 }
125 128
... ... @@ -130,18 +133,21 @@ public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity<OAuth2
130 133 clientRegistrationTemplate.setCreatedTime(createdTime);
131 134 clientRegistrationTemplate.setAdditionalInfo(additionalInfo);
132 135
133   - clientRegistrationTemplate.setMapperType(type);
134 136 clientRegistrationTemplate.setProviderId(providerId);
135   - clientRegistrationTemplate.setBasic(
136   - OAuth2BasicMapperConfig.builder()
137   - .emailAttributeKey(emailAttributeKey)
138   - .firstNameAttributeKey(firstNameAttributeKey)
139   - .lastNameAttributeKey(lastNameAttributeKey)
140   - .tenantNameStrategy(tenantNameStrategy)
141   - .tenantNamePattern(tenantNamePattern)
142   - .customerNamePattern(customerNamePattern)
143   - .defaultDashboardName(defaultDashboardName)
144   - .alwaysFullScreen(alwaysFullScreen)
  137 + clientRegistrationTemplate.setMapperConfig(
  138 + OAuth2MapperConfig.builder()
  139 + .type(type)
  140 + .basic(OAuth2BasicMapperConfig.builder()
  141 + .emailAttributeKey(emailAttributeKey)
  142 + .firstNameAttributeKey(firstNameAttributeKey)
  143 + .lastNameAttributeKey(lastNameAttributeKey)
  144 + .tenantNameStrategy(tenantNameStrategy)
  145 + .tenantNamePattern(tenantNamePattern)
  146 + .customerNamePattern(customerNamePattern)
  147 + .defaultDashboardName(defaultDashboardName)
  148 + .alwaysFullScreen(alwaysFullScreen)
  149 + .build()
  150 + )
145 151 .build()
146 152 );
147 153 clientRegistrationTemplate.setAuthorizationUri(authorizationUri);
... ...
... ... @@ -104,7 +104,13 @@ public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService imple
104 104 if (StringUtils.isEmpty(clientRegistrationTemplate.getProviderId())) {
105 105 throw new DataValidationException("Provider ID should be specified!");
106 106 }
107   - if (clientRegistrationTemplate.getBasic() == null) {
  107 + if (clientRegistrationTemplate.getMapperConfig() == null) {
  108 + throw new DataValidationException("Mapper config should be specified!");
  109 + }
  110 + if (clientRegistrationTemplate.getMapperConfig().getType() == null) {
  111 + throw new DataValidationException("Mapper type should be specified!");
  112 + }
  113 + if (clientRegistrationTemplate.getMapperConfig().getBasic() == null) {
108 114 throw new DataValidationException("Basic mapper config should be specified!");
109 115 }
110 116 }
... ...
... ... @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId;
24 24 import org.thingsboard.server.common.data.oauth2.MapperType;
25 25 import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig;
26 26 import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
  27 +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
27 28 import org.thingsboard.server.dao.exception.DataValidationException;
28 29 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
29 30
... ... @@ -106,9 +107,9 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
106 107 OAuth2ClientRegistrationTemplate clientRegistrationTemplate = new OAuth2ClientRegistrationTemplate();
107 108 clientRegistrationTemplate.setProviderId(providerId);
108 109 clientRegistrationTemplate.setAdditionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString()));
109   - clientRegistrationTemplate.setMapperType(MapperType.BASIC);
110   - clientRegistrationTemplate.setBasic(
111   - OAuth2BasicMapperConfig.builder()
  110 + clientRegistrationTemplate.setMapperConfig(OAuth2MapperConfig.builder()
  111 + .type(MapperType.BASIC)
  112 + .basic(OAuth2BasicMapperConfig.builder()
112 113 .firstNameAttributeKey("firstName")
113 114 .lastNameAttributeKey("lastName")
114 115 .emailAttributeKey("email")
... ... @@ -116,7 +117,8 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
116 117 .defaultDashboardName("Test")
117 118 .alwaysFullScreen(true)
118 119 .build()
119   - );
  120 + )
  121 + .build());
120 122 clientRegistrationTemplate.setAuthorizationUri("authorizationUri");
121 123 clientRegistrationTemplate.setAccessTokenUri("tokenUri");
122 124 clientRegistrationTemplate.setScope(Arrays.asList("scope1", "scope2"));
... ...
... ... @@ -68,6 +68,27 @@ export class AppComponent implements OnInit {
68 68 )
69 69 );
70 70
  71 + this.matIconRegistry.addSvgIconLiteral(
  72 + 'google-logo',
  73 + this.domSanitizer.bypassSecurityTrustHtml(
  74 + '<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>'
  75 + )
  76 + );
  77 +
  78 + this.matIconRegistry.addSvgIconLiteral(
  79 + 'github-logo',
  80 + this.domSanitizer.bypassSecurityTrustHtml(
  81 + '<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>'
  82 + )
  83 + );
  84 +
  85 + this.matIconRegistry.addSvgIconLiteral(
  86 + 'facebook-logo',
  87 + this.domSanitizer.bypassSecurityTrustHtml(
  88 + '<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>'
  89 + )
  90 + );
  91 +
71 92 this.storageService.testLocalStorage();
72 93
73 94 this.setupTranslate();
... ...
... ... @@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http';
21 21 import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs';
22 22 import { catchError, map, mergeMap, tap } from 'rxjs/operators';
23 23
24   -import { LoginRequest, LoginResponse, OAuth2Client, PublicLoginRequest } from '@shared/models/login.models';
  24 +import { LoginRequest, LoginResponse, PublicLoginRequest } from '@shared/models/login.models';
25 25 import { ActivatedRoute, Router, UrlTree } from '@angular/router';
26 26 import { defaultHttpOptions } from '../http/http-utils';
27 27 import { UserService } from '../http/user.service';
... ... @@ -44,6 +44,7 @@ import { AdminService } from '@core/http/admin.service';
44 44 import { ActionNotificationShow } from '@core/notification/notification.actions';
45 45 import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
46 46 import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
  47 +import { OAuth2ClientInfo } from '@shared/models/oauth2.models';
47 48
48 49 @Injectable({
49 50 providedIn: 'root'
... ... @@ -67,7 +68,7 @@ export class AuthService {
67 68 }
68 69
69 70 redirectUrl: string;
70   - oauth2Clients: Array<OAuth2Client> = null;
  71 + oauth2Clients: Array<OAuth2ClientInfo> = null;
71 72
72 73 private refreshTokenSubject: ReplaySubject<LoginResponse> = null;
73 74 private jwtHelper = new JwtHelperService();
... ... @@ -197,13 +198,14 @@ export class AuthService {
197 198 });
198 199 }
199 200
200   - public loadOAuth2Clients(): Observable<Array<OAuth2Client>> {
201   - return this.http.post<Array<OAuth2Client>>(`/api/noauth/oauth2Clients`,
  201 + public loadOAuth2Clients(): Observable<Array<OAuth2ClientInfo>> {
  202 + return this.http.post<Array<OAuth2ClientInfo>>(`/api/noauth/oauth2Clients`,
202 203 null, defaultHttpOptions()).pipe(
203   - tap((OAuth2Clients) => {
204   - this.oauth2Clients = OAuth2Clients;
205   - })
206   - );
  204 + catchError(err => of([])),
  205 + tap((OAuth2Clients) => {
  206 + this.oauth2Clients = OAuth2Clients;
  207 + })
  208 + );
207 209 }
208 210
209 211 private forceDefaultPlace(authState?: AuthState, path?: string, params?: any): boolean {
... ...
... ... @@ -18,14 +18,7 @@ import { Injectable } from '@angular/core';
18 18 import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
19 19 import { Observable } from 'rxjs';
20 20 import { HttpClient } from '@angular/common/http';
21   -import {
22   - AdminSettings,
23   - ClientProviderTemplated,
24   - MailServerSettings,
25   - OAuth2Settings,
26   - SecuritySettings,
27   - UpdateMessage
28   -} from '@shared/models/settings.models';
  21 +import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models';
29 22
30 23 @Injectable({
31 24 providedIn: 'root'
... ... @@ -60,19 +53,6 @@ export class AdminService {
60 53 defaultHttpOptionsFromConfig(config));
61 54 }
62 55
63   - public getOAuth2Settings(config?: RequestConfig): Observable<OAuth2Settings> {
64   - return this.http.get<OAuth2Settings>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config));
65   - }
66   -
67   - public getOAuth2Template(config?: RequestConfig): Observable<Array<ClientProviderTemplated>> {
68   - return this.http.get<Array<ClientProviderTemplated>>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config));
69   - }
70   -
71   - public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, config?: RequestConfig): Observable<OAuth2Settings> {
72   - return this.http.post<OAuth2Settings>('/api/oauth2/config', OAuth2Setting,
73   - defaultHttpOptionsFromConfig(config));
74   - }
75   -
76 56 public checkUpdates(config?: RequestConfig): Observable<UpdateMessage> {
77 57 return this.http.get<UpdateMessage>(`/api/admin/updates`, defaultHttpOptionsFromConfig(config));
78 58 }
... ...
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Injectable } from '@angular/core';
  18 +import { HttpClient } from '@angular/common/http';
  19 +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
  20 +import { Observable } from 'rxjs';
  21 +import { OAuth2ClientRegistrationTemplate, OAuth2ClientsParams } from '@shared/models/oauth2.models';
  22 +
  23 +@Injectable({
  24 + providedIn: 'root'
  25 +})
  26 +export class OAuth2Service {
  27 +
  28 + constructor(
  29 + private http: HttpClient
  30 + ) { }
  31 +
  32 + public getOAuth2Settings(config?: RequestConfig): Observable<OAuth2ClientsParams> {
  33 + return this.http.get<OAuth2ClientsParams>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config));
  34 + }
  35 +
  36 + public getOAuth2Template(config?: RequestConfig): Observable<Array<OAuth2ClientRegistrationTemplate>> {
  37 + return this.http.get<Array<OAuth2ClientRegistrationTemplate>>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config));
  38 + }
  39 +
  40 + public saveOAuth2Settings(OAuth2Setting: OAuth2ClientsParams, config?: RequestConfig): Observable<OAuth2ClientsParams> {
  41 + return this.http.post<OAuth2ClientsParams>('/api/oauth2/config', OAuth2Setting,
  42 + defaultHttpOptionsFromConfig(config));
  43 + }
  44 +}
... ...
... ... @@ -28,6 +28,7 @@ export * from './entity-relation.service';
28 28 export * from './entity-view.service';
29 29 export * from './event.service';
30 30 export * from './http-utils';
  31 +export * from './oauth2.service';
31 32 export * from './queue.service';
32 33 export * from './rule-chain.service';
33 34 export * from './tenant.service';
... ...
... ... @@ -33,7 +33,7 @@
33 33 <mat-checkbox formControlName="enabled">
34 34 {{ 'admin.oauth2.enable' | translate }}
35 35 </mat-checkbox>
36   - <section *ngIf="oauth2SettingsForm.get('enabled').value && !(isLoading$ | async)" style="margin-top: 1em;">
  36 + <section *ngIf="oauth2SettingsForm.get('enabled').value" style="margin-top: 1em;">
37 37 <ng-container formArrayName="domainsParams">
38 38 <div class="container">
39 39 <mat-accordion multi>
... ... @@ -59,7 +59,7 @@
59 59 <section *ngFor="let domainInfo of clientDomainInfos(domain).controls; let n = index; trackBy: trackByParams"
60 60 class="domains-list">
61 61 <div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
62   - <div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
  62 + <div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
63 63 <div fxLayout="column" fxFlex.sm="60" fxFlex.gt-sm="50">
64 64 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
65 65 <mat-form-field fxFlex="30" fxFlex.xs class="mat-block">
... ... @@ -160,7 +160,7 @@
160 160 </mat-option>
161 161 </mat-select>
162 162 </mat-form-field>
163   - <div [tb-help]="getHelpLink(registration)" *ngIf="getProviderName(registration) !== 'Custom'"></div>
  163 + <div [tb-help]="getHelpLink(registration)" *ngIf="!isCustomProvider(registration)"></div>
164 164 </section>
165 165
166 166 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
... ... @@ -182,9 +182,9 @@
182 182 </div>
183 183
184 184 <mat-expansion-panel class="mat-elevation-z0 custom-settings"
185   - [disabled]="getProviderName(registration) === 'Custom'"
186   - [expanded]="getProviderName(registration) === 'Custom'">
187   - <mat-expansion-panel-header [fxHide]="getProviderName(registration) === 'Custom'">
  185 + [disabled]="isCustomProvider(registration)"
  186 + [expanded]="isCustomProvider(registration)">
  187 + <mat-expansion-panel-header [fxHide]="isCustomProvider(registration)">
188 188 <mat-panel-description fxLayoutAlign="end center">
189 189 {{ 'admin.oauth2.custom-setting' | translate }}
190 190 </mat-panel-description>
... ... @@ -199,7 +199,7 @@
199 199 <button mat-icon-button matSuffix
200 200 type="button"
201 201 (click)="toggleEditMode(registration, 'accessTokenUri')"
202   - *ngIf="getProviderName(registration) !== 'Custom'">
  202 + *ngIf="!isCustomProvider(registration)">
203 203 <mat-icon class="material-icons">create</mat-icon>
204 204 </button>
205 205 <mat-error *ngIf="registration.get('accessTokenUri').hasError('required')">
... ... @@ -216,7 +216,7 @@
216 216 <button mat-icon-button matSuffix
217 217 type="button"
218 218 (click)="toggleEditMode(registration, 'authorizationUri')"
219   - *ngIf="getProviderName(registration) !== 'Custom'">
  219 + *ngIf="!isCustomProvider(registration)">
220 220 <mat-icon class="material-icons">create</mat-icon>
221 221 </button>
222 222 <mat-error *ngIf="registration.get('authorizationUri').hasError('required')">
... ... @@ -235,7 +235,7 @@
235 235 <button mat-icon-button matSuffix
236 236 type="button" aria-label="Clear"
237 237 (click)="toggleEditMode(registration, 'jwkSetUri')"
238   - *ngIf="getProviderName(registration) !== 'Custom'">
  238 + *ngIf="!isCustomProvider(registration)">
239 239 <mat-icon class="material-icons">create</mat-icon>
240 240 </button>
241 241 <mat-error *ngIf="registration.get('jwkSetUri').hasError('pattern')">
... ... @@ -249,7 +249,7 @@
249 249 <button mat-icon-button matSuffix
250 250 type="button"
251 251 (click)="toggleEditMode(registration, 'userInfoUri')"
252   - *ngIf="getProviderName(registration) !== 'Custom'">
  252 + *ngIf="!isCustomProvider(registration)">
253 253 <mat-icon class="material-icons">create</mat-icon>
254 254 </button>
255 255 <mat-error *ngIf="registration.get('userInfoUri').hasError('required')">
... ... @@ -271,11 +271,11 @@
271 271 </mat-select>
272 272 </mat-form-field>
273 273
274   - <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" *ngIf="getProviderName(registration) === 'Custom'">
  274 + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" *ngIf="isCustomProvider(registration)">
275 275 <mat-form-field fxFlex class="mat-block" floatLabel="always">
276 276 <mat-label translate>admin.oauth2.login-button-label</mat-label>
277 277 <input matInput formControlName="loginButtonLabel"
278   - placeholder="{{ 'admin.oauth2.login-button-label-1' | translate }}"
  278 + placeholder="{{ 'admin.oauth2.login-button-label-placeholder' | translate }}"
279 279 required>
280 280 <mat-error *ngIf="registration.get('loginButtonLabel').hasError('required')">
281 281 {{ 'admin.oauth2.login-button-label-required' | translate }}
... ... @@ -316,6 +316,10 @@
316 316 {{ 'admin.oauth2.scope-required' | translate }}
317 317 </mat-error>
318 318 </mat-form-field>
  319 + <tb-error style="display: block; margin-top: -24px;"
  320 + [error]="registration.get('scope').hasError('required')
  321 + ? ('admin.oauth2.scope-required' | translate) : ''">
  322 + </tb-error>
319 323
320 324 </mat-tab>
321 325 <mat-tab label="{{ 'admin.oauth2.mapper' | translate }}">
... ... @@ -332,16 +336,17 @@
332 336 <mat-form-field fxFlex class="mat-block">
333 337 <mat-label translate>admin.oauth2.type</mat-label>
334 338 <mat-select formControlName="type">
335   - <mat-option *ngFor="let converterTypeExternalUser of converterTypesExternalUser"
336   - [value]="converterTypeExternalUser">
337   - {{ converterTypeExternalUser }}
  339 + <mat-option *ngFor="let mapperConfigType of mapperConfigTypes"
  340 + [value]="mapperConfigType">
  341 + {{ mapperConfigType }}
338 342 </mat-option>
339 343 </mat-select>
340 344 </mat-form-field>
341 345
342 346 <section formGroupName="basic"
343   - *ngIf="registration.get('mapperConfig.type').value === 'BASIC'">
344   - <mat-form-field class="mat-block">
  347 + *ngIf="registration.get('mapperConfig.type').value !== mapperConfigType.CUSTOM">
  348 + <mat-form-field class="mat-block"
  349 + *ngIf="registration.get('mapperConfig.type').value !== mapperConfigType.GITHUB">
345 350 <mat-label translate>admin.oauth2.email-attribute-key</mat-label>
346 351 <input matInput formControlName="emailAttributeKey" required>
347 352 <mat-error
... ... @@ -403,7 +408,7 @@
403 408 </section>
404 409
405 410 <section formGroupName="custom"
406   - *ngIf="registration.get('mapperConfig.type').value === 'CUSTOM'">
  411 + *ngIf="registration.get('mapperConfig.type').value === mapperConfigType.CUSTOM">
407 412 <mat-form-field class="mat-block">
408 413 <mat-label translate>admin.oauth2.url</mat-label>
409 414 <input matInput formControlName="url" required>
... ...
... ... @@ -41,6 +41,14 @@
41 41 padding-bottom: 0;
42 42 }
43 43 }
  44 +
  45 + .mat-expansion-panel {
  46 + .mat-expansion-panel-header {
  47 + &.mat-expanded {
  48 + height: 48px;
  49 + }
  50 + }
  51 + }
44 52 }
45 53
46 54 :host ::ng-deep{
... ... @@ -49,9 +57,6 @@
49 57 .mat-expansion-panel-body{
50 58 padding: 0 2px 1em;
51 59 }
52   - .mat-tab-label{
53   - text-transform: none;
54   - }
55 60 .mat-form-field-suffix .mat-icon-button .mat-icon{
56 61 font-size: 18px;
57 62 }
... ...
... ... @@ -15,25 +15,24 @@
15 15 ///
16 16
17 17 import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
18   -import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
  18 +import { AbstractControl, FormArray, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
19 19 import {
20 20 ClientAuthenticationMethod,
21   - ClientProviderTemplated,
22 21 ClientRegistration,
23 22 DomainInfo,
24 23 DomainSchema,
25 24 domainSchemaTranslations,
26   - DomainsParam,
27 25 MapperConfig,
28 26 MapperConfigBasic,
29 27 MapperConfigCustom,
30 28 MapperConfigType,
31   - OAuth2Settings,
  29 + OAuth2ClientRegistrationTemplate,
  30 + OAuth2ClientsDomainParams,
  31 + OAuth2ClientsParams,
32 32 TenantNameStrategy
33   -} from '@shared/models/settings.models';
  33 +} from '@shared/models/oauth2.models';
34 34 import { Store } from '@ngrx/store';
35 35 import { AppState } from '@core/core.state';
36   -import { AdminService } from '@core/http/admin.service';
37 36 import { PageComponent } from '@shared/components/page.component';
38 37 import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
39 38 import { COMMA, ENTER } from '@angular/cdk/keycodes';
... ... @@ -42,7 +41,8 @@ import { WINDOW } from '@core/services/window.service';
42 41 import { forkJoin, Subscription } from 'rxjs';
43 42 import { DialogService } from '@core/services/dialog.service';
44 43 import { TranslateService } from '@ngx-translate/core';
45   -import { isDefined } from '@core/utils';
  44 +import { isDefined, isDefinedAndNotNull } from '@core/utils';
  45 +import { OAuth2Service } from '@core/http/oauth2.service';
46 46
47 47 @Component({
48 48 selector: 'tb-oauth2-settings',
... ... @@ -53,7 +53,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
53 53
54 54 private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/;
55 55 private subscriptions: Subscription[] = [];
56   - private templates = new Map<string, ClientProviderTemplated>();
  56 + private templates = new Map<string, OAuth2ClientRegistrationTemplate>();
57 57 private defaultProvider = {
58 58 additionalInfo: {
59 59 providerName: 'Custom'
... ... @@ -75,10 +75,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
75 75 readonly separatorKeysCodes: number[] = [ENTER, COMMA];
76 76
77 77 oauth2SettingsForm: FormGroup;
78   - oauth2Settings: OAuth2Settings;
  78 + auth2ClientsParams: OAuth2ClientsParams;
79 79
80 80 clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod);
81   - converterTypesExternalUser = Object.keys(MapperConfigType);
  81 + mapperConfigType = MapperConfigType;
  82 + mapperConfigTypes = Object.keys(MapperConfigType);
82 83 tenantNameStrategies = Object.keys(TenantNameStrategy);
83 84 protocols = Object.keys(DomainSchema);
84 85 domainSchemaTranslations = domainSchemaTranslations;
... ... @@ -86,7 +87,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
86 87 templateProvider = ['Custom'];
87 88
88 89 constructor(protected store: Store<AppState>,
89   - private adminService: AdminService,
  90 + private oauth2Service: OAuth2Service,
90 91 private fb: FormBuilder,
91 92 private dialogService: DialogService,
92 93 private translate: TranslateService,
... ... @@ -97,13 +98,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
97 98 ngOnInit(): void {
98 99 this.buildOAuth2SettingsForm();
99 100 forkJoin([
100   - this.adminService.getOAuth2Template(),
101   - this.adminService.getOAuth2Settings()
  101 + this.oauth2Service.getOAuth2Template(),
  102 + this.oauth2Service.getOAuth2Settings()
102 103 ]).subscribe(
103   - ([templates, oauth2Settings]) => {
  104 + ([templates, auth2ClientsParams]) => {
104 105 this.initTemplates(templates);
105   - this.oauth2Settings = oauth2Settings;
106   - this.initOAuth2Settings(this.oauth2Settings);
  106 + this.auth2ClientsParams = auth2ClientsParams;
  107 + this.initOAuth2Settings(this.auth2ClientsParams);
107 108 }
108 109 );
109 110 }
... ... @@ -115,8 +116,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
115 116 });
116 117 }
117 118
118   - private initTemplates(templates: ClientProviderTemplated[]): void {
119   - templates.map(provider => this.templates.set(provider.name, provider));
  119 + private initTemplates(templates: OAuth2ClientRegistrationTemplate[]): void {
  120 + templates.map(provider => {
  121 + delete provider.additionalInfo;
  122 + this.templates.set(provider.name, provider);
  123 + });
120 124 this.templateProvider.push(...Array.from(this.templates.keys()));
121 125 this.templateProvider.sort();
122 126 }
... ... @@ -169,10 +173,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
169 173 });
170 174 }
171 175
172   - private initOAuth2Settings(oauth2Settings: OAuth2Settings): void {
173   - if (oauth2Settings) {
174   - this.oauth2SettingsForm.patchValue({enabled: oauth2Settings.enabled}, {emitEvent: false});
175   - oauth2Settings.domainsParams.forEach((domain) => {
  176 + private initOAuth2Settings(auth2ClientsParams: OAuth2ClientsParams): void {
  177 + if (auth2ClientsParams) {
  178 + this.oauth2SettingsForm.patchValue({enabled: auth2ClientsParams.enabled}, {emitEvent: false});
  179 + auth2ClientsParams.domainsParams.forEach((domain) => {
176 180 this.domainsParams.push(this.buildDomainsForm(domain));
177 181 });
178 182 }
... ... @@ -204,17 +208,17 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
204 208 return this.translate.instant('admin.oauth2.new-domain');
205 209 }
206 210
207   - private buildDomainsForm(domainParams?: DomainsParam): FormGroup {
  211 + private buildDomainsForm(auth2ClientsDomainParams?: OAuth2ClientsDomainParams): FormGroup {
208 212 const formDomain = this.fb.group({
209 213 domainInfos: this.fb.array([], Validators.required),
210 214 clientRegistrations: this.fb.array([], Validators.required)
211 215 });
212 216
213   - if (domainParams) {
214   - domainParams.domainInfos.forEach((domain) => {
  217 + if (auth2ClientsDomainParams) {
  218 + auth2ClientsDomainParams.domainInfos.forEach((domain) => {
215 219 this.clientDomainInfos(formDomain).push(this.buildDomainForm(domain));
216 220 });
217   - domainParams.clientRegistrations.forEach((registration) => {
  221 + auth2ClientsDomainParams.clientRegistrations.forEach((registration) => {
218 222 this.clientDomainProviders(formDomain).push(this.buildProviderForm(registration));
219 223 });
220 224 } else {
... ... @@ -235,10 +239,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
235 239 return domain;
236 240 }
237 241
238   - private buildProviderForm(registrationData?: ClientRegistration): FormGroup {
  242 + private buildProviderForm(clientRegistration?: ClientRegistration): FormGroup {
239 243 let additionalInfo = null;
240   - if (registrationData?.additionalInfo) {
241   - additionalInfo = JSON.parse(registrationData.additionalInfo);
  244 + if (isDefinedAndNotNull(clientRegistration?.additionalInfo)) {
  245 + additionalInfo = clientRegistration.additionalInfo;
242 246 if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) {
243 247 additionalInfo.providerName = 'Custom';
244 248 }
... ... @@ -248,73 +252,80 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
248 252 defaultProviderName = 'Google';
249 253 }
250 254
251   - const clientRegistration = this.fb.group({
252   - id: this.fb.group({
253   - id: [registrationData?.id?.id ? registrationData.id.id : null],
254   - entityType: [registrationData?.id?.entityType ? registrationData.id.entityType : null]
255   - }),
  255 + const clientRegistrationFormGroup = this.fb.group({
256 256 additionalInfo: this.fb.group({
257 257 providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required]
258 258 }),
259   - loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required],
260   - loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null],
261   - clientId: [registrationData?.clientId ? registrationData.clientId : '', Validators.required],
262   - clientSecret: [registrationData?.clientSecret ? registrationData.clientSecret : '', Validators.required],
263   - accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '',
  259 + loginButtonLabel: [clientRegistration?.loginButtonLabel ? clientRegistration.loginButtonLabel : null, Validators.required],
  260 + loginButtonIcon: [clientRegistration?.loginButtonIcon ? clientRegistration.loginButtonIcon : null],
  261 + clientId: [clientRegistration?.clientId ? clientRegistration.clientId : '', Validators.required],
  262 + clientSecret: [clientRegistration?.clientSecret ? clientRegistration.clientSecret : '', Validators.required],
  263 + accessTokenUri: [clientRegistration?.accessTokenUri ? clientRegistration.accessTokenUri : '',
264 264 [Validators.required,
265 265 Validators.pattern(this.URL_REGEXP)]],
266   - authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '',
  266 + authorizationUri: [clientRegistration?.authorizationUri ? clientRegistration.authorizationUri : '',
267 267 [Validators.required,
268 268 Validators.pattern(this.URL_REGEXP)]],
269   - scope: this.fb.array(registrationData?.scope ? registrationData.scope : [], Validators.required),
270   - jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)],
271   - userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '',
  269 + scope: this.fb.array(clientRegistration?.scope ? clientRegistration.scope : [], this.validateScope),
  270 + jwkSetUri: [clientRegistration?.jwkSetUri ? clientRegistration.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)],
  271 + userInfoUri: [clientRegistration?.userInfoUri ? clientRegistration.userInfoUri : '',
272 272 [Validators.required,
273 273 Validators.pattern(this.URL_REGEXP)]],
274 274 clientAuthenticationMethod: [
275   - registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : ClientAuthenticationMethod.POST,
  275 + clientRegistration?.clientAuthenticationMethod ? clientRegistration.clientAuthenticationMethod : ClientAuthenticationMethod.POST,
276 276 Validators.required],
277 277 userNameAttributeName: [
278   - registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required],
  278 + clientRegistration?.userNameAttributeName ? clientRegistration.userNameAttributeName : 'email', Validators.required],
279 279 mapperConfig: this.fb.group({
280   - allowUserCreation: [registrationData?.mapperConfig?.allowUserCreation ? registrationData.mapperConfig.allowUserCreation : true],
281   - activateUser: [registrationData?.mapperConfig?.activateUser ? registrationData.mapperConfig.activateUser : false],
282   - type: [registrationData?.mapperConfig?.type ? registrationData.mapperConfig.type : MapperConfigType.BASIC, Validators.required]
  280 + allowUserCreation: [
  281 + clientRegistration?.mapperConfig?.allowUserCreation ? clientRegistration.mapperConfig.allowUserCreation : true
  282 + ],
  283 + activateUser: [clientRegistration?.mapperConfig?.activateUser ? clientRegistration.mapperConfig.activateUser : false],
  284 + type: [
  285 + clientRegistration?.mapperConfig?.type ? clientRegistration.mapperConfig.type : MapperConfigType.BASIC, Validators.required
  286 + ]
283 287 }
284 288 )
285 289 });
286 290
287   - if (registrationData) {
288   - this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig);
  291 + if (clientRegistration) {
  292 + this.changeMapperConfigType(clientRegistrationFormGroup, clientRegistration.mapperConfig.type, clientRegistration.mapperConfig);
289 293 } else {
290   - this.changeMapperConfigType(clientRegistration, MapperConfigType.BASIC);
291   - this.setProviderDefaultValue(defaultProviderName, clientRegistration);
  294 + this.changeMapperConfigType(clientRegistrationFormGroup, MapperConfigType.BASIC);
  295 + this.setProviderDefaultValue(defaultProviderName, clientRegistrationFormGroup);
292 296 }
293 297
294   - this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => {
295   - this.changeMapperConfigType(clientRegistration, value);
  298 + this.subscriptions.push(clientRegistrationFormGroup.get('mapperConfig.type').valueChanges.subscribe((value) => {
  299 + this.changeMapperConfigType(clientRegistrationFormGroup, value);
296 300 }));
297 301
298   - this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => {
299   - (clientRegistration.get('scope') as FormArray).clear();
300   - this.setProviderDefaultValue(provider, clientRegistration);
  302 + this.subscriptions.push(clientRegistrationFormGroup.get('additionalInfo.providerName').valueChanges.subscribe((provider) => {
  303 + (clientRegistrationFormGroup.get('scope') as FormArray).clear();
  304 + this.setProviderDefaultValue(provider, clientRegistrationFormGroup);
301 305 }));
302 306
303   - return clientRegistration;
  307 + return clientRegistrationFormGroup;
  308 + }
  309 +
  310 + private validateScope (control: AbstractControl): ValidationErrors | null {
  311 + const scope: string[] = control.value;
  312 + if (!scope || !scope.length) {
  313 + return {
  314 + required: true
  315 + };
  316 + }
  317 + return null;
304 318 }
305 319
306 320 private setProviderDefaultValue(provider: string, clientRegistration: FormGroup) {
307 321 if (provider === 'Custom') {
308   - const defaultSettings = {...this.defaultProvider, ...{id: clientRegistration.get('id').value}};
309   - clientRegistration.reset(defaultSettings, {emitEvent: false});
  322 + clientRegistration.reset(this.defaultProvider, {emitEvent: false});
310 323 clientRegistration.get('accessTokenUri').enable();
311 324 clientRegistration.get('authorizationUri').enable();
312 325 clientRegistration.get('jwkSetUri').enable();
313 326 clientRegistration.get('userInfoUri').enable();
314 327 } else {
315 328 const template = this.templates.get(provider);
316   - delete template.id;
317   - delete template.additionalInfo;
318 329 template.clientId = '';
319 330 template.clientSecret = '';
320 331 template.scope.forEach(() => {
... ... @@ -324,44 +335,33 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
324 335 clientRegistration.get('authorizationUri').disable();
325 336 clientRegistration.get('jwkSetUri').disable();
326 337 clientRegistration.get('userInfoUri').disable();
327   - clientRegistration.patchValue(template, {emitEvent: false});
  338 + clientRegistration.patchValue(template);
328 339 }
329 340 }
330 341
331 342 private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) {
332 343 const mapperConfig = control.get('mapperConfig') as FormGroup;
333   - if (type === MapperConfigType.BASIC) {
334   - mapperConfig.removeControl('custom');
335   - mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic));
336   - } else {
  344 + if (type === MapperConfigType.CUSTOM) {
337 345 mapperConfig.removeControl('basic');
338 346 mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
  347 + } else {
  348 + mapperConfig.removeControl('custom');
  349 + mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic));
339 350 }
340 351 }
341 352
342 353 save(): void {
343   - const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue());
344   - this.adminService.saveOAuth2Settings(setting).subscribe(
  354 + const setting = this.oauth2SettingsForm.getRawValue();
  355 + this.oauth2Service.saveOAuth2Settings(setting).subscribe(
345 356 (oauth2Settings) => {
346   - this.oauth2Settings = oauth2Settings;
347   - this.oauth2SettingsForm.markAsPristine();
  357 + this.auth2ClientsParams = oauth2Settings;
  358 + this.oauth2SettingsForm.patchValue(this.oauth2SettingsForm, {emitEvent: false});
348 359 this.oauth2SettingsForm.markAsUntouched();
  360 + this.oauth2SettingsForm.markAsPristine();
349 361 }
350 362 );
351 363 }
352 364
353   - private prepareFormValue(formValue: OAuth2Settings): OAuth2Settings{
354   - formValue.domainsParams.forEach((setting, index) => {
355   - setting.clientRegistrations.forEach((registration) => {
356   - registration.additionalInfo = JSON.stringify(registration.additionalInfo);
357   - if (registration.id.id === null) {
358   - delete registration.id;
359   - }
360   - });
361   - });
362   - return formValue;
363   - }
364   -
365 365 confirmForm(): FormGroup {
366 366 return this.oauth2SettingsForm;
367 367 }
... ... @@ -451,6 +451,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
451 451 return controller.get('additionalInfo.providerName').value;
452 452 }
453 453
  454 + isCustomProvider(controller: AbstractControl): boolean {
  455 + return this.getProviderName(controller) === 'Custom';
  456 + }
  457 +
454 458 getHelpLink(controller: AbstractControl): string {
455 459 const provider = controller.get('additionalInfo.providerName').value;
456 460 if (provider === null || provider === 'Custom') {
... ...
... ... @@ -28,6 +28,19 @@
28 28 <span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span>
29 29 <div tb-toast fxLayout="column" class="layout-padding">
30 30 <span style="height: 50px;"></span>
  31 + <div class="oauth-container tb-default" fxLayout="column" fxLayoutGap="16px" *ngIf="oauth2Clients?.length">
  32 + <ng-container *ngFor="let oauth2Client of oauth2Clients">
  33 + <a mat-raised-button class="login-with-button" href="{{ oauth2Client.url }}">
  34 + <mat-icon class="icon" svgIcon="{{ oauth2Client.icon }}"></mat-icon>
  35 + {{ 'login.login-with' | translate: {name: oauth2Client.name} }}
  36 + </a>
  37 + </ng-container>
  38 + <div class="container-divider">
  39 + <div class="line"><mat-divider></mat-divider></div>
  40 + <div class="text mat-typography">{{ "login.or" | translate | uppercase }}</div>
  41 + <div class="line"><mat-divider></mat-divider></div>
  42 + </div>
  43 + </div>
31 44 <mat-form-field>
32 45 <mat-label translate>login.username</mat-label>
33 46 <input id="username-input" matInput type="email" autofocus formControlName="username" email required/>
... ... @@ -41,7 +54,7 @@
41 54 <input id="password-input" matInput type="password" formControlName="password"/>
42 55 <mat-icon matPrefix>lock</mat-icon>
43 56 </mat-form-field>
44   - <div fxLayoutAlign="end center">
  57 + <div fxLayoutAlign="end center" class="forgot-password">
45 58 <button class="tb-reset-password" mat-button type="button" routerLink="/login/resetPasswordRequest">{{ 'login.forgot-password' | translate }}
46 59 </button>
47 60 </div>
... ... @@ -49,19 +62,6 @@
49 62 <button mat-raised-button color="accent" [disabled]="(isLoading$ | async)"
50 63 type="submit">{{ 'login.login' | translate }}</button>
51 64 </div>
52   - <div class="oauth-container" fxLayout="column" fxLayoutGap="16px" *ngIf="oauth2Clients?.length">
53   - <div class="container-divider">
54   - <div class="line"><mat-divider></mat-divider></div>
55   - <div class="text mat-typography">{{ "login.or" | translate | uppercase }}</div>
56   - <div class="line"><mat-divider></mat-divider></div>
57   - </div>
58   - <ng-container *ngFor="let oauth2Client of oauth2Clients">
59   - <a mat-raised-button color="warn" class="centered" href="{{ oauth2Client.url }}">
60   - <mat-icon svgIcon="{{ oauth2Client.icon }}"></mat-icon>
61   - {{ 'login.login-with' | translate: {name: oauth2Client.name} }}
62   - </a>
63   - </ng-container>
64   - </div>
65 65 </div>
66 66 </fieldset>
67 67 </form>
... ...
... ... @@ -27,8 +27,11 @@
27 27 width: 550px !important;
28 28 }
29 29
30   - .tb-reset-password{
31   - padding: 0 6px;
  30 + .forgot-password {
  31 + padding: 0 0.5em 1em;
  32 + .tb-reset-password {
  33 + padding: 0 6px;
  34 + }
32 35 }
33 36
34 37 .tb-action-button{
... ... @@ -65,9 +68,17 @@
65 68 min-width: 20px;
66 69 }
67 70
68   - a.centered {
  71 + a.login-with-button {
  72 + color: rgba(black, 0.87);
  73 +
69 74 &:hover {
70   - border-bottom: 0px;
  75 + border-bottom: 0;
  76 + }
  77 +
  78 + .icon{
  79 + height: 20px;
  80 + width: 20px;
  81 + vertical-align: sub;
71 82 }
72 83 }
73 84
... ...
... ... @@ -23,7 +23,7 @@ import { FormBuilder } from '@angular/forms';
23 23 import { HttpErrorResponse } from '@angular/common/http';
24 24 import { Constants } from '@shared/models/constants';
25 25 import { Router } from '@angular/router';
26   -import { OAuth2Client } from '@shared/models/login.models';
  26 +import { OAuth2ClientInfo } from '@shared/models/oauth2.models';
27 27
28 28 @Component({
29 29 selector: 'tb-login',
... ... @@ -36,7 +36,7 @@ export class LoginComponent extends PageComponent implements OnInit {
36 36 username: '',
37 37 password: ''
38 38 });
39   - oauth2Clients: Array<OAuth2Client> = null;
  39 + oauth2Clients: Array<OAuth2ClientInfo> = null;
40 40
41 41 constructor(protected store: Store<AppState>,
42 42 private authService: AuthService,
... ...
... ... @@ -27,9 +27,3 @@ export interface LoginResponse {
27 27 token: string;
28 28 refreshToken: string;
29 29 }
30   -
31   -export interface OAuth2Client {
32   - name: string;
33   - icon?: string;
34   - url: string;
35   -}
... ...
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { HasUUID } from '@shared/models/id/has-uuid';
  18 +
  19 +export interface OAuth2ClientsParams {
  20 + enabled: boolean;
  21 + domainsParams: OAuth2ClientsDomainParams[];
  22 +}
  23 +
  24 +export interface OAuth2ClientsDomainParams {
  25 + clientRegistrations: ClientRegistration[];
  26 + domainInfos: DomainInfo[];
  27 +}
  28 +
  29 +export interface DomainInfo {
  30 + name: string;
  31 + scheme: DomainSchema;
  32 +}
  33 +
  34 +export enum DomainSchema{
  35 + HTTP = 'HTTP',
  36 + HTTPS = 'HTTPS',
  37 + MIXED = 'MIXED'
  38 +}
  39 +
  40 +export const domainSchemaTranslations = new Map<DomainSchema, string>(
  41 + [
  42 + [DomainSchema.HTTP, 'admin.oauth2.domain-schema-http'],
  43 + [DomainSchema.HTTPS, 'admin.oauth2.domain-schema-https'],
  44 + [DomainSchema.MIXED, 'admin.oauth2.domain-schema-mixed']
  45 + ]
  46 +);
  47 +
  48 +export enum MapperConfigType{
  49 + BASIC = 'BASIC',
  50 + CUSTOM = 'CUSTOM',
  51 + GITHUB = 'GITHUB'
  52 +}
  53 +
  54 +export enum TenantNameStrategy{
  55 + DOMAIN = 'DOMAIN',
  56 + EMAIL = 'EMAIL',
  57 + CUSTOM = 'CUSTOM'
  58 +}
  59 +
  60 +export interface OAuth2ClientRegistrationTemplate extends ClientRegistration{
  61 + comment: string;
  62 + createdTime: number;
  63 + helpLink: string;
  64 + name: string;
  65 + providerId: string;
  66 + id: HasUUID;
  67 +}
  68 +
  69 +export interface ClientRegistration {
  70 + loginButtonLabel: string;
  71 + loginButtonIcon: string;
  72 + clientId: string;
  73 + clientSecret: string;
  74 + accessTokenUri: string;
  75 + authorizationUri: string;
  76 + scope: string[];
  77 + jwkSetUri?: string;
  78 + userInfoUri: string;
  79 + clientAuthenticationMethod: ClientAuthenticationMethod;
  80 + userNameAttributeName: string;
  81 + mapperConfig: MapperConfig;
  82 + additionalInfo: string;
  83 +}
  84 +
  85 +export enum ClientAuthenticationMethod {
  86 + BASIC = 'BASIC',
  87 + POST = 'POST'
  88 +}
  89 +
  90 +export interface MapperConfig {
  91 + allowUserCreation: boolean;
  92 + activateUser: boolean;
  93 + type: MapperConfigType;
  94 + basic?: MapperConfigBasic;
  95 + custom?: MapperConfigCustom;
  96 +}
  97 +
  98 +export interface MapperConfigBasic {
  99 + emailAttributeKey: string;
  100 + firstNameAttributeKey?: string;
  101 + lastNameAttributeKey?: string;
  102 + tenantNameStrategy: TenantNameStrategy;
  103 + tenantNamePattern?: string;
  104 + customerNamePattern?: string;
  105 + defaultDashboardName?: string;
  106 + alwaysFullScreen?: boolean;
  107 +}
  108 +
  109 +export interface MapperConfigCustom {
  110 + url: string;
  111 + username?: string;
  112 + password?: string;
  113 +}
  114 +
  115 +export interface OAuth2ClientInfo {
  116 + name: string;
  117 + icon?: string;
  118 + url: string;
  119 +}
... ...
... ... @@ -36,6 +36,7 @@ export * from './error.models';
36 36 export * from './event.models';
37 37 export * from './login.models';
38 38 export * from './material.models';
  39 +export * from './oauth2.models';
39 40 export * from './queue.models';
40 41 export * from './relation.models';
41 42 export * from './rule-chain.models';
... ...
... ... @@ -14,9 +14,6 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { EntityId } from '@shared/models/id/entity-id';
18   -import { TenantId } from '@shared/models/id/tenant-id';
19   -
20 17 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])$/;
21 18
22 19 export interface AdminSettings<T> {
... ... @@ -63,99 +60,3 @@ export interface UpdateMessage {
63 60 message: string;
64 61 updateAvailable: boolean;
65 62 }
66   -
67   -export interface OAuth2Settings {
68   - enabled: boolean;
69   - domainsParams: DomainsParam[];
70   -}
71   -
72   -export interface DomainsParam {
73   - clientRegistrations: ClientRegistration[];
74   - domainInfos: DomainInfo[];
75   -}
76   -
77   -export interface DomainInfo {
78   - name: string;
79   - scheme: DomainSchema;
80   -}
81   -
82   -export enum DomainSchema{
83   - HTTP = 'HTTP',
84   - HTTPS = 'HTTPS',
85   - MIXED = 'MIXED'
86   -}
87   -
88   -export const domainSchemaTranslations = new Map<DomainSchema, string>(
89   - [
90   - [DomainSchema.HTTP, 'admin.oauth2.domain-schema-http'],
91   - [DomainSchema.HTTPS, 'admin.oauth2.domain-schema-https'],
92   - [DomainSchema.MIXED, 'admin.oauth2.domain-schema-mixed']
93   - ]
94   -);
95   -
96   -export enum MapperConfigType{
97   - BASIC = 'BASIC',
98   - CUSTOM = 'CUSTOM'
99   -}
100   -
101   -export enum TenantNameStrategy{
102   - DOMAIN = 'DOMAIN',
103   - EMAIL = 'EMAIL',
104   - CUSTOM = 'CUSTOM'
105   -}
106   -
107   -export interface ClientProviderTemplated extends ClientRegistration{
108   - comment: string;
109   - createdTime: number;
110   - helpLink: string;
111   - name: string;
112   - providerId: string;
113   - tenantId: TenantId;
114   -}
115   -
116   -export interface ClientRegistration {
117   - loginButtonLabel: string;
118   - loginButtonIcon: string;
119   - clientId: string;
120   - clientSecret: string;
121   - accessTokenUri: string;
122   - authorizationUri: string;
123   - scope: string[];
124   - jwkSetUri?: string;
125   - userInfoUri: string;
126   - clientAuthenticationMethod: ClientAuthenticationMethod;
127   - userNameAttributeName: string;
128   - mapperConfig: MapperConfig;
129   - id?: EntityId;
130   - additionalInfo: string;
131   -}
132   -
133   -export enum ClientAuthenticationMethod {
134   - BASIC = 'BASIC',
135   - POST = 'POST'
136   -}
137   -
138   -export interface MapperConfig {
139   - allowUserCreation: boolean;
140   - activateUser: boolean;
141   - type: MapperConfigType;
142   - basic?: MapperConfigBasic;
143   - custom?: MapperConfigCustom;
144   -}
145   -
146   -export interface MapperConfigBasic {
147   - emailAttributeKey: string;
148   - firstNameAttributeKey?: string;
149   - lastNameAttributeKey?: string;
150   - tenantNameStrategy: TenantNameStrategy;
151   - tenantNamePattern?: string;
152   - customerNamePattern?: string;
153   - defaultDashboardName?: string;
154   - alwaysFullScreen?: boolean;
155   -}
156   -
157   -export interface MapperConfigCustom {
158   - url: string;
159   - username?: string;
160   - password?: string;
161   -}
... ...
... ... @@ -158,7 +158,7 @@
158 158 "last-name-attribute-key": "Last name attribute key",
159 159 "login-button-icon": "Login button icon",
160 160 "login-button-label": "Provider label",
161   - "login-button-label-1": "Login with $(Provider label)",
  161 + "login-button-label-placeholder": "Login with $(Provider label)",
162 162 "login-button-label-required": "Label is required.",
163 163 "login-provider": "Login provider",
164 164 "mapper": "Mapper",
... ...