Skip to content
Snippets Groups Projects
Commit b273fcea authored by François Agneray's avatar François Agneray
Browse files

Auth guard for dataset

parent 5e02f891
No related branches found
No related tags found
2 merge requests!72Develop,!34New features
Showing with 205 additions and 78 deletions
......@@ -11,16 +11,12 @@ import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import * as fromAuth from 'src/app/auth/auth.reducer';
import * as authActions from 'src/app/auth/auth.actions';
import * as authSelector from 'src/app/auth/auth.selector';
import * as instanceActions from 'src/app/metamodel/actions/instance.actions';
import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
import * as instanceGroupActions from 'src/app/metamodel/actions/instance-group.actions';
import * as instanceGroupSelector from 'src/app/metamodel/selectors/instance-group.selector';
import { UserProfile } from 'src/app/auth/user-profile.model';
import { AppConfigService } from 'src/app/app-config.service';
/**
......@@ -38,18 +34,12 @@ export class AppComponent implements OnInit {
public instanceListIsLoaded: Observable<boolean>;
public instanceGroupListIsLoading: Observable<boolean>;
public instanceGroupListIsLoaded: Observable<boolean>;
public isAuthenticated: Observable<boolean>;
public userProfile: Observable<UserProfile>;
public userRoles: Observable<string[]>;
constructor(private store: Store<{ auth: fromAuth.State }>, private config: AppConfigService) {
this.instanceListIsLoading = store.select(instanceSelector.selectInstanceListIsLoading);
this.instanceListIsLoaded = store.select(instanceSelector.selectInstanceListIsLoaded);
this.instanceGroupListIsLoading = store.select(instanceGroupSelector.selectInstanceGroupListIsLoading);
this.instanceGroupListIsLoaded = store.select(instanceGroupSelector.selectInstanceGroupListIsLoaded);
this.isAuthenticated = store.select(authSelector.selectIsAuthenticated);
this.userProfile = store.select(authSelector.selectUserProfile);
this.userRoles = store.select(authSelector.selectUserRoles);
}
ngOnInit() {
......@@ -58,45 +48,4 @@ export class AppComponent implements OnInit {
Promise.resolve(null).then(() => this.store.dispatch(instanceActions.loadInstanceList()));
Promise.resolve(null).then(() => this.store.dispatch(instanceGroupActions.loadInstanceGroupList()));
}
/**
* Checks if authentication is enabled.
*
* @return boolean
*/
authenticationEnabled(): boolean {
return this.config.authenticationEnabled;
}
/**
* Checks if authentication is enabled.
*/
login(): void {
this.store.dispatch(authActions.login());
}
/**
* Dispatches action to log in.
*/
logout(): void {
this.store.dispatch(authActions.logout());
}
/**
* Dispatches action to open profile editor.
*/
openEditProfile(): void {
this.store.dispatch(authActions.openEditProfile());
}
/**
* Checks if user is ANIS administrator.
*
* @return Observable<boolean>
*/
isAnisAdmin(): Observable<boolean> {
return this.userRoles.pipe(
map(roles => roles.includes('anis_admin'))
);
}
}
/**
* This file is part of Anis Client.
*
* @copyright Laboratoire d'Astrophysique de Marseille / CNRS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { Store, select } from '@ngrx/store';
import { combineLatest, Observable } from 'rxjs';
import { switchMap, map, skipWhile } from 'rxjs/operators';
import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
import * as instanceGroupSelector from 'src/app/metamodel/selectors/instance-group.selector';
import * as authActions from 'src/app/auth/auth.actions';
import * as authSelector from 'src/app/auth/auth.selector';
import { AppConfigService } from 'src/app/app-config.service';
import { isAdmin } from 'src/app/shared/utils';
@Injectable({
providedIn: 'root',
})
export class InstanceAuthGuard implements CanActivate {
constructor(
protected readonly router: Router,
private store: Store<{ }>,
private config: AppConfigService
) { }
canActivate(): Observable<boolean> {
return this.store.pipe(select(instanceSelector.selectInstanceListIsLoaded)).pipe(
skipWhile(instanceListIsLoaded => !instanceListIsLoaded),
switchMap(() => {
return combineLatest([
this.store.pipe(select(instanceSelector.selectInstanceByRouteName)),
this.store.pipe(select(authSelector.selectUserRoles)),
this.store.pipe(select(authSelector.selectIsAuthenticated)),
this.store.pipe(select(instanceGroupSelector.selectAllInstanceGroups))
]).pipe(
map(([instance, userRoles, isAuthenticated, instanceGroupList]) => {
// No authorization required to continue
if (!this.config.authenticationEnabled
|| instance.public
|| (isAuthenticated && isAdmin(this.config.adminRoles, userRoles))) {
return true;
}
// Force the user to log in if currently unauthenticated.
if (!isAuthenticated) {
this.store.dispatch(authActions.login());
return false;
}
// If authenticated but not authorized go to unauthorized page.
let accessible = instanceGroupList
.filter(instanceGroup => instanceGroup.instances.includes(instance.name))
.filter(instanceGroup => userRoles.includes(instanceGroup.role))
.length > 0;
if (!accessible) {
this.router.navigateByUrl('/unauthorized');
return false;
}
// Let "Router" allow user entering the page
return true;
})
);
})
);
}
}
......@@ -11,10 +11,11 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { InstanceComponent } from './instance.component';
import { InstanceAuthGuard } from './instance-auth.guard';
const routes: Routes = [
{
path: ':iname', component: InstanceComponent, children: [
path: ':iname', component: InstanceComponent, canActivate: [InstanceAuthGuard], children: [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomeModule) },
{ path: 'search', loadChildren: () => import('./search/search.module').then(m => m.SearchModule) },
......
......@@ -11,6 +11,7 @@ import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { Router } from '@angular/router';
import { Dataset, DatasetGroup } from 'src/app/metamodel/models';
import { isAdmin } from 'src/app/shared/utils';
/**
* @class
......@@ -55,13 +56,7 @@ export class DatasetCardComponent {
* @returns boolean
*/
isAdmin() {
let admin = false;
for (let i = 0; i < this.adminRoles.length; i++) {
admin = this.userRoles.includes(this.adminRoles[i]);
if (admin) break;
}
return admin;
return isAdmin(this.adminRoles, this.userRoles);
}
/**
......
/**
* This file is part of Anis Client.
*
* @copyright Laboratoire d'Astrophysique de Marseille / CNRS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { Store, select } from '@ngrx/store';
import { combineLatest, Observable, of } from 'rxjs';
import { switchMap, map, skipWhile } from 'rxjs/operators';
import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector';
import * as datasetActions from 'src/app/metamodel/actions/dataset.actions';
import * as datasetGroupSelector from 'src/app/metamodel/selectors/dataset-group.selector';
import * as datasetGroupActions from 'src/app/metamodel/actions/dataset-group.actions';
import * as authActions from 'src/app/auth/auth.actions';
import * as authSelector from 'src/app/auth/auth.selector';
import { AppConfigService } from 'src/app/app-config.service';
import { isAdmin } from 'src/app/shared/utils';
@Injectable({
providedIn: 'root',
})
export class SearchAuthGuard implements CanActivate {
constructor(
protected readonly router: Router,
private store: Store<{ }>,
private config: AppConfigService
) { }
canActivate(): Observable<boolean> {
return combineLatest([
this.store.pipe(select(datasetSelector.selectDatasetListIsLoaded)),
this.store.pipe(select(datasetGroupSelector.selectDatasetGroupListIsLoaded))
]).pipe(
map(([datasetListIsLoaded, datasetGroupListIsLoaded]) => {
if (!datasetListIsLoaded) {
this.store.dispatch(datasetActions.loadDatasetList());
}
if (!datasetGroupListIsLoaded) {
this.store.dispatch(datasetGroupActions.loadDatasetGroupList());
}
return [datasetListIsLoaded, datasetGroupListIsLoaded];
}),
skipWhile(([datasetListIsLoaded, datasetGroupListIsLoaded]) => !datasetListIsLoaded || !datasetGroupListIsLoaded),
switchMap(() => {
return combineLatest([
this.store.pipe(select(datasetSelector.selectDatasetByRouteName)),
this.store.pipe(select(authSelector.selectUserRoles)),
this.store.pipe(select(authSelector.selectIsAuthenticated)),
this.store.pipe(select(datasetGroupSelector.selectAllDatasetGroups))
]).pipe(
map(([dataset, userRoles, isAuthenticated, datasetGroupList]) => {
// No authorization required to continue
if (!this.config.authenticationEnabled
|| dataset.public
|| (isAuthenticated && isAdmin(this.config.adminRoles, userRoles))) {
return true;
}
// Force the user to log in if currently unauthenticated.
if (!isAuthenticated) {
this.store.dispatch(authActions.login());
return false;
}
// If authenticated but not authorized go to unauthorized page.
let accessible = datasetGroupList
.filter(datasetGroup => datasetGroup.datasets.includes(dataset.name))
.filter(datasetGroup => userRoles.includes(datasetGroup.role))
.length > 0;
if (!accessible) {
this.router.navigateByUrl('/unauthorized');
return false;
}
// Let "Router" allow user entering the page
return true;
})
);
})
);
}
}
......@@ -16,6 +16,7 @@ import { CriteriaComponent } from './containers/criteria.component';
import { OutputComponent } from './containers/output.component';
import { ResultComponent } from './containers/result.component';
import { DetailComponent } from './containers/detail.component';
import { SearchAuthGuard } from './search-auth.guard';
const routes: Routes = [
{ path: 'detail/:dname/:id', component: DetailComponent },
......@@ -23,10 +24,10 @@ const routes: Routes = [
path: '', component: SearchComponent, children: [
{ path: '', redirectTo: 'dataset', pathMatch: 'full' },
{ path: 'dataset', component: DatasetComponent },
{ path: 'dataset/:dname', component: DatasetComponent },
{ path: 'criteria/:dname', component: CriteriaComponent },
{ path: 'output/:dname', component: OutputComponent },
{ path: 'result/:dname', component: ResultComponent }
{ path: 'dataset/:dname', canActivate: [SearchAuthGuard], component: DatasetComponent },
{ path: 'criteria/:dname', canActivate: [SearchAuthGuard], component: CriteriaComponent },
{ path: 'output/:dname', canActivate: [SearchAuthGuard], component: OutputComponent },
{ path: 'result/:dname', canActivate: [SearchAuthGuard], component: ResultComponent }
]
}
];
......
......@@ -10,6 +10,7 @@
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { Instance, InstanceGroup } from 'src/app/metamodel/models';
import { isAdmin } from 'src/app/shared/utils';
/**
* @class
......@@ -61,12 +62,6 @@ export class InstanceCardComponent {
* @returns boolean
*/
isAdmin() {
let admin = false;
for (let i = 0; i < this.adminRoles.length; i++) {
admin = this.userRoles.includes(this.adminRoles[i]);
if (admin) break;
}
return admin;
return isAdmin(this.adminRoles, this.userRoles);
}
}
......@@ -78,10 +78,20 @@ export class PortalHomeComponent implements OnInit {
return this.config.authenticationEnabled;
}
/**
* Returns admin roles list
*
* @returns string[]
*/
getAdminRoles(): string[] {
return this.config.adminRoles;
}
/**
* Returns ANIS Server API URL
*
* @returns string
*/
getApiUrl(): string {
return this.config.apiUrl;
}
......
......@@ -11,6 +11,7 @@ import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from
import { Instance } from 'src/app/metamodel/models';
import { UserProfile } from 'src/app/auth/user-profile.model';
import { isAdmin } from 'src/app/shared/utils';
/**
* @class
......@@ -43,13 +44,7 @@ export class NavbarComponent {
* @returns boolean
*/
isAdmin() {
let admin = false;
for (let i = 0; i < this.adminRoles.length; i++) {
admin = this.userRoles.includes(this.adminRoles[i]);
if (admin) break;
}
return admin;
return isAdmin(this.adminRoles, this.userRoles);
}
isPortalRoute() {
......
......@@ -13,3 +13,18 @@ export const getHost = (apiUrl: string): string => {
}
return apiUrl;
}
/**
* Returns true if user is admin
*
* @returns boolean
*/
export const isAdmin = (adminRoles: string[], userRoles: string[]): boolean => {
let admin = false;
for (let i = 0; i < adminRoles.length; i++) {
admin = userRoles.includes(adminRoles[i]);
if (admin) break;
}
return admin;
}
......@@ -35,7 +35,7 @@ services:
SSO_AUTH_URL: "http://localhost:8180/auth"
SSO_REALM: "anis"
SSO_CLIENT_ID: "anis-client"
TOKEN_ENABLED: 1
TOKEN_ENABLED: 0
TOKEN_JWKS_URL: "http://keycloak:8180/auth/realms/anis/protocol/openid-connect/certs"
TOKEN_ADMIN_ROLES: anis_admin,superuser
RMQ_HOST: rmq
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment