Commit 483a3b847b8266d71e39477094add11836076219

Authored by Vladyslav_Prykhodko
1 parent 7af3fe00

Add tenant menu settingOAuth2

Add validation unique parameters to settings OAuth2
... ... @@ -22,6 +22,7 @@ export interface AuthPayload {
22 22 userTokenAccessEnabled: boolean;
23 23 allowedDashboardIds: string[];
24 24 forceFullscreen: boolean;
  25 + allowOAuth2Configuration: boolean;
25 26 }
26 27
27 28 export interface AuthState {
... ... @@ -33,4 +34,5 @@ export interface AuthState {
33 34 allowedDashboardIds: string[];
34 35 forceFullscreen: boolean;
35 36 lastPublicDashboardId: string;
  37 + allowOAuth2Configuration: boolean;
36 38 }
... ...
... ... @@ -22,6 +22,7 @@ const emptyUserAuthState: AuthPayload = {
22 22 userDetails: null,
23 23 userTokenAccessEnabled: false,
24 24 forceFullscreen: false,
  25 + allowOAuth2Configuration: false,
25 26 allowedDashboardIds: []
26 27 };
27 28
... ...
... ... @@ -425,15 +425,25 @@ export class AuthService {
425 425 }
426 426 }
427 427
  428 + private loadIsOAuth2ConfigurationAllow(authUser: AuthUser): Observable<boolean> {
  429 + if (authUser.authority === Authority.TENANT_ADMIN) {
  430 + return this.http.get<boolean>('/api/oauth2/config/isAllowed', defaultHttpOptions());
  431 + } else {
  432 + return of(false);
  433 + }
  434 + }
  435 +
428 436 private loadSystemParams(authPayload: AuthPayload): Observable<any> {
429 437 const sources: Array<Observable<any>> = [this.loadIsUserTokenAccessEnabled(authPayload.authUser),
430 438 this.fetchAllowedDashboardIds(authPayload),
  439 + this.loadIsOAuth2ConfigurationAllow(authPayload.authUser),
431 440 this.timeService.loadMaxDatapointsLimit()];
432 441 return forkJoin(sources)
433 442 .pipe(map((data) => {
434 443 const userTokenAccessEnabled: boolean = data[0];
435 444 const allowedDashboardIds: string[] = data[1];
436   - return {userTokenAccessEnabled, allowedDashboardIds};
  445 + const allowOAuth2Configuration: boolean = data[2];
  446 + return {userTokenAccessEnabled, allowedDashboardIds, allowOAuth2Configuration};
437 447 }));
438 448 }
439 449
... ...
... ... @@ -18,12 +18,13 @@ import { Injectable } from '@angular/core';
18 18 import { AuthService } from '../auth/auth.service';
19 19 import { select, Store } from '@ngrx/store';
20 20 import { AppState } from '../core.state';
21   -import { selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors';
  21 +import { getCurrentAuthState, selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors';
22 22 import { take } from 'rxjs/operators';
23 23 import { HomeSection, MenuSection } from '@core/services/menu.models';
24 24 import { BehaviorSubject, Observable, Subject } from 'rxjs';
25 25 import { Authority } from '@shared/models/authority.enum';
26 26 import { AuthUser } from '@shared/models/user.model';
  27 +import { AuthState } from '@core/auth/auth.models';
27 28
28 29 @Injectable({
29 30 providedIn: 'root'
... ... @@ -43,6 +44,8 @@ export class MenuService {
43 44 );
44 45 }
45 46
  47 + authState: AuthState = getCurrentAuthState(this.store);
  48 +
46 49 private buildMenu() {
47 50 this.store.pipe(select(selectAuthUser), take(1)).subscribe(
48 51 (authUser: AuthUser) => {
... ... @@ -233,6 +236,25 @@ export class MenuService {
233 236 icon: 'track_changes'
234 237 }
235 238 );
  239 +
  240 + if (this.authState.allowOAuth2Configuration) {
  241 + sections.push({
  242 + name: 'admin.system-settings',
  243 + type: 'toggle',
  244 + path: '/settings',
  245 + height: '40px',
  246 + icon: 'settings',
  247 + pages: [
  248 + {
  249 + name: 'admin.oauth2.settings',
  250 + type: 'link',
  251 + path: '/settings/oauth2-settings',
  252 + icon: 'security'
  253 + }
  254 + ]
  255 + })
  256 + }
  257 +
236 258 return sections;
237 259 }
238 260
... ...
... ... @@ -28,7 +28,7 @@ const routes: Routes = [
28 28 {
29 29 path: 'settings',
30 30 data: {
31   - auth: [Authority.SYS_ADMIN],
  31 + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
32 32 breadcrumb: {
33 33 label: 'admin.system-settings',
34 34 icon: 'settings'
... ... @@ -37,7 +37,7 @@ const routes: Routes = [
37 37 children: [
38 38 {
39 39 path: '',
40   - redirectTo: 'general',
  40 + redirectTo: Authority.TENANT_ADMIN ? 'oauth2-settings' : 'general',
41 41 pathMatch: 'full'
42 42 },
43 43 {
... ... @@ -84,7 +84,7 @@ const routes: Routes = [
84 84 component: OAuth2SettingsComponent,
85 85 canDeactivate: [ConfirmOnExitGuard],
86 86 data: {
87   - auth: [Authority.SYS_ADMIN],
  87 + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
88 88 title: 'admin.oauth2.settings',
89 89 breadcrumb: {
90 90 label: 'admin.oauth2.settings',
... ...
... ... @@ -52,6 +52,9 @@
52 52 <mat-error *ngIf="domain.get('domainName').hasError('pattern')">
53 53 {{ 'admin.error-verification-url' | translate }}
54 54 </mat-error>
  55 + <mat-error *ngIf="domain.get('domainName').hasError('unique')">
  56 + {{ 'admin.domain-name-unique' | translate }}
  57 + </mat-error>
55 58 </mat-form-field>
56 59
57 60 <mat-form-field fxFlex class="mat-block">
... ... @@ -84,6 +87,9 @@
84 87 <mat-error *ngIf="registration.get('registrationId').hasError('required')">
85 88 {{ 'admin.oauth2.registration-id-required' | translate }}
86 89 </mat-error>
  90 + <mat-error *ngIf="registration.get('registrationId').hasError('unique')">
  91 + {{ 'admin.oauth2.registration-id-unique' | translate }}
  92 + </mat-error>
87 93 </mat-form-field>
88 94
89 95 <mat-form-field fxFlex class="mat-block">
... ... @@ -140,10 +146,8 @@
140 146 <mat-form-field fxFlex class="mat-block">
141 147 <mat-label translate>admin.oauth2.scope</mat-label>
142 148 <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)">
  149 + <mat-chip *ngFor="let scope of registration.get('scope').value; let k = index;"
  150 + removable (removed)="removeScope(k, registration)">
147 151 {{scope}}
148 152 <mat-icon matChipRemove>cancel</mat-icon>
149 153 </mat-chip>
... ... @@ -152,6 +156,9 @@
152 156 matChipInputAddOnBlur
153 157 (matChipInputTokenEnd)="addScope($event, registration)">
154 158 </mat-chip-list>
  159 + <mat-error *ngIf="registration.get('scope').hasError('required')">
  160 + {{ 'admin.oauth2.jwk-set-uri-required' | translate }}
  161 + </mat-error>
155 162 </mat-form-field>
156 163
157 164 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
... ...
... ... @@ -112,14 +112,44 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
112 112
113 113 private buildOAuth2SettingsForm(): void {
114 114 this.oauth2SettingsForm = this.fb.group({
115   - clientsDomainsParams: this.fb.array([])
  115 + clientsDomainsParams: this.fb.array([], Validators.required)
116 116 });
117 117 }
118 118
119 119 private initOAuth2Settings(oauth2Settings: OAuth2Settings): void {
120   - oauth2Settings.clientsDomainsParams.forEach((domaindomain) => {
121   - this.clientsDomainsParams.push(this.buildSettingsDomain(domaindomain));
122   - });
  120 + if(oauth2Settings.clientsDomainsParams) {
  121 + oauth2Settings.clientsDomainsParams.forEach((domaindomain) => {
  122 + this.clientsDomainsParams.push(this.buildSettingsDomain(domaindomain));
  123 + });
  124 + }
  125 + }
  126 +
  127 + private uniqueDomainValidator(control: AbstractControl): { [key: string]: boolean } | null {
  128 + if (control.value !== null && control?.root) {
  129 + const listDomainName = [];
  130 + control.root.value.clientsDomainsParams.forEach((domain) => {
  131 + listDomainName.push(domain.domainName);
  132 + })
  133 + if (listDomainName.indexOf(control.value) > -1) {
  134 + return {unique: true};
  135 + }
  136 + }
  137 + return null;
  138 + }
  139 +
  140 + private uniqueRegistrationIdValidator(control: AbstractControl): { [key: string]: boolean } | null {
  141 + if (control.value !== null && control?.root) {
  142 + const listRegistration = [];
  143 + control.root.value.clientsDomainsParams.forEach((domain) => {
  144 + domain.clientRegistrations.forEach((client) => {
  145 + listRegistration.push(client.registrationId);
  146 + })
  147 + })
  148 + if (listRegistration.indexOf(control.value) > -1) {
  149 + return {unique: true};
  150 + }
  151 + }
  152 + return null;
123 153 }
124 154
125 155 private buildSettingsDomain(domainParams?: DomainParams): FormGroup {
... ... @@ -130,9 +160,9 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
130 160 }
131 161 url += '/login/oauth2/code/';
132 162 const formDomain = this.fb.group({
133   - domainName: ['', [Validators.required, Validators.pattern('((?![:/]).)*$')]],
  163 + domainName: [null, [Validators.required, Validators.pattern('((?![:/]).)*$'), this.uniqueDomainValidator]],
134 164 redirectUriTemplate: [url, [Validators.required, Validators.pattern(this.URL_REGEXP)]],
135   - clientRegistrations: this.fb.array([])
  165 + clientRegistrations: this.fb.array([], Validators.required)
136 166 });
137 167
138 168 this.subscriptions.push(formDomain.get('domainName').valueChanges.subscribe((domain) => {
... ... @@ -156,7 +186,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
156 186
157 187 private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup {
158 188 const clientRegistration = this.fb.group({
159   - registrationId: [null, [Validators.required]],
  189 + registrationId: [null, [Validators.required, this.uniqueRegistrationIdValidator]],
160 190 clientName: [null, [Validators.required]],
161 191 loginButtonLabel: [null, [Validators.required]],
162 192 loginButtonIcon: [null],
... ... @@ -202,7 +232,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
202 232 }
203 233
204 234 save(): void {
205   - console.log(this.oauth2SettingsForm.value);
206 235 this.adminService.saveOAuth2Settings(this.oauth2SettingsForm.value).subscribe(
207 236 (oauth2Settings) => {
208 237 this.oauth2Settings = oauth2Settings;
... ... @@ -246,7 +275,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
246 275
247 276 const domainName = this.clientsDomainsParams.at(index).get('domainName').value;
248 277 this.dialogService.confirm(
249   - this.translate.instant('admin.oauth2.delete-domain-title', {domainName}),
  278 + this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}),
250 279 this.translate.instant('admin.oauth2.delete-domain-text'), null,
251 280 this.translate.instant('action.delete')
252 281 ).subscribe((data) => {
... ... @@ -272,7 +301,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
272 301
273 302 const registrationId = this.clientDomainRegistrations(controler).at(index).get('registrationId').value;
274 303 this.dialogService.confirm(
275   - this.translate.instant('admin.oauth2.delete-registration-title', {name: registrationId}),
  304 + this.translate.instant('admin.oauth2.delete-registration-title', {name: registrationId || ''}),
276 305 this.translate.instant('admin.oauth2.delete-registration-text'), null,
277 306 this.translate.instant('action.delete')
278 307 ).subscribe((data) => {
... ...
... ... @@ -58,7 +58,7 @@ export class TenantComponent extends ContactBasedComponent<Tenant> {
58 58 {
59 59 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
60 60 allowOAuth2Configuration: [isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ?
61   - entity.additionalInfo.allowOAuth2Configuration : true]
  61 + entity.additionalInfo.allowOAuth2Configuration : false]
62 62 }
63 63 )
64 64 }
... ... @@ -72,7 +72,7 @@ export class TenantComponent extends ContactBasedComponent<Tenant> {
72 72 this.entityForm.patchValue({additionalInfo: {
73 73 description: entity.additionalInfo ? entity.additionalInfo.description : '',
74 74 allowOAuth2Configuration: isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ?
75   - entity.additionalInfo.allowOAuth2Configuration : true
  75 + entity.additionalInfo.allowOAuth2Configuration : false
76 76 }});
77 77 }
78 78
... ...
... ... @@ -121,6 +121,7 @@
121 121 "minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative",
122 122 "user-lockout-notification-email": "In case user account lockout, send notification to email",
123 123 "domain-name": "Domain name",
  124 + "domain-name-unique": "Domain name need to unique for the system.",
124 125 "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io",
125 126 "add-domain": "Add domain",
126 127 "new-domain": "New domain",
... ... @@ -129,6 +130,7 @@
129 130 "settings": "OAuth2 settings",
130 131 "registration-id": "Registration ID",
131 132 "registration-id-required": "Registration ID is required.",
  133 + "registration-id-unique": "Registration ID need to unique for the system.",
132 134 "client-name": "Client name",
133 135 "client-name-required": "Client name is required.",
134 136 "client-id": "Client ID",
... ...