Showing
12 changed files
with
845 additions
and
9 deletions
@@ -18,7 +18,13 @@ import { Injectable } from '@angular/core'; | @@ -18,7 +18,13 @@ import { Injectable } from '@angular/core'; | ||
18 | import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; | 18 | import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; |
19 | import { Observable } from 'rxjs'; | 19 | import { Observable } from 'rxjs'; |
20 | import { HttpClient } from '@angular/common/http'; | 20 | import { HttpClient } from '@angular/common/http'; |
21 | -import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models'; | 21 | +import { |
22 | + AdminSettings, | ||
23 | + MailServerSettings, | ||
24 | + OAuth2Settings, | ||
25 | + SecuritySettings, | ||
26 | + UpdateMessage | ||
27 | +} from '@shared/models/settings.models'; | ||
22 | 28 | ||
23 | @Injectable({ | 29 | @Injectable({ |
24 | providedIn: 'root' | 30 | providedIn: 'root' |
@@ -53,6 +59,16 @@ export class AdminService { | @@ -53,6 +59,16 @@ export class AdminService { | ||
53 | defaultHttpOptionsFromConfig(config)); | 59 | defaultHttpOptionsFromConfig(config)); |
54 | } | 60 | } |
55 | 61 | ||
62 | + public getOAuth2Settings(config?: RequestConfig): Observable<OAuth2Settings> { | ||
63 | + return this.http.get<OAuth2Settings>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); | ||
64 | + } | ||
65 | + | ||
66 | + public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, | ||
67 | + config?: RequestConfig): Observable<OAuth2Settings> { | ||
68 | + return this.http.post<OAuth2Settings>('/api/oauth2/config', OAuth2Setting, | ||
69 | + defaultHttpOptionsFromConfig(config)); | ||
70 | + } | ||
71 | + | ||
56 | public checkUpdates(config?: RequestConfig): Observable<UpdateMessage> { | 72 | public checkUpdates(config?: RequestConfig): Observable<UpdateMessage> { |
57 | return this.http.get<UpdateMessage>(`/api/admin/updates`, defaultHttpOptionsFromConfig(config)); | 73 | return this.http.get<UpdateMessage>(`/api/admin/updates`, defaultHttpOptionsFromConfig(config)); |
58 | } | 74 | } |
@@ -95,7 +95,7 @@ export class MenuService { | @@ -95,7 +95,7 @@ export class MenuService { | ||
95 | name: 'admin.system-settings', | 95 | name: 'admin.system-settings', |
96 | type: 'toggle', | 96 | type: 'toggle', |
97 | path: '/settings', | 97 | path: '/settings', |
98 | - height: '120px', | 98 | + height: '160px', |
99 | icon: 'settings', | 99 | icon: 'settings', |
100 | pages: [ | 100 | pages: [ |
101 | { | 101 | { |
@@ -115,6 +115,12 @@ export class MenuService { | @@ -115,6 +115,12 @@ export class MenuService { | ||
115 | type: 'link', | 115 | type: 'link', |
116 | path: '/settings/security-settings', | 116 | path: '/settings/security-settings', |
117 | icon: 'security' | 117 | icon: 'security' |
118 | + }, | ||
119 | + { | ||
120 | + name: 'admin.oauth2.settings', | ||
121 | + type: 'link', | ||
122 | + path: '/settings/oauth2-settings', | ||
123 | + icon: 'security' | ||
118 | } | 124 | } |
119 | ] | 125 | ] |
120 | } | 126 | } |
@@ -22,6 +22,7 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; | @@ -22,6 +22,7 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; | ||
22 | import { Authority } from '@shared/models/authority.enum'; | 22 | import { Authority } from '@shared/models/authority.enum'; |
23 | import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; | 23 | import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; |
24 | import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; | 24 | import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; |
25 | +import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; | ||
25 | 26 | ||
26 | const routes: Routes = [ | 27 | const routes: Routes = [ |
27 | { | 28 | { |
@@ -77,6 +78,19 @@ const routes: Routes = [ | @@ -77,6 +78,19 @@ const routes: Routes = [ | ||
77 | icon: 'security' | 78 | icon: 'security' |
78 | } | 79 | } |
79 | } | 80 | } |
81 | + }, | ||
82 | + { | ||
83 | + path: 'oauth2-settings', | ||
84 | + component: OAuth2SettingsComponent, | ||
85 | + canDeactivate: [ConfirmOnExitGuard], | ||
86 | + data: { | ||
87 | + auth: [Authority.SYS_ADMIN], | ||
88 | + title: 'admin.oauth2.settings', | ||
89 | + breadcrumb: { | ||
90 | + label: 'admin.oauth2.settings', | ||
91 | + icon: 'security' | ||
92 | + } | ||
93 | + } | ||
80 | } | 94 | } |
81 | ] | 95 | ] |
82 | } | 96 | } |
@@ -23,13 +23,15 @@ import { MailServerComponent } from '@modules/home/pages/admin/mail-server.compo | @@ -23,13 +23,15 @@ import { MailServerComponent } from '@modules/home/pages/admin/mail-server.compo | ||
23 | import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; | 23 | import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; |
24 | import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; | 24 | import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; |
25 | import { HomeComponentsModule } from '@modules/home/components/home-components.module'; | 25 | import { HomeComponentsModule } from '@modules/home/components/home-components.module'; |
26 | +import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; | ||
26 | 27 | ||
27 | @NgModule({ | 28 | @NgModule({ |
28 | declarations: | 29 | declarations: |
29 | [ | 30 | [ |
30 | GeneralSettingsComponent, | 31 | GeneralSettingsComponent, |
31 | MailServerComponent, | 32 | MailServerComponent, |
32 | - SecuritySettingsComponent | 33 | + SecuritySettingsComponent, |
34 | + OAuth2SettingsComponent | ||
33 | ], | 35 | ], |
34 | imports: [ | 36 | imports: [ |
35 | CommonModule, | 37 | CommonModule, |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2020 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div> | ||
19 | + <mat-card class="settings-card"> | ||
20 | + <mat-card-title> | ||
21 | + <div fxLayout="row"> | ||
22 | + <span class="mat-headline" translate>admin.oauth2.settings</span> | ||
23 | + <span fxFlex></span> | ||
24 | + <div tb-help="oauth2Settings"></div> | ||
25 | + </div> | ||
26 | + </mat-card-title> | ||
27 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | ||
28 | + </mat-progress-bar> | ||
29 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | ||
30 | + <mat-card-content style="padding-top: 16px;"> | ||
31 | + <form [formGroup]="oauth2SettingsForm" (ngSubmit)="save()"> | ||
32 | + <fieldset [disabled]="isLoading$ | async"> | ||
33 | + <ng-container formArrayName="clientsDomainsParams"> | ||
34 | + <div class="container"> | ||
35 | + <mat-accordion multi> | ||
36 | + <ng-container *ngFor="let domain of clientsDomainsParams.controls; let i = index; let last = last;"> | ||
37 | + <mat-expansion-panel [formGroupName]="i" [expanded]="last"> | ||
38 | + <mat-expansion-panel-header> | ||
39 | + <mat-panel-title fxLayoutAlign="start center"> | ||
40 | + {{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.new-domain" | translate) }} | ||
41 | + </mat-panel-title> | ||
42 | + <mat-panel-description fxLayoutAlign="end center"> | ||
43 | + <button mat-icon-button (click)="deleteDomain($event, i)"> | ||
44 | + <mat-icon>delete</mat-icon> | ||
45 | + </button> | ||
46 | + </mat-panel-description> | ||
47 | + </mat-expansion-panel-header> | ||
48 | + | ||
49 | + <mat-form-field class="mat-block"> | ||
50 | + <mat-label translate>admin.domain-name</mat-label> | ||
51 | + <input matInput formControlName="domainName" required> | ||
52 | + <mat-error *ngIf="domain.get('domainName').hasError('pattern')"> | ||
53 | + {{ 'admin.error-verification-url' | translate }} | ||
54 | + </mat-error> | ||
55 | + </mat-form-field> | ||
56 | + | ||
57 | + <mat-form-field fxFlex class="mat-block"> | ||
58 | + <mat-label translate>admin.oauth2.redirect-uri-template</mat-label> | ||
59 | + <input matInput formControlName="redirectUriTemplate" required> | ||
60 | + <mat-error | ||
61 | + *ngIf="domain.get('redirectUriTemplate').hasError('required')"> | ||
62 | + {{ 'admin.oauth2.redirect-uri-template-required' | translate }} | ||
63 | + </mat-error> | ||
64 | + <mat-error | ||
65 | + *ngIf="domain.get('redirectUriTemplate').hasError('pattern')"> | ||
66 | + {{ 'admin.oauth2.uri-pattern-error' | translate }} | ||
67 | + </mat-error> | ||
68 | + </mat-form-field> | ||
69 | + | ||
70 | + <ng-container formArrayName="clientRegistrations"> | ||
71 | + <div class="container"> | ||
72 | + <mat-card *ngFor="let registration of clientDomainRegistrations(domain).controls; let j = index;" | ||
73 | + class="registration-card"> | ||
74 | + <section [formGroupName]="j"> | ||
75 | + <div fxLayoutAlign="end center"> | ||
76 | + <button mat-icon-button (click)="deleteRegistration($event, domain, j)"> | ||
77 | + <mat-icon>delete</mat-icon> | ||
78 | + </button> | ||
79 | + </div> | ||
80 | + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> | ||
81 | + <mat-form-field fxFlex class="mat-block"> | ||
82 | + <mat-label translate>admin.oauth2.registration-id</mat-label> | ||
83 | + <input matInput formControlName="registrationId" required> | ||
84 | + <mat-error *ngIf="registration.get('registrationId').hasError('required')"> | ||
85 | + {{ 'admin.oauth2.registration-id-required' | translate }} | ||
86 | + </mat-error> | ||
87 | + </mat-form-field> | ||
88 | + | ||
89 | + <mat-form-field fxFlex class="mat-block"> | ||
90 | + <mat-label translate>admin.oauth2.client-name</mat-label> | ||
91 | + <input matInput formControlName="clientName" required> | ||
92 | + <mat-error *ngIf="registration.get('clientName').hasError('required')"> | ||
93 | + {{ 'admin.oauth2.client-name-required' | translate }} | ||
94 | + </mat-error> | ||
95 | + </mat-form-field> | ||
96 | + </div> | ||
97 | + | ||
98 | + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> | ||
99 | + <mat-form-field fxFlex class="mat-block"> | ||
100 | + <mat-label translate>admin.oauth2.client-id</mat-label> | ||
101 | + <input matInput formControlName="clientId" required> | ||
102 | + <mat-error *ngIf="registration.get('clientId').hasError('required')"> | ||
103 | + {{ 'admin.oauth2.client-id-required' | translate }} | ||
104 | + </mat-error> | ||
105 | + </mat-form-field> | ||
106 | + | ||
107 | + <mat-form-field fxFlex class="mat-block"> | ||
108 | + <mat-label translate>admin.oauth2.client-secret</mat-label> | ||
109 | + <input matInput formControlName="clientSecret" required> | ||
110 | + <mat-error *ngIf="registration.get('clientSecret').hasError('required')"> | ||
111 | + {{ 'admin.oauth2.client-secret-required' | translate }} | ||
112 | + </mat-error> | ||
113 | + </mat-form-field> | ||
114 | + </div> | ||
115 | + | ||
116 | + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> | ||
117 | + <mat-form-field fxFlex class="mat-block"> | ||
118 | + <mat-label translate>admin.oauth2.access-token-uri</mat-label> | ||
119 | + <input matInput formControlName="accessTokenUri" required> | ||
120 | + <mat-error *ngIf="registration.get('accessTokenUri').hasError('required')"> | ||
121 | + {{ 'admin.oauth2.access-token-uri-required' | translate }} | ||
122 | + </mat-error> | ||
123 | + <mat-error *ngIf="registration.get('accessTokenUri').hasError('pattern')"> | ||
124 | + {{ 'admin.oauth2.uri-pattern-error' | translate }} | ||
125 | + </mat-error> | ||
126 | + </mat-form-field> | ||
127 | + | ||
128 | + <mat-form-field fxFlex class="mat-block"> | ||
129 | + <mat-label translate>admin.oauth2.authorization-uri</mat-label> | ||
130 | + <input matInput formControlName="authorizationUri" required> | ||
131 | + <mat-error *ngIf="registration.get('authorizationUri').hasError('required')"> | ||
132 | + {{ 'admin.oauth2.authorization-uri-required' | translate }} | ||
133 | + </mat-error> | ||
134 | + <mat-error *ngIf="registration.get('authorizationUri').hasError('pattern')"> | ||
135 | + {{ 'admin.oauth2.uri-pattern-error' | translate }} | ||
136 | + </mat-error> | ||
137 | + </mat-form-field> | ||
138 | + </div> | ||
139 | + | ||
140 | + <mat-form-field fxFlex class="mat-block"> | ||
141 | + <mat-label translate>admin.oauth2.scope</mat-label> | ||
142 | + <mat-chip-list #scopeList> | ||
143 | + <mat-chip | ||
144 | + *ngFor="let scope of registration.get('scope').value; let k = index;" | ||
145 | + removable | ||
146 | + (removed)="removeScope(k, registration)"> | ||
147 | + {{scope}} | ||
148 | + <mat-icon matChipRemove>cancel</mat-icon> | ||
149 | + </mat-chip> | ||
150 | + <input [matChipInputFor]="scopeList" | ||
151 | + [matChipInputSeparatorKeyCodes]="separatorKeysCodes" | ||
152 | + matChipInputAddOnBlur | ||
153 | + (matChipInputTokenEnd)="addScope($event, registration)"> | ||
154 | + </mat-chip-list> | ||
155 | + </mat-form-field> | ||
156 | + | ||
157 | + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> | ||
158 | + <mat-form-field fxFlex class="mat-block"> | ||
159 | + <mat-label translate>admin.oauth2.jwk-set-uri</mat-label> | ||
160 | + <input matInput formControlName="jwkSetUri" required> | ||
161 | + <mat-error *ngIf="registration.get('jwkSetUri').hasError('required')"> | ||
162 | + {{ 'admin.oauth2.jwk-set-uri-required' | translate }} | ||
163 | + </mat-error> | ||
164 | + <mat-error *ngIf="registration.get('jwkSetUri').hasError('pattern')"> | ||
165 | + {{ 'admin.oauth2.uri-pattern-error' | translate }} | ||
166 | + </mat-error> | ||
167 | + </mat-form-field> | ||
168 | + | ||
169 | + <mat-form-field fxFlex class="mat-block"> | ||
170 | + <mat-label translate>admin.oauth2.user-info-uri</mat-label> | ||
171 | + <input matInput formControlName="userInfoUri" required> | ||
172 | + <mat-error *ngIf="registration.get('userInfoUri').hasError('required')"> | ||
173 | + {{ 'admin.oauth2.user-info-uri-required' | translate }} | ||
174 | + </mat-error> | ||
175 | + <mat-error *ngIf="registration.get('userInfoUri').hasError('pattern')"> | ||
176 | + {{ 'admin.oauth2.uri-pattern-error' | translate }} | ||
177 | + </mat-error> | ||
178 | + </mat-form-field> | ||
179 | + </div> | ||
180 | + | ||
181 | + <mat-form-field fxFlex class="mat-block"> | ||
182 | + <mat-label translate>admin.oauth2.client-authentication-method</mat-label> | ||
183 | + <mat-select formControlName="clientAuthenticationMethod"> | ||
184 | + <mat-option *ngFor="let clientAuthenticationMethod of clientAuthenticationMethods" | ||
185 | + [value]="clientAuthenticationMethod"> | ||
186 | + {{ clientAuthenticationMethod | uppercase }} | ||
187 | + </mat-option> | ||
188 | + </mat-select> | ||
189 | + </mat-form-field> | ||
190 | + | ||
191 | + <mat-form-field class="mat-block"> | ||
192 | + <mat-label translate>admin.oauth2.user-name-attribute-name</mat-label> | ||
193 | + <input matInput formControlName="userNameAttributeName" required> | ||
194 | + <mat-error *ngIf="registration.get('userNameAttributeName').hasError('required')"> | ||
195 | + {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} | ||
196 | + </mat-error> | ||
197 | + </mat-form-field> | ||
198 | + | ||
199 | + <section formGroupName="mapperConfig"> | ||
200 | + <div fxLayout="column" fxLayoutGap="8px"> | ||
201 | + <mat-checkbox formControlName="allowUserCreation"> | ||
202 | + {{ 'admin.oauth2.allow-user-creation' | translate }} | ||
203 | + </mat-checkbox> | ||
204 | + <mat-checkbox formControlName="activateUser"> | ||
205 | + {{ 'admin.oauth2.activate-user' | translate }} | ||
206 | + </mat-checkbox> | ||
207 | + </div> | ||
208 | + | ||
209 | + <mat-form-field fxFlex class="mat-block"> | ||
210 | + <mat-label translate>admin.oauth2.type</mat-label> | ||
211 | + <mat-select formControlName="type"> | ||
212 | + <mat-option *ngFor="let converterTypeExternalUser of converterTypesExternalUser" | ||
213 | + [value]="converterTypeExternalUser"> | ||
214 | + {{ converterTypeExternalUser }} | ||
215 | + </mat-option> | ||
216 | + </mat-select> | ||
217 | + </mat-form-field> | ||
218 | + | ||
219 | + <section formGroupName="basic" | ||
220 | + *ngIf="registration.get('mapperConfig.type').value === 'BASIC'"> | ||
221 | + <mat-form-field class="mat-block"> | ||
222 | + <mat-label translate>admin.oauth2.email-attribute-key</mat-label> | ||
223 | + <input matInput formControlName="emailAttributeKey" required> | ||
224 | + <mat-error | ||
225 | + *ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('required')"> | ||
226 | + {{ 'admin.oauth2.email-attribute-key-required' | translate }} | ||
227 | + </mat-error> | ||
228 | + </mat-form-field> | ||
229 | + | ||
230 | + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> | ||
231 | + <mat-form-field fxFlex class="mat-block"> | ||
232 | + <mat-label translate>admin.oauth2.first-name-attribute-key</mat-label> | ||
233 | + <input matInput formControlName="firstNameAttributeKey"> | ||
234 | + </mat-form-field> | ||
235 | + | ||
236 | + <mat-form-field fxFlex class="mat-block"> | ||
237 | + <mat-label translate>admin.oauth2.last-name-attribute-key</mat-label> | ||
238 | + <input matInput formControlName="lastNameAttributeKey"> | ||
239 | + </mat-form-field> | ||
240 | + </div> | ||
241 | + | ||
242 | + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> | ||
243 | + <mat-form-field fxFlex class="mat-block"> | ||
244 | + <mat-label translate>admin.oauth2.tenant-name-strategy</mat-label> | ||
245 | + <mat-select formControlName="tenantNameStrategy"> | ||
246 | + <mat-option *ngFor="let tenantNameStrategy of tenantNameStrategies" | ||
247 | + [value]="tenantNameStrategy"> | ||
248 | + {{ tenantNameStrategy }} | ||
249 | + </mat-option> | ||
250 | + </mat-select> | ||
251 | + </mat-form-field> | ||
252 | + | ||
253 | + <mat-form-field fxFlex class="mat-block"> | ||
254 | + <mat-label translate>admin.oauth2.tenant-name-pattern</mat-label> | ||
255 | + <input matInput | ||
256 | + formControlName="tenantNamePattern" | ||
257 | + [required]="registration.get('mapperConfig.basic.tenantNameStrategy').value === 'CUSTOM'"> | ||
258 | + <mat-error | ||
259 | + *ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('required')"> | ||
260 | + {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} | ||
261 | + </mat-error> | ||
262 | + </mat-form-field> | ||
263 | + </div> | ||
264 | + | ||
265 | + <mat-form-field fxFlex class="mat-block"> | ||
266 | + <mat-label translate>admin.oauth2.customer-name-pattern</mat-label> | ||
267 | + <input matInput formControlName="customerNamePattern"> | ||
268 | + </mat-form-field> | ||
269 | + | ||
270 | + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> | ||
271 | + <mat-form-field fxFlex class="mat-block"> | ||
272 | + <mat-label translate>admin.oauth2.default-dashboard-name</mat-label> | ||
273 | + <input matInput formControlName="defaultDashboardName"> | ||
274 | + </mat-form-field> | ||
275 | + | ||
276 | + <mat-checkbox fxFlex formControlName="alwaysFullScreen" class="checkbox-row"> | ||
277 | + {{ 'admin.oauth2.always-fullscreen' | translate}} | ||
278 | + </mat-checkbox> | ||
279 | + </div> | ||
280 | + | ||
281 | + </section> | ||
282 | + | ||
283 | + <section formGroupName="custom" | ||
284 | + *ngIf="registration.get('mapperConfig.type').value === 'CUSTOM'"> | ||
285 | + <mat-form-field class="mat-block"> | ||
286 | + <mat-label translate>admin.oauth2.url</mat-label> | ||
287 | + <input matInput formControlName="url" required> | ||
288 | + <mat-error | ||
289 | + *ngIf="registration.get('mapperConfig.custom.url').hasError('required')"> | ||
290 | + {{ 'admin.oauth2.url-required' | translate }} | ||
291 | + </mat-error> | ||
292 | + <mat-error | ||
293 | + *ngIf="registration.get('mapperConfig.custom.url').hasError('pattern')"> | ||
294 | + {{ 'admin.oauth2.url-pattern' | translate }} | ||
295 | + </mat-error> | ||
296 | + </mat-form-field> | ||
297 | + | ||
298 | + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> | ||
299 | + <mat-form-field fxFlex class="mat-block"> | ||
300 | + <mat-label translate>common.username</mat-label> | ||
301 | + <input matInput formControlName="username"> | ||
302 | + </mat-form-field> | ||
303 | + | ||
304 | + <mat-form-field fxFlex class="mat-block"> | ||
305 | + <mat-label translate>common.password</mat-label> | ||
306 | + <input matInput formControlName="password"> | ||
307 | + </mat-form-field> | ||
308 | + </div> | ||
309 | + </section> | ||
310 | + </section> | ||
311 | + | ||
312 | + <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> | ||
313 | + <mat-form-field fxFlex class="mat-block"> | ||
314 | + <mat-label translate>admin.oauth2.login-button-label</mat-label> | ||
315 | + <input matInput formControlName="loginButtonLabel" required> | ||
316 | + <mat-error | ||
317 | + *ngIf="registration.get('loginButtonLabel').hasError('required')"> | ||
318 | + {{ 'admin.oauth2.login-button-label-required' | translate }} | ||
319 | + </mat-error> | ||
320 | + </mat-form-field> | ||
321 | + | ||
322 | + <mat-form-field fxFlex class="mat-block"> | ||
323 | + <mat-label translate>admin.oauth2.login-button-icon</mat-label> | ||
324 | + <input matInput formControlName="loginButtonIcon"> | ||
325 | + </mat-form-field> | ||
326 | + </div> | ||
327 | + </section> | ||
328 | + </mat-card> | ||
329 | + </div> | ||
330 | + </ng-container> | ||
331 | + | ||
332 | + <div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px"> | ||
333 | + <button mat-button mat-raised-button color="primary" | ||
334 | + [disabled]="(isLoading$ | async)" | ||
335 | + (click)="addRegistration(domain)" | ||
336 | + type="button"> | ||
337 | + {{'admin.add-registration' | translate}} | ||
338 | + </button> | ||
339 | + </div> | ||
340 | + | ||
341 | + </mat-expansion-panel> | ||
342 | + </ng-container> | ||
343 | + </mat-accordion> | ||
344 | + </div> | ||
345 | + </ng-container> | ||
346 | + | ||
347 | + <div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px"> | ||
348 | + <button mat-button mat-raised-button color="primary" | ||
349 | + [disabled]="(isLoading$ | async)" | ||
350 | + (click)="addDomain()" | ||
351 | + type="button"> | ||
352 | + {{'admin.add-domain' | translate}} | ||
353 | + </button> | ||
354 | + <button mat-button mat-raised-button color="primary" | ||
355 | + [disabled]="(isLoading$ | async) || oauth2SettingsForm.invalid || !oauth2SettingsForm.dirty" | ||
356 | + type="submit"> | ||
357 | + {{'action.save' | translate}} | ||
358 | + </button> | ||
359 | + </div> | ||
360 | + </fieldset> | ||
361 | + </form> | ||
362 | + </mat-card-content> | ||
363 | + </mat-card> | ||
364 | +</div> |
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 | +:host{ | ||
17 | + .checkbox-row { | ||
18 | + margin-top: 16px; | ||
19 | + } | ||
20 | + | ||
21 | + .registration-card{ | ||
22 | + margin-bottom: 8px; | ||
23 | + } | ||
24 | + | ||
25 | + .container{ | ||
26 | + margin-bottom: 16px; | ||
27 | + } | ||
28 | +} |
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 { Component, Inject, OnDestroy, OnInit } from '@angular/core'; | ||
18 | +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; | ||
19 | +import { ClientRegistration, DomainParams, OAuth2Settings } from '@shared/models/settings.models'; | ||
20 | +import { Store } from '@ngrx/store'; | ||
21 | +import { AppState } from '@core/core.state'; | ||
22 | +import { AdminService } from '@core/http/admin.service'; | ||
23 | +import { PageComponent } from '@shared/components/page.component'; | ||
24 | +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; | ||
25 | +import { COMMA, ENTER } from '@angular/cdk/keycodes'; | ||
26 | +import { MatChipInputEvent } from '@angular/material/chips'; | ||
27 | +import { WINDOW } from '@core/services/window.service'; | ||
28 | +import { Subscription } from 'rxjs'; | ||
29 | +import { DialogService } from '@core/services/dialog.service'; | ||
30 | +import { TranslateService } from '@ngx-translate/core'; | ||
31 | + | ||
32 | +@Component({ | ||
33 | + selector: 'tb-oauth2-settings', | ||
34 | + templateUrl: './oauth2-settings.component.html', | ||
35 | + styleUrls: ['./oauth2-settings.component.scss', './settings-card.scss'] | ||
36 | +}) | ||
37 | +export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { | ||
38 | + | ||
39 | + private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/; | ||
40 | + private subscriptions: Subscription[] = []; | ||
41 | + | ||
42 | + readonly separatorKeysCodes: number[] = [ENTER, COMMA]; | ||
43 | + | ||
44 | + oauth2SettingsForm: FormGroup; | ||
45 | + oauth2Settings: OAuth2Settings; | ||
46 | + | ||
47 | + clientAuthenticationMethods = ['basic', 'post']; | ||
48 | + converterTypesExternalUser = ['BASIC', 'CUSTOM']; | ||
49 | + tenantNameStrategies = ['DOMAIN', 'EMAIL', 'CUSTOM']; | ||
50 | + | ||
51 | + constructor(protected store: Store<AppState>, | ||
52 | + private adminService: AdminService, | ||
53 | + private fb: FormBuilder, | ||
54 | + private dialogService: DialogService, | ||
55 | + private translate: TranslateService, | ||
56 | + @Inject(WINDOW) private window: Window) { | ||
57 | + super(store); | ||
58 | + } | ||
59 | + | ||
60 | + ngOnInit(): void { | ||
61 | + this.buildOAuth2SettingsForm(); | ||
62 | + this.adminService.getOAuth2Settings().subscribe( | ||
63 | + (oauth2Settings) => { | ||
64 | + this.oauth2Settings = oauth2Settings; | ||
65 | + this.initOAuth2Settings(this.oauth2Settings); | ||
66 | + this.oauth2SettingsForm.reset(this.oauth2Settings); | ||
67 | + } | ||
68 | + ); | ||
69 | + } | ||
70 | + | ||
71 | + ngOnDestroy() { | ||
72 | + super.ngOnDestroy(); | ||
73 | + this.subscriptions.forEach((subscription) => { | ||
74 | + subscription.unsubscribe(); | ||
75 | + }) | ||
76 | + } | ||
77 | + | ||
78 | + get clientsDomainsParams(): FormArray { | ||
79 | + return this.oauth2SettingsForm.get('clientsDomainsParams') as FormArray; | ||
80 | + } | ||
81 | + | ||
82 | + private get formBasicGroup(): FormGroup { | ||
83 | + const basicGroup = this.fb.group({ | ||
84 | + emailAttributeKey: ['email', [Validators.required]], | ||
85 | + firstNameAttributeKey: [''], | ||
86 | + lastNameAttributeKey: [''], | ||
87 | + tenantNameStrategy: ['DOMAIN'], | ||
88 | + tenantNamePattern: [null], | ||
89 | + customerNamePattern: [null], | ||
90 | + defaultDashboardName: [null], | ||
91 | + alwaysFullScreen: [false], | ||
92 | + }); | ||
93 | + | ||
94 | + this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { | ||
95 | + if (domain === 'CUSTOM') { | ||
96 | + basicGroup.get('tenantNamePattern').setValidators(Validators.required); | ||
97 | + } else { | ||
98 | + basicGroup.get('tenantNamePattern').clearValidators(); | ||
99 | + } | ||
100 | + })); | ||
101 | + | ||
102 | + return basicGroup; | ||
103 | + } | ||
104 | + | ||
105 | + get formCustomGroup(): FormGroup { | ||
106 | + return this.fb.group({ | ||
107 | + url: [null, [Validators.required, Validators.pattern(this.URL_REGEXP)]], | ||
108 | + username: [null], | ||
109 | + password: [null] | ||
110 | + }) | ||
111 | + } | ||
112 | + | ||
113 | + private buildOAuth2SettingsForm(): void { | ||
114 | + this.oauth2SettingsForm = this.fb.group({ | ||
115 | + clientsDomainsParams: this.fb.array([]) | ||
116 | + }); | ||
117 | + } | ||
118 | + | ||
119 | + private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { | ||
120 | + oauth2Settings.clientsDomainsParams.forEach((domaindomain) => { | ||
121 | + this.clientsDomainsParams.push(this.buildSettingsDomain(domaindomain)); | ||
122 | + }); | ||
123 | + } | ||
124 | + | ||
125 | + private buildSettingsDomain(domainParams?: DomainParams): FormGroup { | ||
126 | + let url = this.window.location.protocol + '//' + this.window.location.hostname; | ||
127 | + const port = this.window.location.port; | ||
128 | + if (port !== '80' && port !== '443') { | ||
129 | + url += ':' + port; | ||
130 | + } | ||
131 | + url += '/login/oauth2/code/'; | ||
132 | + const formDomain = this.fb.group({ | ||
133 | + domainName: ['', [Validators.required, Validators.pattern('((?![:/]).)*$')]], | ||
134 | + redirectUriTemplate: [url, [Validators.required, Validators.pattern(this.URL_REGEXP)]], | ||
135 | + clientRegistrations: this.fb.array([]) | ||
136 | + }); | ||
137 | + | ||
138 | + this.subscriptions.push(formDomain.get('domainName').valueChanges.subscribe((domain) => { | ||
139 | + if (!domain) { | ||
140 | + domain = this.window.location.hostname | ||
141 | + } | ||
142 | + const uri = this.window.location.protocol + `//${domain}/login/oauth2/code/`; | ||
143 | + formDomain.get('redirectUriTemplate').patchValue(uri); | ||
144 | + })); | ||
145 | + | ||
146 | + if(domainParams){ | ||
147 | + domainParams.clientRegistrations.forEach((registration) => { | ||
148 | + this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration(registration)); | ||
149 | + }) | ||
150 | + } else { | ||
151 | + this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration()); | ||
152 | + } | ||
153 | + | ||
154 | + return formDomain; | ||
155 | + } | ||
156 | + | ||
157 | + private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup { | ||
158 | + const clientRegistration = this.fb.group({ | ||
159 | + registrationId: [null, [Validators.required]], | ||
160 | + clientName: [null, [Validators.required]], | ||
161 | + loginButtonLabel: [null, [Validators.required]], | ||
162 | + loginButtonIcon: [null], | ||
163 | + clientId: ['', [Validators.required]], | ||
164 | + clientSecret: ['', [Validators.required]], | ||
165 | + accessTokenUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], | ||
166 | + authorizationUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], | ||
167 | + scope: this.fb.array([], [Validators.required]), | ||
168 | + jwkSetUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], | ||
169 | + userInfoUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], | ||
170 | + clientAuthenticationMethod: ['post', [Validators.required]], | ||
171 | + userNameAttributeName: ['email', [Validators.required]], | ||
172 | + mapperConfig: this.fb.group({ | ||
173 | + allowUserCreation: [true], | ||
174 | + activateUser: [false], | ||
175 | + type: ['BASIC', [Validators.required]], | ||
176 | + basic: this.formBasicGroup | ||
177 | + } | ||
178 | + ) | ||
179 | + }); | ||
180 | + | ||
181 | + this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => { | ||
182 | + const mapperConfig = clientRegistration.get('mapperConfig') as FormGroup; | ||
183 | + if (value === 'BASIC') { | ||
184 | + mapperConfig.removeControl('custom'); | ||
185 | + mapperConfig.addControl('basic', this.formBasicGroup); | ||
186 | + } else { | ||
187 | + mapperConfig.removeControl('basic'); | ||
188 | + mapperConfig.addControl('custom', this.formCustomGroup); | ||
189 | + } | ||
190 | + })); | ||
191 | + | ||
192 | + if(registrationData){ | ||
193 | + registrationData.scope.forEach(() => { | ||
194 | + (clientRegistration.get('scope') as FormArray).push(this.fb.control('')) | ||
195 | + }) | ||
196 | + if(registrationData.mapperConfig.type !== 'BASIC'){ | ||
197 | + clientRegistration.get('mapperConfig.type').patchValue('CUSTOM'); | ||
198 | + } | ||
199 | + } | ||
200 | + | ||
201 | + return clientRegistration; | ||
202 | + } | ||
203 | + | ||
204 | + save(): void { | ||
205 | + console.log(this.oauth2SettingsForm.value); | ||
206 | + this.adminService.saveOAuth2Settings(this.oauth2SettingsForm.value).subscribe( | ||
207 | + (oauth2Settings) => { | ||
208 | + this.oauth2Settings = oauth2Settings; | ||
209 | + this.oauth2SettingsForm.markAsPristine(); | ||
210 | + this.oauth2SettingsForm.markAsUntouched(); | ||
211 | + } | ||
212 | + ); | ||
213 | + } | ||
214 | + | ||
215 | + confirmForm(): FormGroup { | ||
216 | + return this.oauth2SettingsForm; | ||
217 | + } | ||
218 | + | ||
219 | + addScope(event: MatChipInputEvent, control: AbstractControl): void { | ||
220 | + const input = event.input; | ||
221 | + const value = event.value; | ||
222 | + const controller = control.get('scope') as FormArray; | ||
223 | + if ((value.trim() !== '')) { | ||
224 | + controller.push(this.fb.control(value.trim())); | ||
225 | + } | ||
226 | + | ||
227 | + if (input) { | ||
228 | + input.value = ''; | ||
229 | + } | ||
230 | + } | ||
231 | + | ||
232 | + removeScope(i: number, control: AbstractControl): void { | ||
233 | + const controller = control.get('scope') as FormArray; | ||
234 | + controller.removeAt(i); | ||
235 | + } | ||
236 | + | ||
237 | + addDomain(): void { | ||
238 | + this.clientsDomainsParams.push(this.buildSettingsDomain()); | ||
239 | + } | ||
240 | + | ||
241 | + deleteDomain($event: Event, index: number): void { | ||
242 | + if ($event) { | ||
243 | + $event.stopPropagation(); | ||
244 | + $event.preventDefault(); | ||
245 | + } | ||
246 | + | ||
247 | + const domainName = this.clientsDomainsParams.at(index).get('domainName').value; | ||
248 | + this.dialogService.confirm( | ||
249 | + this.translate.instant('admin.oauth2.delete-domain-title', {domainName}), | ||
250 | + this.translate.instant('admin.oauth2.delete-domain-text'), null, | ||
251 | + this.translate.instant('action.delete') | ||
252 | + ).subscribe((data) => { | ||
253 | + if (data) { | ||
254 | + this.clientsDomainsParams.removeAt(index); | ||
255 | + } | ||
256 | + }) | ||
257 | + } | ||
258 | + | ||
259 | + clientDomainRegistrations(control: AbstractControl): FormArray { | ||
260 | + return control.get('clientRegistrations') as FormArray; | ||
261 | + } | ||
262 | + | ||
263 | + addRegistration(control: AbstractControl): void { | ||
264 | + this.clientDomainRegistrations(control).push(this.buildSettingsRegistration()); | ||
265 | + } | ||
266 | + | ||
267 | + deleteRegistration($event: Event, controler: AbstractControl, index: number): void { | ||
268 | + if ($event) { | ||
269 | + $event.stopPropagation(); | ||
270 | + $event.preventDefault(); | ||
271 | + } | ||
272 | + | ||
273 | + const registrationId = this.clientDomainRegistrations(controler).at(index).get('registrationId').value; | ||
274 | + this.dialogService.confirm( | ||
275 | + this.translate.instant('admin.oauth2.delete-registration-title', {name: registrationId}), | ||
276 | + this.translate.instant('admin.oauth2.delete-registration-text'), null, | ||
277 | + this.translate.instant('action.delete') | ||
278 | + ).subscribe((data) => { | ||
279 | + if (data) { | ||
280 | + this.clientDomainRegistrations(controler).removeAt(index); | ||
281 | + } | ||
282 | + }) | ||
283 | + } | ||
284 | +} |
@@ -54,6 +54,9 @@ | @@ -54,6 +54,9 @@ | ||
54 | <mat-label translate>tenant.description</mat-label> | 54 | <mat-label translate>tenant.description</mat-label> |
55 | <textarea matInput formControlName="description" rows="2"></textarea> | 55 | <textarea matInput formControlName="description" rows="2"></textarea> |
56 | </mat-form-field> | 56 | </mat-form-field> |
57 | + <mat-checkbox fxFlex formControlName="allowOAuth2Configuration" style="padding-bottom: 16px;"> | ||
58 | + {{ 'tenant.allow-oauth2-configuration' | translate }} | ||
59 | + </mat-checkbox> | ||
57 | </div> | 60 | </div> |
58 | <tb-contact [parentForm]="entityForm" [isEdit]="isEdit"></tb-contact> | 61 | <tb-contact [parentForm]="entityForm" [isEdit]="isEdit"></tb-contact> |
59 | <div fxLayout="column"> | 62 | <div fxLayout="column"> |
@@ -23,6 +23,7 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti | @@ -23,6 +23,7 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti | ||
23 | import { TranslateService } from '@ngx-translate/core'; | 23 | import { TranslateService } from '@ngx-translate/core'; |
24 | import { ContactBasedComponent } from '../../components/entity/contact-based.component'; | 24 | import { ContactBasedComponent } from '../../components/entity/contact-based.component'; |
25 | import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | 25 | import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; |
26 | +import { isDefined } from '@core/utils'; | ||
26 | 27 | ||
27 | @Component({ | 28 | @Component({ |
28 | selector: 'tb-tenant', | 29 | selector: 'tb-tenant', |
@@ -55,7 +56,9 @@ export class TenantComponent extends ContactBasedComponent<Tenant> { | @@ -55,7 +56,9 @@ export class TenantComponent extends ContactBasedComponent<Tenant> { | ||
55 | isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], | 56 | isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], |
56 | additionalInfo: this.fb.group( | 57 | additionalInfo: this.fb.group( |
57 | { | 58 | { |
58 | - description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] | 59 | + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], |
60 | + allowOAuth2Configuration: [isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ? | ||
61 | + entity.additionalInfo.allowOAuth2Configuration : true] | ||
59 | } | 62 | } |
60 | ) | 63 | ) |
61 | } | 64 | } |
@@ -66,7 +69,11 @@ export class TenantComponent extends ContactBasedComponent<Tenant> { | @@ -66,7 +69,11 @@ export class TenantComponent extends ContactBasedComponent<Tenant> { | ||
66 | this.entityForm.patchValue({title: entity.title}); | 69 | this.entityForm.patchValue({title: entity.title}); |
67 | this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); | 70 | this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); |
68 | this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); | 71 | this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); |
69 | - this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); | 72 | + this.entityForm.patchValue({additionalInfo: { |
73 | + description: entity.additionalInfo ? entity.additionalInfo.description : '', | ||
74 | + allowOAuth2Configuration: isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ? | ||
75 | + entity.additionalInfo.allowOAuth2Configuration : true | ||
76 | + }}); | ||
70 | } | 77 | } |
71 | 78 | ||
72 | updateFormState() { | 79 | updateFormState() { |
@@ -58,6 +58,7 @@ export const HelpLinks = { | @@ -58,6 +58,7 @@ export const HelpLinks = { | ||
58 | linksMap: { | 58 | linksMap: { |
59 | outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', | 59 | outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', |
60 | securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', | 60 | securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', |
61 | + oauth2Settings: helpBaseUrl + '/docs/user-guide/oauth-2-support/', | ||
61 | ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/', | 62 | ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/', |
62 | ruleNodeCheckRelation: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-relation-filter-node', | 63 | ruleNodeCheckRelation: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-relation-filter-node', |
63 | ruleNodeCheckExistenceFields: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-existence-fields-node', | 64 | ruleNodeCheckExistenceFields: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-existence-fields-node', |
@@ -108,7 +109,7 @@ export const HelpLinks = { | @@ -108,7 +109,7 @@ export const HelpLinks = { | ||
108 | widgetsConfigLatest: helpBaseUrl + '/docs/user-guide/ui/dashboards#latest', | 109 | widgetsConfigLatest: helpBaseUrl + '/docs/user-guide/ui/dashboards#latest', |
109 | widgetsConfigRpc: helpBaseUrl + '/docs/user-guide/ui/dashboards#rpc', | 110 | widgetsConfigRpc: helpBaseUrl + '/docs/user-guide/ui/dashboards#rpc', |
110 | widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/ui/dashboards#alarm', | 111 | widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/ui/dashboards#alarm', |
111 | - widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static' | 112 | + widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static', |
112 | } | 113 | } |
113 | }; | 114 | }; |
114 | 115 |
@@ -23,6 +23,10 @@ export interface AdminSettings<T> { | @@ -23,6 +23,10 @@ export interface AdminSettings<T> { | ||
23 | 23 | ||
24 | export declare type SmtpProtocol = 'smtp' | 'smtps'; | 24 | export declare type SmtpProtocol = 'smtp' | 'smtps'; |
25 | 25 | ||
26 | +export declare type ClientAuthenticationMethod = 'basic' | 'post'; | ||
27 | +export declare type MapperConfigType = 'BASIC' | 'CUSTOM'; | ||
28 | +export declare type TenantNameStrategy = 'DOMAIN' | 'EMAIL' | 'CUSTOM'; | ||
29 | + | ||
26 | export interface MailServerSettings { | 30 | export interface MailServerSettings { |
27 | mailFrom: string; | 31 | mailFrom: string; |
28 | smtpProtocol: SmtpProtocol; | 32 | smtpProtocol: SmtpProtocol; |
@@ -60,3 +64,55 @@ export interface UpdateMessage { | @@ -60,3 +64,55 @@ export interface UpdateMessage { | ||
60 | message: string; | 64 | message: string; |
61 | updateAvailable: boolean; | 65 | updateAvailable: boolean; |
62 | } | 66 | } |
67 | + | ||
68 | +export interface OAuth2Settings { | ||
69 | + clientsDomainsParams: DomainParams[]; | ||
70 | +} | ||
71 | + | ||
72 | +export interface DomainParams { | ||
73 | + domainName: string; | ||
74 | + redirectUriTemplate: string; | ||
75 | + clientRegistrations: ClientRegistration[]; | ||
76 | +} | ||
77 | + | ||
78 | +export interface ClientRegistration { | ||
79 | + registrationId: string; | ||
80 | + clientName: string; | ||
81 | + loginButtonLabel: string; | ||
82 | + loginButtonIcon: string; | ||
83 | + clientId: string; | ||
84 | + clientSecret: string; | ||
85 | + accessTokenUri: string; | ||
86 | + authorizationUri: string; | ||
87 | + scope: string[]; | ||
88 | + jwkSetUri: string; | ||
89 | + userInfoUri: string; | ||
90 | + clientAuthenticationMethod: ClientAuthenticationMethod | ||
91 | + userNameAttributeName: string; | ||
92 | + mapperConfig: MapperConfig | ||
93 | +} | ||
94 | + | ||
95 | +export interface MapperConfig { | ||
96 | + allowUserCreation: boolean; | ||
97 | + activateUser: boolean; | ||
98 | + type: MapperConfigType; | ||
99 | + basic?: MapperConfigBasic; | ||
100 | + custom?: MapperConfigCustom; | ||
101 | +} | ||
102 | + | ||
103 | +export interface MapperConfigBasic { | ||
104 | + emailAttributeKey: string; | ||
105 | + firstNameAttributeKey?: string; | ||
106 | + lastNameAttributeKey?: string; | ||
107 | + tenantNameStrategy: TenantNameStrategy; | ||
108 | + tenantNamePattern?: string; | ||
109 | + customerNamePattern?: string; | ||
110 | + defaultDashboardName?: string; | ||
111 | + alwaysFullScreen?: boolean; | ||
112 | +} | ||
113 | + | ||
114 | +export interface MapperConfigCustom { | ||
115 | + url: string; | ||
116 | + username?: string; | ||
117 | + password?: string; | ||
118 | +} |
@@ -119,8 +119,62 @@ | @@ -119,8 +119,62 @@ | ||
119 | "general-policy": "General policy", | 119 | "general-policy": "General policy", |
120 | "max-failed-login-attempts": "Maximum number of failed login attempts, before account is locked", | 120 | "max-failed-login-attempts": "Maximum number of failed login attempts, before account is locked", |
121 | "minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative", | 121 | "minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative", |
122 | - "user-lockout-notification-email": "In case user account lockout, send notification to email" | ||
123 | - }, | 122 | + "user-lockout-notification-email": "In case user account lockout, send notification to email", |
123 | + "domain-name": "Domain name", | ||
124 | + "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io", | ||
125 | + "add-domain": "Add domain", | ||
126 | + "new-domain": "New domain", | ||
127 | + "add-registration": "Add registration", | ||
128 | + "oauth2": { | ||
129 | + "settings": "OAuth2 settings", | ||
130 | + "registration-id": "Registration ID", | ||
131 | + "registration-id-required": "Registration ID is required.", | ||
132 | + "client-name": "Client name", | ||
133 | + "client-name-required": "Client name is required.", | ||
134 | + "client-id": "Client ID", | ||
135 | + "client-id-required": "Client ID is required.", | ||
136 | + "client-secret": "Client secret", | ||
137 | + "client-secret-required": "Client secret is required.", | ||
138 | + "access-token-uri": "Access token URI", | ||
139 | + "access-token-uri-required": "Access token URI is required.", | ||
140 | + "authorization-uri": "Authorization URI", | ||
141 | + "authorization-uri-required": "Authorization URI is required.", | ||
142 | + "uri-pattern-error": "Invalid URI format.", | ||
143 | + "scope": "Scope", | ||
144 | + "redirect-uri-template": "Redirect URI template", | ||
145 | + "redirect-uri-template-required": "Redirect URI template is required.", | ||
146 | + "jwk-set-uri": "JSON Web Key URI", | ||
147 | + "jwk-set-uri-required": "JSON Web Key URI is required.", | ||
148 | + "user-info-uri": "User info URI", | ||
149 | + "user-info-uri-required": "User info URI is required.", | ||
150 | + "client-authentication-method": "Client authentication method", | ||
151 | + "user-name-attribute-name": "User name attribute key", | ||
152 | + "user-name-attribute-name-required": "User name attribute key is required", | ||
153 | + "allow-user-creation": "Allow user creation", | ||
154 | + "activate-user": "Activate user", | ||
155 | + "type": "Mapper type", | ||
156 | + "email-attribute-key": "Email attribute key", | ||
157 | + "email-attribute-key-required": "Email attribute key is required.", | ||
158 | + "first-name-attribute-key": "First name attribute key", | ||
159 | + "last-name-attribute-key": "Last name attribute key", | ||
160 | + "tenant-name-strategy": "Tenant name strategy", | ||
161 | + "tenant-name-pattern": "Tenant name pattern", | ||
162 | + "tenant-name-pattern-required": "Tenant name pattern is required.", | ||
163 | + "customer-name-pattern": "Customer name pattern", | ||
164 | + "default-dashboard-name": "Default dashboard name", | ||
165 | + "always-fullscreen": "Always fullscreen", | ||
166 | + "url": "URL", | ||
167 | + "url-required": "URL is required.", | ||
168 | + "url-pattern": "Invalid URL format.", | ||
169 | + "login-button-label": "Login button label", | ||
170 | + "login-button-label-required": "Login button label is required.", | ||
171 | + "login-button-icon": "Login button icon", | ||
172 | + "delete-domain-title": "Are you sure you want to delete the domain '{{domainName}}'?", | ||
173 | + "delete-domain-text": "Be careful, after the confirmation a domain and all registration data will be unavailable.", | ||
174 | + "delete-registration-title": "Are you sure you want to delete the registration '{{name}}'?", | ||
175 | + "delete-registration-text": "Be careful, after the confirmation a registration data will be unavailable." | ||
176 | + } | ||
177 | + }, | ||
124 | "alarm": { | 178 | "alarm": { |
125 | "alarm": "Alarm", | 179 | "alarm": "Alarm", |
126 | "alarms": "Alarms", | 180 | "alarms": "Alarms", |
@@ -1561,7 +1615,8 @@ | @@ -1561,7 +1615,8 @@ | ||
1561 | "isolated-tb-core": "Processing in isolated ThingsBoard Core container", | 1615 | "isolated-tb-core": "Processing in isolated ThingsBoard Core container", |
1562 | "isolated-tb-rule-engine": "Processing in isolated ThingsBoard Rule Engine container", | 1616 | "isolated-tb-rule-engine": "Processing in isolated ThingsBoard Rule Engine container", |
1563 | "isolated-tb-core-details": "Requires separate microservice(s) per isolated Tenant", | 1617 | "isolated-tb-core-details": "Requires separate microservice(s) per isolated Tenant", |
1564 | - "isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant" | 1618 | + "isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant", |
1619 | + "allow-oauth2-configuration": "Allow OAuth2 configuration" | ||
1565 | }, | 1620 | }, |
1566 | "timeinterval": { | 1621 | "timeinterval": { |
1567 | "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", | 1622 | "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", |