mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-3735] SSO Role guard and Login error improvement (#4377)
* fix lint and doc * Update auth-guard-sso-role.service.md * Update auth-guard-sso-role.service.md * fix json en * restore en.json file
This commit is contained in:
@@ -311,9 +311,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SETTINGS_CLOUD": {
|
"SETTINGS_CLOUD": {
|
||||||
"MULTISELECTION": "Multiselection",
|
"MULTISELECTION": "Multiselection",
|
||||||
"TESTING_MODE": "Testing Mode",
|
"TESTING_MODE": "Testing Mode",
|
||||||
"SELECTION_MODE": "Selection Mode",
|
"SELECTION_MODE": "Selection Mode",
|
||||||
"TASK_DETAILS_REDIRECTION": "Display task details on task click"
|
"TASK_DETAILS_REDIRECTION": "Display task details on task click"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
import { ModuleWithProviders } from '@angular/core';
|
import { ModuleWithProviders } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { AuthGuard, AuthGuardEcm, ErrorContentComponent, AuthGuardBpm } from '@alfresco/adf-core';
|
import { AuthGuard, AuthGuardEcm, ErrorContentComponent, AuthGuardBpm, AuthGuardSsoRoleService } from '@alfresco/adf-core';
|
||||||
import { AppLayoutComponent } from './components/app-layout/app-layout.component';
|
import { AppLayoutComponent } from './components/app-layout/app-layout.component';
|
||||||
import { LoginComponent } from './components/login/login.component';
|
import { LoginComponent } from './components/login/login.component';
|
||||||
import { HomeComponent } from './components/home/home.component';
|
import { HomeComponent } from './components/home/home.component';
|
||||||
@@ -143,6 +143,8 @@ export const appRoutes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'cloud',
|
path: 'cloud',
|
||||||
|
canActivate: [AuthGuardSsoRoleService],
|
||||||
|
data: { roles: ['ACTIVITI_USER'], redirectUrl: '/error/403'},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -359,6 +361,10 @@ export const appRoutes: Routes = [
|
|||||||
path: 'error/:id',
|
path: 'error/:id',
|
||||||
component: ErrorContentComponent
|
component: ErrorContentComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'error/no-authorization',
|
||||||
|
component: ErrorContentComponent
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
redirectTo: 'error/404'
|
redirectTo: 'error/404'
|
||||||
|
@@ -103,7 +103,8 @@ for more information about installing and using the source code.
|
|||||||
| [Apps process service](apps-process.service.md) | Gets details of the Process Services apps that are deployed for the user. | [Source](../../lib/core/services/apps-process.service.ts) |
|
| [Apps process service](apps-process.service.md) | Gets details of the Process Services apps that are deployed for the user. | [Source](../../lib/core/services/apps-process.service.ts) |
|
||||||
| [Auth guard bpm service](auth-guard-bpm.service.md) | Adds authentication with Process Services to a route within the app. | [Source](../../lib/core/services/auth-guard-bpm.service.ts) |
|
| [Auth guard bpm service](auth-guard-bpm.service.md) | Adds authentication with Process Services to a route within the app. | [Source](../../lib/core/services/auth-guard-bpm.service.ts) |
|
||||||
| [Auth guard ecm service](auth-guard-ecm.service.md) | Adds authentication with Content Services to a route within the app. | [Source](../../lib/core/services/auth-guard-ecm.service.ts) |
|
| [Auth guard ecm service](auth-guard-ecm.service.md) | Adds authentication with Content Services to a route within the app. | [Source](../../lib/core/services/auth-guard-ecm.service.ts) |
|
||||||
| [Auth guard service](auth-guard.service.md) | Adds authentication to a route within the app. | [Source](../../lib/core/services/auth-guard.service.ts) |
|
| [Auth guard service](auth-guard.service.md) | Adds authentication to a route within the app. | [Source](../../lib/core/services/auth-guard.service.ts)
|
||||||
|
| [Auth guard SSO Role service](auth-guard-sso-role.service.md) | check the roles on a user | [Source](../../lib/core/services/auth-guard-sso-role.service.ts) |
|
||||||
| [Authentication service](authentication.service.md) | Provides authentication to ACS and APS. | [Source](../../lib/core/services/authentication.service.ts) |
|
| [Authentication service](authentication.service.md) | Provides authentication to ACS and APS. | [Source](../../lib/core/services/authentication.service.ts) |
|
||||||
| [Comment content service](comment-content.service.md) | Adds and retrieves comments for nodes in Content Services. | [Source](../../lib/core/services/comment-content.service.ts) |
|
| [Comment content service](comment-content.service.md) | Adds and retrieves comments for nodes in Content Services. | [Source](../../lib/core/services/comment-content.service.ts) |
|
||||||
| [Comment process service](comment-process.service.md) | Adds and retrieves comments for task and process instances in Process Services. | [Source](../../lib/core/services/comment-process.service.ts) |
|
| [Comment process service](comment-process.service.md) | Adds and retrieves comments for task and process instances in Process Services. | [Source](../../lib/core/services/comment-process.service.ts) |
|
||||||
|
57
docs/core/auth-guard-sso-role.service.md
Normal file
57
docs/core/auth-guard-sso-role.service.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
Title: Auth Guard SSO Role service
|
||||||
|
Added: v3.1.0
|
||||||
|
Status: Active
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Auth Guard SSO role service](../../lib/core/services/auth-guard-sso-role.service.ts "Defined in auth-guard-sso-role.service.ts")
|
||||||
|
|
||||||
|
Allow to check the user roles of a user
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
The Auth Guard SSO role service implements an Angular
|
||||||
|
[route guard](https://angular.io/guide/router#milestone-5-route-guards)
|
||||||
|
to check the user has the right role permission. This is typically used with the
|
||||||
|
`canActivate` guard check in the route definition. The roles that user needs to have in order to access the route has to be specified in the roles array as in the example below:
|
||||||
|
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const appRoutes: Routes = [
|
||||||
|
...
|
||||||
|
{
|
||||||
|
path: 'examplepath',
|
||||||
|
component: ExampleComponent,
|
||||||
|
canActivate: [ AuthGuardSsoRoleService ],
|
||||||
|
data: { roles: ['USER_ROLE1', 'USER_ROLE2']}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
If the user now clicks on a link or button that follows this route, they will be not able to access to this content if the user does not have the roles.
|
||||||
|
|
||||||
|
## Redirect over forbidden
|
||||||
|
|
||||||
|
If the you want to redirect the user to a different page over a forbidden error you can use the **redirectUrl** as the example below:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const appRoutes: Routes = [
|
||||||
|
...
|
||||||
|
{
|
||||||
|
path: 'examplepath',
|
||||||
|
component: ExampleComponent,
|
||||||
|
canActivate: [ AuthGuardSsoRoleService ],
|
||||||
|
data: { roles: ['ACTIVITI_USER'], redirectUrl: '/error/403'}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: you can use this Guard in and with the other ADF auth guard.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Auth guard ecm service](auth-guard-ecm.service.md)
|
||||||
|
- [Auth guard bpm service](auth-guard-bpm.service.md)
|
||||||
|
- [Auth guard service](auth-guard.service.md)
|
@@ -210,7 +210,8 @@
|
|||||||
"LOGIN-ERROR-PROVIDERS": "Providers cannot be undefined",
|
"LOGIN-ERROR-PROVIDERS": "Providers cannot be undefined",
|
||||||
"LOGIN-ERROR-CORS": "CORS exception, check your server configuration",
|
"LOGIN-ERROR-CORS": "CORS exception, check your server configuration",
|
||||||
"LOGIN-ERROR-CSRF": "CSRF exception, set [disableCsrf]=\"true\" in login.component",
|
"LOGIN-ERROR-CSRF": "CSRF exception, set [disableCsrf]=\"true\" in login.component",
|
||||||
"LOGIN-ECM-LICENSE": "Alfresco Content Services repository is in read-only mode"
|
"LOGIN-ECM-LICENSE": "Alfresco Content Services repository is in read-only mode",
|
||||||
|
"SSO-WRONG-CONFIGURATION": "SSO Authentication server unreachable"
|
||||||
},
|
},
|
||||||
"BUTTON": {
|
"BUTTON": {
|
||||||
"LOGIN": "SIGN IN",
|
"LOGIN": "SIGN IN",
|
||||||
|
@@ -21,15 +21,16 @@
|
|||||||
|
|
||||||
<mat-card-content class="adf-login-controls">
|
<mat-card-content class="adf-login-controls">
|
||||||
|
|
||||||
<div *ngIf="!implicitFlow">
|
<!--ERRORS AREA-->
|
||||||
<!--ERRORS AREA-->
|
<div class="adf-error-container">
|
||||||
<div class="adf-error-container">
|
<div *ngIf="isError" id="login-error" data-automation-id="login-error"
|
||||||
<div *ngIf="isError" id="login-error" data-automation-id="login-error"
|
class="adf-error adf-error-message">
|
||||||
class="adf-error adf-error-message">
|
<mat-icon class="adf-error-icon">warning</mat-icon>
|
||||||
<mat-icon class="adf-error-icon">warning</mat-icon>
|
<span class="adf-login-error-message">{{errorMsg | translate }}</span>
|
||||||
<span class="adf-login-error-message">{{errorMsg | translate }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="!implicitFlow">
|
||||||
|
|
||||||
<!--USERNAME FIELD-->
|
<!--USERNAME FIELD-->
|
||||||
<div class="adf-login__field"
|
<div class="adf-login__field"
|
||||||
|
@@ -489,7 +489,7 @@ describe('LoginComponent', () => {
|
|||||||
it('should return ECM read-only error when error occurs', async(() => {
|
it('should return ECM read-only error when error occurs', async(() => {
|
||||||
spyOn(authService, 'login')
|
spyOn(authService, 'login')
|
||||||
.and.returnValue(
|
.and.returnValue(
|
||||||
throwError(
|
throwError(
|
||||||
{
|
{
|
||||||
message: 'ERROR: 00170728 Access Denied. The system is currently in read-only mode',
|
message: 'ERROR: 00170728 Access Denied. The system is currently in read-only mode',
|
||||||
status: 403
|
status: 403
|
||||||
@@ -569,49 +569,80 @@ describe('LoginComponent', () => {
|
|||||||
loginWithCredentials('fake-username', 'fake-password');
|
loginWithCredentials('fake-username', 'fake-password');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('SSO', () => {
|
describe('SSO ', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
describe('implicitFlow ', () => {
|
||||||
appConfigService.config.oauth2 = <OauthConfigModel> { implicitFlow: true };
|
|
||||||
appConfigService.load();
|
beforeEach(() => {
|
||||||
alfrescoApiService.reset();
|
appConfigService.config.oauth2 = <OauthConfigModel> { implicitFlow: true };
|
||||||
|
appConfigService.load();
|
||||||
|
alfrescoApiService.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show login username and password if SSO implicit flow is active', async(() => {
|
||||||
|
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||||
|
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(element.querySelector('#username')).toBeNull();
|
||||||
|
expect(element.querySelector('#password')).toBeNull();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not show the login base auth button', async(() => {
|
||||||
|
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||||
|
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(element.querySelector('#login-button')).toBeNull();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the login SSO button', async(() => {
|
||||||
|
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||||
|
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(element.querySelector('#login-button-sso')).toBeDefined();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the SSO error when the discovery server is unreachable', async(() => {
|
||||||
|
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||||
|
spyOn(authService, 'isSSODiscoveryConfigured').and.returnValue(false);
|
||||||
|
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
element.querySelector('[data-automation-id="login-button-sso"]').click();
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(getLoginErrorMessage()).toEqual('LOGIN.MESSAGES.SSO-WRONG-CONFIGURATION' );
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not show the SSO error when the discovery server is reachable', async(() => {
|
||||||
|
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||||
|
spyOn(authService, 'isSSODiscoveryConfigured').and.returnValue(true);
|
||||||
|
spyOn(authService, 'ssoImplicitLogin').and.stub();
|
||||||
|
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
element.querySelector('[data-automation-id="login-button-sso"]').click();
|
||||||
|
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(getLoginErrorMessage()).toBeUndefined();
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show login username and password if SSO implicit flow is active', async(() => {
|
|
||||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
|
||||||
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
fixture.whenStable().then(() => {
|
|
||||||
expect(element.querySelector('#username')).toBeNull();
|
|
||||||
expect(element.querySelector('#password')).toBeNull();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should not show the login base auth button', async(() => {
|
|
||||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
|
||||||
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
fixture.whenStable().then(() => {
|
|
||||||
expect(element.querySelector('#login-button')).toBeNull();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should show the login SSO button', async(() => {
|
|
||||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
|
||||||
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
fixture.whenStable().then(() => {
|
|
||||||
expect(element.querySelector('#login-button-sso')).toBeDefined();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -15,7 +15,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, EventEmitter,
|
import {
|
||||||
|
Component, EventEmitter,
|
||||||
Input, OnInit, Output, TemplateRef, ViewEncapsulation
|
Input, OnInit, Output, TemplateRef, ViewEncapsulation
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
@@ -173,20 +174,31 @@ export class LoginComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
onSubmit(values: any) {
|
onSubmit(values: any) {
|
||||||
this.disableError();
|
this.disableError();
|
||||||
const args = new LoginSubmitEvent({
|
|
||||||
controls: { username: this.form.controls.username }
|
|
||||||
});
|
|
||||||
this.executeSubmit.emit(args);
|
|
||||||
|
|
||||||
if (args.defaultPrevented) {
|
if (this.authService.isOauth() && this.authService.isSSODiscoveryConfigured()) {
|
||||||
return false;
|
this.errorMsg = 'LOGIN.MESSAGES.SSO-WRONG-CONFIGURATION';
|
||||||
|
this.isError = true;
|
||||||
} else {
|
} else {
|
||||||
this.performLogin(values);
|
const args = new LoginSubmitEvent({
|
||||||
|
controls: { username: this.form.controls.username }
|
||||||
|
});
|
||||||
|
this.executeSubmit.emit(args);
|
||||||
|
|
||||||
|
if (args.defaultPrevented) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this.performLogin(values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitLogin() {
|
implicitLogin() {
|
||||||
this.authService.ssoImplicitLogin();
|
if (this.authService.isOauth() && !this.authService.isSSODiscoveryConfigured()) {
|
||||||
|
this.errorMsg = 'LOGIN.MESSAGES.SSO-WRONG-CONFIGURATION';
|
||||||
|
this.isError = true;
|
||||||
|
} else {
|
||||||
|
this.authService.ssoImplicitLogin();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -267,7 +279,7 @@ export class LoginComponent implements OnInit {
|
|||||||
} else if (
|
} else if (
|
||||||
err.status === 403 &&
|
err.status === 403 &&
|
||||||
err.message.indexOf('The system is currently in read-only mode') !==
|
err.message.indexOf('The system is currently in read-only mode') !==
|
||||||
-1
|
-1
|
||||||
) {
|
) {
|
||||||
this.errorMsg = 'LOGIN.MESSAGES.LOGIN-ECM-LICENSE';
|
this.errorMsg = 'LOGIN.MESSAGES.LOGIN-ECM-LICENSE';
|
||||||
} else {
|
} else {
|
||||||
@@ -351,7 +363,7 @@ export class LoginComponent implements OnInit {
|
|||||||
this._message = {
|
this._message = {
|
||||||
username: {
|
username: {
|
||||||
required: {
|
required: {
|
||||||
value: 'LOGIN.MESSAGES.USERNAME-REQUIRED'
|
value: 'LOGIN.MESSAGES.USERNAME-REQUIRED'
|
||||||
},
|
},
|
||||||
minLength: {
|
minLength: {
|
||||||
value: 'LOGIN.MESSAGES.USERNAME-MIN',
|
value: 'LOGIN.MESSAGES.USERNAME-MIN',
|
||||||
|
119
lib/core/services/auth-guard-sso-role.service.spec.ts
Normal file
119
lib/core/services/auth-guard-sso-role.service.spec.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* 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 { async, TestBed } from '@angular/core/testing';
|
||||||
|
import { ActivatedRouteSnapshot, Router } from '@angular/router';
|
||||||
|
import { setupTestBed } from '../testing/setupTestBed';
|
||||||
|
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||||
|
import { AuthGuardSsoRoleService } from './auth-guard-sso-role.service';
|
||||||
|
import { JwtHelperService } from './jwt-helper.service';
|
||||||
|
import { StorageService } from './storage.service';
|
||||||
|
|
||||||
|
describe('Auth Guard SSO role service', () => {
|
||||||
|
|
||||||
|
let authGuard: AuthGuardSsoRoleService;
|
||||||
|
let storageService: StorageService;
|
||||||
|
let jwtHelperService: JwtHelperService;
|
||||||
|
let routerService: Router;
|
||||||
|
|
||||||
|
setupTestBed({
|
||||||
|
imports: [CoreTestingModule]
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
storageService = TestBed.get(StorageService);
|
||||||
|
authGuard = TestBed.get(AuthGuardSsoRoleService);
|
||||||
|
jwtHelperService = TestBed.get(JwtHelperService);
|
||||||
|
routerService = TestBed.get(Router);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should canActivate be true if the Role is present int the JWT token', async(() => {
|
||||||
|
spyOn(storageService, 'getItem').and.returnValue('my-access_token');
|
||||||
|
spyOn(jwtHelperService, 'decodeToken').and.returnValue({ 'realm_access': { roles: ['role1'] } });
|
||||||
|
|
||||||
|
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||||
|
router.data = { 'roles': ['role1', 'role2'] };
|
||||||
|
|
||||||
|
expect(authGuard.canActivate(router, null)).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('Should canActivate be false if the Role is not present int the JWT token', async(() => {
|
||||||
|
spyOn(storageService, 'getItem').and.returnValue('my-access_token');
|
||||||
|
spyOn(jwtHelperService, 'decodeToken').and.returnValue({ 'realm_access': { roles: ['role3'] } });
|
||||||
|
|
||||||
|
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||||
|
router.data = { 'roles': ['role1', 'role2'] };
|
||||||
|
|
||||||
|
expect(authGuard.canActivate(router, null)).toBeFalsy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('Should not redirect if canActivate is', async(() => {
|
||||||
|
spyOn(storageService, 'getItem').and.returnValue('my-access_token');
|
||||||
|
spyOn(jwtHelperService, 'decodeToken').and.returnValue({ 'realm_access': { roles: ['role1'] } });
|
||||||
|
spyOn(routerService, 'navigate').and.stub();
|
||||||
|
|
||||||
|
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||||
|
router.data = { 'roles': ['role1', 'role2']};
|
||||||
|
|
||||||
|
expect(authGuard.canActivate(router, null)).toBeTruthy();
|
||||||
|
expect(routerService.navigate).not.toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('Should canActivate return false if the data Role to check is empty', async(() => {
|
||||||
|
spyOn(storageService, 'getItem').and.returnValue('my-access_token');
|
||||||
|
spyOn(jwtHelperService, 'decodeToken').and.returnValue({ 'realm_access': { roles: ['role1', 'role3'] } });
|
||||||
|
|
||||||
|
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||||
|
|
||||||
|
expect(authGuard.canActivate(router, null)).toBeFalsy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('Should canActivate return false if the realm_access is not present', async(() => {
|
||||||
|
spyOn(storageService, 'getItem').and.returnValue('my-access_token');
|
||||||
|
spyOn(jwtHelperService, 'decodeToken').and.returnValue({ });
|
||||||
|
|
||||||
|
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||||
|
|
||||||
|
expect(authGuard.canActivate(router, null)).toBeFalsy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('Should redirect to the redirectURL if canActivate is false and redirectUrl is in data', async(() => {
|
||||||
|
spyOn(storageService, 'getItem').and.returnValue('my-access_token');
|
||||||
|
spyOn(jwtHelperService, 'decodeToken').and.returnValue({ });
|
||||||
|
spyOn(routerService, 'navigate').and.stub();
|
||||||
|
|
||||||
|
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||||
|
router.data = { 'roles': ['role1', 'role2'], 'redirectUrl': 'no-role-url'};
|
||||||
|
|
||||||
|
expect(authGuard.canActivate(router, null)).toBeFalsy();
|
||||||
|
expect(routerService.navigate).toHaveBeenCalledWith(['/no-role-url']);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('Should not redirect if canActivate is false and redirectUrl is not in data', async(() => {
|
||||||
|
spyOn(storageService, 'getItem').and.returnValue('my-access_token');
|
||||||
|
spyOn(jwtHelperService, 'decodeToken').and.returnValue({ });
|
||||||
|
spyOn(routerService, 'navigate').and.stub();
|
||||||
|
|
||||||
|
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||||
|
router.data = { 'roles': ['role1', 'role2']};
|
||||||
|
|
||||||
|
expect(authGuard.canActivate(router, null)).toBeFalsy();
|
||||||
|
expect(routerService.navigate).not.toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
82
lib/core/services/auth-guard-sso-role.service.ts
Normal file
82
lib/core/services/auth-guard-sso-role.service.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* 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 { JwtHelperService } from './jwt-helper.service';
|
||||||
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { StorageService } from './storage.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthGuardSsoRoleService implements CanActivate {
|
||||||
|
|
||||||
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
||||||
|
let hasRole = false;
|
||||||
|
|
||||||
|
if (route.data) {
|
||||||
|
let rolesToCheck = route.data['roles'];
|
||||||
|
hasRole = this.hasRoles(rolesToCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasRole && route.data && route.data['redirectUrl']) {
|
||||||
|
this.router.navigate(['/' + route.data['redirectUrl']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private storageService: StorageService, private jwtHelperService: JwtHelperService, private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoles(): string[] {
|
||||||
|
const access = this.getValueFromToken<any>('realm_access');
|
||||||
|
const roles = access ? access['roles'] : [];
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessToken(): string {
|
||||||
|
return this.storageService.getItem('access_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRole(role: string): boolean {
|
||||||
|
let hasRole = false;
|
||||||
|
if (this.getAccessToken()) {
|
||||||
|
const roles = this.getRoles();
|
||||||
|
hasRole = roles.some((currentRole) => {
|
||||||
|
return currentRole === role;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return hasRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRoles(rolesToCheck: string []): boolean {
|
||||||
|
return rolesToCheck.some((currentRole) => {
|
||||||
|
return this.hasRole(currentRole);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getValueFromToken<T>(key: string): T {
|
||||||
|
let value;
|
||||||
|
const accessToken = this.getAccessToken();
|
||||||
|
if (accessToken) {
|
||||||
|
const tokenPayload = this.jwtHelperService.decodeToken(accessToken);
|
||||||
|
value = tokenPayload[key];
|
||||||
|
}
|
||||||
|
return <T> value;
|
||||||
|
}
|
||||||
|
}
|
@@ -314,4 +314,11 @@ export class AuthenticationService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if SSO is configured correctly
|
||||||
|
*/
|
||||||
|
isSSODiscoveryConfigured() {
|
||||||
|
return this.alfrescoApi.getInstance().storage.getItem('discovery') ? true : false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,7 @@ export * from './content.service';
|
|||||||
export * from './auth-guard.service';
|
export * from './auth-guard.service';
|
||||||
export * from './auth-guard-ecm.service';
|
export * from './auth-guard-ecm.service';
|
||||||
export * from './auth-guard-bpm.service';
|
export * from './auth-guard-bpm.service';
|
||||||
|
export * from './auth-guard-sso-role.service';
|
||||||
export * from './apps-process.service';
|
export * from './apps-process.service';
|
||||||
export * from './page-title.service';
|
export * from './page-title.service';
|
||||||
export * from './storage.service';
|
export * from './storage.service';
|
||||||
|
Reference in New Issue
Block a user