Commit da7d88470e415c95265205a4ebdcc1297ee5591c
Committed by
GitHub
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 | } | ... | ... |
ui-ngx/src/app/core/http/oauth2.service.ts
0 → 100644
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, | ... | ... |
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", | ... | ... |