Browse Source

Refactoring OAuth2

pull/3557/head
Vladyslav_Prykhodko 6 years ago
parent
commit
556cb5aa5d
  1. 2
      ui-ngx/src/app/core/auth/auth.models.ts
  2. 1
      ui-ngx/src/app/core/auth/auth.reducer.ts
  3. 14
      ui-ngx/src/app/core/auth/auth.service.ts
  4. 51
      ui-ngx/src/app/core/guards/redirect-guard-settings.service.ts
  5. 24
      ui-ngx/src/app/core/services/menu.service.ts
  6. 5
      ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts
  7. 36
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts
  8. 3
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html
  9. 14
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts
  10. 4
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
ui-ngx/src/app/core/auth/auth.models.ts

@ -22,7 +22,6 @@ export interface AuthPayload {
userTokenAccessEnabled: boolean;
allowedDashboardIds: string[];
forceFullscreen: boolean;
allowOAuth2Configuration: boolean;
}
export interface AuthState {
@ -34,5 +33,4 @@ export interface AuthState {
allowedDashboardIds: string[];
forceFullscreen: boolean;
lastPublicDashboardId: string;
allowOAuth2Configuration: boolean;
}

1
ui-ngx/src/app/core/auth/auth.reducer.ts

@ -22,7 +22,6 @@ const emptyUserAuthState: AuthPayload = {
userDetails: null,
userTokenAccessEnabled: false,
forceFullscreen: false,
allowOAuth2Configuration: false,
allowedDashboardIds: []
};

14
ui-ngx/src/app/core/auth/auth.service.ts

@ -18,7 +18,7 @@ import { Injectable, NgZone } from '@angular/core';
import { JwtHelperService } from '@auth0/angular-jwt';
import { HttpClient } from '@angular/common/http';
import { forkJoin, Observable, of, throwError, ReplaySubject } from 'rxjs';
import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs';
import { catchError, map, mergeMap, tap } from 'rxjs/operators';
import { LoginRequest, LoginResponse, OAuth2Client, PublicLoginRequest } from '@shared/models/login.models';
@ -425,25 +425,15 @@ export class AuthService {
}
}
private loadIsOAuth2ConfigurationAllow(authUser: AuthUser): Observable<boolean> {
if (authUser.authority === Authority.TENANT_ADMIN) {
return this.http.get<boolean>('/api/oauth2/config/isAllowed', defaultHttpOptions());
} else {
return of(false);
}
}
private loadSystemParams(authPayload: AuthPayload): Observable<any> {
const sources: Array<Observable<any>> = [this.loadIsUserTokenAccessEnabled(authPayload.authUser),
this.fetchAllowedDashboardIds(authPayload),
this.loadIsOAuth2ConfigurationAllow(authPayload.authUser),
this.timeService.loadMaxDatapointsLimit()];
return forkJoin(sources)
.pipe(map((data) => {
const userTokenAccessEnabled: boolean = data[0];
const allowedDashboardIds: string[] = data[1];
const allowOAuth2Configuration: boolean = data[2];
return {userTokenAccessEnabled, allowedDashboardIds, allowOAuth2Configuration};
return {userTokenAccessEnabled, allowedDashboardIds};
}));
}

51
ui-ngx/src/app/core/guards/redirect-guard-settings.service.ts

@ -1,51 +0,0 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { AuthState } from '@core/auth/auth.models';
import { select, Store } from '@ngrx/store';
import { selectAuth } from '@core/auth/auth.selectors';
import { take } from 'rxjs/operators';
import { AppState } from '@core/core.state';
import { Authority } from '@shared/models/authority.enum';
@Injectable({
providedIn: 'root'
})
export class RedirectGuardSettings implements CanActivate {
constructor(private store: Store<AppState>,
private router: Router) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot) {
let auth: AuthState = null;
this.store.pipe(select(selectAuth), take(1)).subscribe(
(authState: AuthState) => {
auth = authState;
}
);
if (auth?.userDetails?.authority === Authority.TENANT_ADMIN) {
this.router.navigateByUrl('/settings/oauth2');
return false;
}
this.router.navigateByUrl('/settings/general');
return false;
}
}

24
ui-ngx/src/app/core/services/menu.service.ts

@ -18,13 +18,12 @@ import { Injectable } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { select, Store } from '@ngrx/store';
import { AppState } from '../core.state';
import { getCurrentAuthState, selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors';
import { selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors';
import { take } from 'rxjs/operators';
import { HomeSection, MenuSection } from '@core/services/menu.models';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { Authority } from '@shared/models/authority.enum';
import { AuthUser } from '@shared/models/user.model';
import { AuthState } from '@core/auth/auth.models';
@Injectable({
providedIn: 'root'
@ -44,8 +43,6 @@ export class MenuService {
);
}
authState: AuthState = getCurrentAuthState(this.store);
private buildMenu() {
this.store.pipe(select(selectAuthUser), take(1)).subscribe(
(authUser: AuthUser) => {
@ -236,25 +233,6 @@ export class MenuService {
icon: 'track_changes'
}
);
if (this.authState.allowOAuth2Configuration) {
sections.push({
name: 'admin.settings',
type: 'toggle',
path: '/settings',
height: '40px',
icon: 'settings',
pages: [
{
name: 'admin.oauth2.oauth2',
type: 'link',
path: '/settings/oauth2',
icon: 'security'
}
]
});
}
return sections;
}

5
ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts

@ -23,7 +23,6 @@ import { Authority } from '@shared/models/authority.enum';
import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component';
import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component';
import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component';
import { RedirectGuardSettings } from '../../../../core/guards/redirect-guard-settings.service';
const routes: Routes = [
{
@ -38,7 +37,7 @@ const routes: Routes = [
children: [
{
path: '',
canActivate: [RedirectGuardSettings],
redirectTo: 'general',
pathMatch: 'full'
},
{
@ -85,7 +84,7 @@ const routes: Routes = [
component: OAuth2SettingsComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
auth: [Authority.SYS_ADMIN],
title: 'admin.oauth2.oauth2',
breadcrumb: {
label: 'admin.oauth2.oauth2',

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

@ -60,7 +60,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
additionalInfo: {
providerName: 'Custom'
},
clientAuthenticationMethod: 'Post',
clientAuthenticationMethod: 'POST',
userNameAttributeName: 'email',
mapperConfig: {
allowUserCreation: true,
@ -249,17 +249,17 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null],
clientId: [registrationData?.clientId ? registrationData.clientId : '', Validators.required],
clientSecret: [registrationData?.clientSecret ? registrationData.clientSecret : '', Validators.required],
accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '', [
Validators.required,
Validators.pattern(this.URL_REGEXP)]],
authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '', [
Validators.required,
Validators.pattern(this.URL_REGEXP)]],
accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '',
[Validators.required,
Validators.pattern(this.URL_REGEXP)]],
authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '',
[Validators.required,
Validators.pattern(this.URL_REGEXP)]],
scope: this.fb.array(registrationData?.scope ? registrationData.scope : []),
jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)],
userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '', [
Validators.required,
Validators.pattern(this.URL_REGEXP)]],
userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '',
[Validators.required,
Validators.pattern(this.URL_REGEXP)]],
clientAuthenticationMethod: [
registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : 'POST', Validators.required],
userNameAttributeName: [
@ -285,7 +285,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => {
(clientRegistration.get('scope') as FormArray).clear();
if (provider === 'Custom') {
clientRegistration.reset(this.defaultProvider, {emitEvent: false});
const defaultSettings = {...this.defaultProvider, ...{id: clientRegistration.get('id').value}};
clientRegistration.reset(defaultSettings, {emitEvent: false});
clientRegistration.get('accessTokenUri').enable();
clientRegistration.get('authorizationUri').enable();
clientRegistration.get('jwkSetUri').enable();
@ -294,6 +295,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
const template = this.templates.get(provider);
delete template.id;
delete template.additionalInfo;
template.clientId = '';
template.clientSecret = '';
template.scope.forEach(() => {
(clientRegistration.get('scope') as FormArray).push(this.fb.control(''));
});
@ -301,13 +304,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
clientRegistration.get('authorizationUri').disable();
clientRegistration.get('jwkSetUri').disable();
clientRegistration.get('userInfoUri').disable();
clientRegistration.patchValue({
...template, ...{
clientId: '',
clientSecret: '',
id: {id: null, entityType: null}
}
}, {emitEvent: false});
clientRegistration.patchValue(template, {emitEvent: false});
}
}));
@ -469,7 +466,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
componentRef.onDestroy(() => {
if (componentRef.instance.result !== null) {
const attributeValue = componentRef.instance.result;
this.clientDomains.at(index).get('redirectUriTemplate').patchValue(attributeValue, {emitEvent: true});
this.clientDomains.at(index).get('redirectUriTemplate').patchValue(attributeValue);
this.clientDomains.at(index).get('redirectUriTemplate').markAsDirty();
}
});
}

3
ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html

@ -54,9 +54,6 @@
<mat-label translate>tenant.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea>
</mat-form-field>
<mat-checkbox fxFlex formControlName="allowOAuth2Configuration" style="padding-bottom: 16px;">
{{ 'tenant.allow-oauth2-configuration' | translate }}
</mat-checkbox>
</div>
<tb-contact [parentForm]="entityForm" [isEdit]="isEdit"></tb-contact>
<div fxLayout="column">

14
ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts

@ -23,7 +23,6 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti
import { TranslateService } from '@ngx-translate/core';
import { ContactBasedComponent } from '../../components/entity/contact-based.component';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { isDefined } from '@core/utils';
@Component({
selector: 'tb-tenant',
@ -54,11 +53,8 @@ export class TenantComponent extends ContactBasedComponent<Tenant> {
title: [entity ? entity.title : '', [Validators.required]],
isolatedTbCore: [entity ? entity.isolatedTbCore : false, []],
isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []],
additionalInfo: this.fb.group(
{
description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
allowOAuth2Configuration: [isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ?
entity.additionalInfo.allowOAuth2Configuration : false]
additionalInfo: this.fb.group({
description: [entity && entity.additionalInfo ? entity.additionalInfo.description : '']
}
)
}
@ -69,11 +65,7 @@ export class TenantComponent extends ContactBasedComponent<Tenant> {
this.entityForm.patchValue({title: entity.title});
this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore});
this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine});
this.entityForm.patchValue({additionalInfo: {
description: entity.additionalInfo ? entity.additionalInfo.description : '',
allowOAuth2Configuration: isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ?
entity.additionalInfo.allowOAuth2Configuration : false
}});
this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}});
}
updateFormState() {

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

@ -126,7 +126,6 @@
"add-domain": "Add domain",
"new-domain": "New domain",
"add-provider": "Add provider",
"settings": "Settings",
"oauth2": {
"oauth2": "OAuth2",
"registration-id": "Registration ID",
@ -1715,8 +1714,7 @@
"isolated-tb-core": "Processing in isolated ThingsBoard Core container",
"isolated-tb-rule-engine": "Processing in isolated ThingsBoard Rule Engine container",
"isolated-tb-core-details": "Requires separate microservice(s) per isolated Tenant",
"isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant",
"allow-oauth2-configuration": "Allow OAuth2 configuration"
"isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant"
},
"timeinterval": {
"seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",

Loading…
Cancel
Save