added role handling

This commit is contained in:
Timo Bejan 2020-10-20 10:59:23 +03:00
parent 31902a78b5
commit e046c74da0
9 changed files with 172 additions and 96 deletions

View File

@ -1,58 +1,60 @@
import { BrowserModule } from '@angular/platform-browser';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {APP_INITIALIZER, NgModule} from '@angular/core';
import { AppComponent } from './app.component';
import {ActivatedRoute, ActivatedRouteSnapshot, Router, RouterModule} from '@angular/router';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
import { BaseScreenComponent } from './screens/base-screen/base-screen.component';
import { ProjectListingScreenComponent } from './screens/project-listing-screen/project-listing-screen.component';
import { ProjectOverviewScreenComponent } from './screens/project-overview-screen/project-overview-screen.component';
import { MatToolbarModule } from '@angular/material/toolbar';
import { ApiModule } from '@redaction/red-ui-http';
import { ApiPathInterceptorService } from './interceptor/api-path-interceptor.service';
import { MatButtonModule } from '@angular/material/button';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { MatMenuModule } from '@angular/material/menu';
import { languageInitializer } from './i18n/language.initializer';
import { LanguageService } from './i18n/language.service';
import { MatIconModule } from '@angular/material/icon';
import { IconsModule } from './icons/icons.module';
import { AddEditProjectDialogComponent } from './screens/project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ConfirmationDialogComponent } from './common/confirmation-dialog/confirmation-dialog.component';
import { FilePreviewScreenComponent } from './screens/file/file-preview-screen/file-preview-screen.component';
import { PdfViewerComponent } from './screens/file/pdf-viewer/pdf-viewer.component';
import { MatTabsModule } from '@angular/material/tabs';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { NgpSortModule } from 'ngp-sort-pipe';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { FileDetailsDialogComponent } from './screens/file/file-preview-screen/file-details-dialog/file-details-dialog.component';
import { ToastrModule } from 'ngx-toastr';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { ProjectDetailsDialogComponent } from './screens/project-overview-screen/project-details-dialog/project-details-dialog.component';
import { AuthModule } from './auth/auth.module';
import { FileUploadModule } from './upload/file-upload.module';
import { FullPageLoadingIndicatorComponent } from './utils/full-page-loading-indicator/full-page-loading-indicator.component';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { InitialsAvatarComponent } from './common/initials-avatar/initials-avatar.component';
import { StatusBarComponent } from './components/status-bar/status-bar.component';
import { LogoComponent } from './logo/logo.component';
import { CompositeRouteGuard } from './utils/composite-route.guard';
import { AppStateGuard } from './state/app-state.guard';
import { SimpleDoughnutChartComponent } from './components/simple-doughnut-chart/simple-doughnut-chart.component';
import { ManualRedactionDialogComponent } from './screens/file/manual-redaction-dialog/manual-redaction-dialog.component';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { AnnotationIconComponent } from './components/annotation-icon/annotation-icon.component';
import { AuthGuard } from "./auth/auth.guard";
import {AppComponent} from './app.component';
import {ActivatedRoute, Router, RouterModule} from '@angular/router';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule} from '@angular/common/http';
import {BaseScreenComponent} from './screens/base-screen/base-screen.component';
import {ProjectListingScreenComponent} from './screens/project-listing-screen/project-listing-screen.component';
import {ProjectOverviewScreenComponent} from './screens/project-overview-screen/project-overview-screen.component';
import {MatToolbarModule} from '@angular/material/toolbar';
import {ApiModule} from '@redaction/red-ui-http';
import {ApiPathInterceptorService} from './interceptor/api-path-interceptor.service';
import {MatButtonModule} from '@angular/material/button';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {MatMenuModule} from '@angular/material/menu';
import {languageInitializer} from './i18n/language.initializer';
import {LanguageService} from './i18n/language.service';
import {MatIconModule} from '@angular/material/icon';
import {IconsModule} from './icons/icons.module';
import {AddEditProjectDialogComponent} from './screens/project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component';
import {MatDialogModule} from '@angular/material/dialog';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {ConfirmationDialogComponent} from './common/confirmation-dialog/confirmation-dialog.component';
import {FilePreviewScreenComponent} from './screens/file/file-preview-screen/file-preview-screen.component';
import {PdfViewerComponent} from './screens/file/pdf-viewer/pdf-viewer.component';
import {MatTabsModule} from '@angular/material/tabs';
import {MatButtonToggleModule} from '@angular/material/button-toggle';
import {NgpSortModule} from 'ngp-sort-pipe';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatSelectModule} from '@angular/material/select';
import {MatSidenavModule} from '@angular/material/sidenav';
import {FileDetailsDialogComponent} from './screens/file/file-preview-screen/file-details-dialog/file-details-dialog.component';
import {ToastrModule} from 'ngx-toastr';
import {ServiceWorkerModule} from '@angular/service-worker';
import {environment} from '../environments/environment';
import {ProjectDetailsDialogComponent} from './screens/project-overview-screen/project-details-dialog/project-details-dialog.component';
import {AuthModule} from './auth/auth.module';
import {FileUploadModule} from './upload/file-upload.module';
import {FullPageLoadingIndicatorComponent} from './utils/full-page-loading-indicator/full-page-loading-indicator.component';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {InitialsAvatarComponent} from './common/initials-avatar/initials-avatar.component';
import {StatusBarComponent} from './components/status-bar/status-bar.component';
import {LogoComponent} from './logo/logo.component';
import {CompositeRouteGuard} from './utils/composite-route.guard';
import {AppStateGuard} from './state/app-state.guard';
import {SimpleDoughnutChartComponent} from './components/simple-doughnut-chart/simple-doughnut-chart.component';
import {ManualRedactionDialogComponent} from './screens/file/manual-redaction-dialog/manual-redaction-dialog.component';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
import {AnnotationIconComponent} from './components/annotation-icon/annotation-icon.component';
import {AuthGuard} from "./auth/auth.guard";
import {AuthErrorComponent} from './screens/auth-error/auth-error.component';
import {RedRoleGuard} from "./auth/red-role.guard";
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@ -77,6 +79,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
SimpleDoughnutChartComponent,
ManualRedactionDialogComponent,
AnnotationIconComponent,
AuthErrorComponent,
],
imports: [
BrowserModule,
@ -101,6 +104,11 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
redirectTo: 'ui/projects',
pathMatch: 'full'
},
{
path: 'auth-error',
component: AuthErrorComponent,
canActivate: [AuthGuard]
},
{
path: 'ui',
component: BaseScreenComponent,
@ -110,7 +118,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
component: ProjectListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, AppStateGuard]
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
@ -118,7 +126,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
component: ProjectOverviewScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, AppStateGuard]
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
@ -126,7 +134,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
component: FilePreviewScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, AppStateGuard]
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
}
]
@ -148,7 +156,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
MatSelectModule,
MatSidenavModule,
FileUploadModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}),
MatProgressSpinnerModule,
MatCheckboxModule
],
@ -166,9 +174,9 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
})
export class AppModule {
constructor(private router: Router,private route: ActivatedRoute) {
route.queryParamMap.subscribe(queryParams=>{
if(queryParams.has('code') || queryParams.has('state') || queryParams.has('session_state')) {
constructor(private router: Router, private route: ActivatedRoute) {
route.queryParamMap.subscribe(queryParams => {
if (queryParams.has('code') || queryParams.has('state') || queryParams.has('session_state')) {
this.router.navigate([], {
queryParams: {
'state': null,

View File

@ -2,6 +2,7 @@ import {Injectable} from "@angular/core";
import {ActivatedRouteSnapshot, Router, RouterStateSnapshot} from "@angular/router";
import {KeycloakAuthGuard, KeycloakService} from "keycloak-angular";
import {UserService} from "../user/user.service";
import {AppLoadStateService} from "../utils/app-load-state.service";
@Injectable({
providedIn: 'root',
@ -10,6 +11,7 @@ export class AuthGuard extends KeycloakAuthGuard {
constructor(
protected readonly _router: Router,
protected readonly _keycloak: KeycloakService,
private readonly _appLoadStateService: AppLoadStateService,
private readonly _userService: UserService
) {
super(_router, _keycloak);
@ -25,6 +27,7 @@ export class AuthGuard extends KeycloakAuthGuard {
await this._userService.loadCurrentUser();
return true;
}
}

View File

@ -0,0 +1,30 @@
import {Injectable} from "@angular/core";
import {ActivatedRouteSnapshot, Router, RouterStateSnapshot} from "@angular/router";
import {KeycloakAuthGuard, KeycloakService} from "keycloak-angular";
import {UserService} from "../user/user.service";
import {AppLoadStateService} from "../utils/app-load-state.service";
@Injectable({
providedIn: 'root',
})
export class RedRoleGuard extends KeycloakAuthGuard {
constructor(
protected readonly _router: Router,
protected readonly _keycloak: KeycloakService,
private readonly _appLoadStateService: AppLoadStateService,
private readonly _userService: UserService
) {
super(_router, _keycloak);
}
public async isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (!this._userService.user.hasAnyREDRoles) {
this._router.navigate(['/auth-error']);
this._appLoadStateService.pushLoadingEvent(false);
return false;
}
return true;
}
}

View File

@ -0,0 +1,4 @@
<section>
<p class="heading-xl" translate="auth-error.heading.label"></p>
<a (click)="logout()" translate="auth-error.logout.label"></a>
</section>

View File

@ -0,0 +1,3 @@
section {
padding: 24px;
}

View File

@ -0,0 +1,19 @@
import { Component, OnInit } from '@angular/core';
import {UserService} from "../../user/user.service";
@Component({
selector: 'redaction-auth-error',
templateUrl: './auth-error.component.html',
styleUrls: ['./auth-error.component.scss']
})
export class AuthErrorComponent implements OnInit {
constructor(private readonly _userService: UserService) { }
ngOnInit(): void {
}
logout() {
this._userService.logout();
}
}

View File

@ -44,7 +44,7 @@ export class UserService {
}
logout() {
this._keycloakService.logout();
this._keycloakService.logout(window.location.origin);
}
async loadCurrentUser() {

View File

@ -1,7 +1,7 @@
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Injectable, Injector } from '@angular/core';
import { Observable, of } from 'rxjs';
import { catchError, mergeMap, tap } from 'rxjs/operators';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {Injectable, Injector} from '@angular/core';
import {Observable, of} from 'rxjs';
import {catchError, mergeMap, tap} from 'rxjs/operators';
import {AppLoadStateService} from "./app-load-state.service";
@Injectable({
@ -10,40 +10,41 @@ import {AppLoadStateService} from "./app-load-state.service";
export class CompositeRouteGuard implements CanActivate {
constructor(protected router: Router, protected injector: Injector, private appLoadStateService: AppLoadStateService) {}
constructor(protected readonly _router: Router, protected readonly _injector: Injector, private readonly _appLoadStateService: AppLoadStateService) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
this.appLoadStateService.pushLoadingEvent(true);
let compositeCanActivateObservable: Observable<boolean> = of(true);
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
this._appLoadStateService.pushLoadingEvent(true);
let compositeCanActivateObservable: Observable<boolean> = of(true);
const routeGuards = route.data.routeGuards;
if (routeGuards) {
for (let i = 0; i < routeGuards.length; i++) {
const routeGuard = this.injector.get(routeGuards[i]);
const canActivateObservable: Observable<boolean> = routeGuard.canActivate(route, state);
compositeCanActivateObservable = compositeCanActivateObservable.pipe(
mergeMap(bool => {
if (!bool) {
return of(false);
} else {
return canActivateObservable;
}
})
);
}
}
const routeGuards = route.data.routeGuards;
if (routeGuards) {
for (let i = 0; i < routeGuards.length; i++) {
const routeGuard = this._injector.get(routeGuards[i]);
const canActivateObservable: Observable<boolean> = routeGuard.canActivate(route, state);
compositeCanActivateObservable = compositeCanActivateObservable.pipe(
tap(() => {
this.appLoadStateService.pushLoadingEvent(false);
}),
catchError(() => {
this.appLoadStateService.pushLoadingEvent(false);
return of(false);
})
mergeMap(bool => {
if (!bool) {
return of(false);
} else {
return canActivateObservable;
}
})
);
return compositeCanActivateObservable;
}
}
compositeCanActivateObservable = compositeCanActivateObservable.pipe(
tap(() => {
this._appLoadStateService.pushLoadingEvent(false);
}),
catchError(() => {
this._appLoadStateService.pushLoadingEvent(false);
return of(false);
})
);
return compositeCanActivateObservable;
}
}

View File

@ -1,4 +1,12 @@
{
"auth-error": {
"heading": {
"label": "Your user doesn't have the required RED-* roles to access this application"
},
"logout": {
"label": "Logout"
}
},
"manual-redaction": {
"remove-annotation": {
"success": {