Changed how user roles are requested by the webclient
Signed-off-by: Matthias Koller <m.koller@peak-solution.de>
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/app-routing.module.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/app-routing.module.ts
index f7507f3..5afad64 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/app-routing.module.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/app-routing.module.ts
@@ -15,10 +15,13 @@
import { NgModule, Component } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
+import { AuthGuard } from './authentication/authguard';
+import { Role } from './authentication/authentication.service';
const appRoutes: Routes = [
{ path: 'navigator', loadChildren: './navigator-view/mdm-navigator-view.module#MDMNavigatorViewModule' },
- { path: 'administration', loadChildren: './administration/admin.module#AdminModule' },
+ { path: 'administration', loadChildren: './administration/admin.module#AdminModule',
+ canActivate: [AuthGuard], data: { roles: [Role.Admin] } },
{ path: '', redirectTo: 'navigator', pathMatch: 'full' }
];
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/app.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/app.component.html
index 70ad10b..886ea26 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/app.component.html
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/app.component.html
@@ -17,7 +17,7 @@
<a class="navbar-brand" routerLink="/navigator" style="cursor:pointer;">openMDM5 Web</a>
<div class="collapse navbar-collapse" id="bs-mdm-navbar">
<ul class="navbar-nav mr-auto">
- <li class="nav-item" *ngFor="let m of links" [routerLinkActive]="['active']"><a class="nav-link" routerLink="{{m.path}}" style="cursor:pointer;"> {{m.name}}</a></li>
+ <li class="nav-item" *ngFor="let m of (links | authPipe | async)" [routerLinkActive]="['active']"><a class="nav-link" routerLink="{{m.path}}" style="cursor:pointer;"> {{m.name}}</a></li>
</ul>
<ul class="navbar-nav ml-md-auto">
<li class="nav-item"><span class="navbar-text" style="padding: 0 5px; vertical-align: middle;">{{ 'app.language' | translate }}</span> <p-dropdown [options]="languages" (onChange)="selectLanguage($event)" [(ngModel)]="selectedLanguage" [style]="{ 'margin-top': '2px' }"></p-dropdown></li>
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/app.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/app.component.ts
index 573301d..fdcd9df 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/app.component.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/app.component.ts
@@ -17,6 +17,8 @@
import { TranslateService } from '@ngx-translate/core';
import {SelectItem} from 'primeng/api';
+import { User, Role } from './authentication/authentication.service';
+import { AuthenticationService } from './authentication/authentication.service';
@Component({
selector: 'app-root',
@@ -36,11 +38,11 @@
selectedLanguage = 'en';
links = [
- { name: 'Administration', path: '/administration' }
+ { name: 'Administration', path: '/administration', roles: [ Role.Admin ] }
];
displayAboutDialog = false;
- constructor(private translate: TranslateService) {
+ constructor(private translate: TranslateService, private authService: AuthenticationService) {
}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/app.module.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/app.module.ts
index a1c2102..5432274 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/app.module.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/app.module.ts
@@ -43,12 +43,14 @@
import { QueryService } from './tableview/query.service';
import { ViewService } from './tableview/tableview.service';
import { environment } from '../environments/environment';
+import { FilesAttachableService } from './file-explorer/services/files-attachable.service';
+import { AuthenticationModule } from './authentication/authentication.module';
// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
}
-import { FilesAttachableService } from './file-explorer/services/files-attachable.service';
+
@NgModule({
imports: [
@@ -57,6 +59,7 @@
HttpModule,
FormsModule,
MDMCoreModule,
+ AuthenticationModule,
DialogModule,
AppRoutingModule,
DropdownModule,
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/auth.pipe.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/auth.pipe.ts
new file mode 100644
index 0000000..a696573
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/auth.pipe.ts
@@ -0,0 +1,24 @@
+import { Pipe, PipeTransform } from '@angular/core';
+import { AuthenticationService } from './authentication.service';
+import { forkJoin, Observable, of } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+
+@Pipe({
+ name: 'authPipe',
+ pure: true
+})
+export class AuthPipe implements PipeTransform {
+ constructor(private authService: AuthenticationService) {
+ }
+
+ transform(linksWithRoles: { roles: string }[]): Observable<{ roles: string }[]> {
+ if (!linksWithRoles) {
+ return of(linksWithRoles);
+ }
+
+ return forkJoin(linksWithRoles.map(l => this.authService.isUserInRole(l.roles))).pipe(
+ map(booleanFilter => linksWithRoles.filter((l, i) => booleanFilter[i]))
+ );
+ }
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.module.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.module.ts
index a3920c7..0e80250 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.module.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.module.ts
@@ -13,14 +13,15 @@
********************************************************************************/
import { NgModule } from '@angular/core';
-import { AuthenticationService } from './authentication.service';
-import { AuthenticationStateService } from './authenticationstate.service';
+import { AuthPipe } from './auth.pipe';
@NgModule({
-providers: [
- AuthenticationStateService,
- AuthenticationService
-]
+declarations: [
+ AuthPipe,
+],
+exports: [
+ AuthPipe,
+],
})
export class AuthenticationModule {
}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.service.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.service.ts
index c94221a..09bdbda 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.service.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.service.ts
@@ -14,42 +14,68 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
-import { Router } from '@angular/router';
-import { AuthenticationStateService, User } from './authenticationstate.service';
+import { Router, ResolveStart } from '@angular/router';
import { PropertyService } from '../core/property.service';
+import { Observable } from 'rxjs';
+import { publishReplay, refCount, map, tap } from 'rxjs/operators';
+
+export class User {
+ username: string;
+ roles: string[];
+}
+
+export enum Role {
+ Admin = 'Admin',
+ User = 'User',
+ Guest = 'Guest',
+}
@Injectable({
- providedIn: 'root'
- })
+ providedIn: 'root'
+})
export class AuthenticationService {
readonly loginURL: string;
readonly logoutURL: string;
readonly currentUserURL: string;
- constructor(private http: HttpClient, private router: Router, private _prop: PropertyService,
- private authService: AuthenticationStateService) {
+ private loginUser: Observable<User>;
+ constructor(private http: HttpClient,
+ private _prop: PropertyService) {
this.currentUserURL = _prop.getUrl('mdm/user/current');
-
- this.loadUser().subscribe(
- user => this.authService.setLoginUser(user),
- error => this.authService.unsetLoginUser());
- }
-
- private loadUser() {
- return this.http.get<User>(this.currentUserURL);
}
isLoggedIn() {
- return this.authService.isLoggedIn();
+ return this.getLoginUser().pipe(map(user => user !== null));
}
getLoginUser() {
- return this.authService.getLoginUser();
+ if (!this.loginUser) {
+ this.loginUser = this.loadUser().pipe(
+ publishReplay(1),
+ refCount()
+ );
+ }
+
+ return this.loginUser;
}
- hasRole(role: string) {
- return this.authService.isUserInRole(role);
+ isUserInRole(roles: string | string[]) {
+ if (typeof roles === 'string') {
+ roles = [ roles ];
+ }
+
+ return this.getLoginUser().pipe(
+ map(user => user.roles.filter(x => roles.includes(x)).length > 0),
+ tap(b => console.log('user has roles', roles, b))
+ );
}
+ private loadUser() {
+ return this.http.get<User>(this.currentUserURL, {
+ params: {
+ roles: Object.values(Role).join(',')
+ }
+ });
+ }
}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authenticationstate.service.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authenticationstate.service.ts
deleted file mode 100644
index 377ecbd..0000000
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authenticationstate.service.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information regarding copyright ownership.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v. 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0.
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- ********************************************************************************/
-
-import { Injectable } from '@angular/core';
-import { BehaviorSubject } from 'rxjs';
-import { map } from 'rxjs/operators';
-
-export class User {
- username: string;
- roles: string[];
-}
-
-@Injectable({
- providedIn: 'root'
- })
-export class AuthenticationStateService {
- readonly currentUserKey = 'currentUser';
-
- loginUser = new BehaviorSubject<User>(null);
-
- getLoginUserValue() {
- return this.loginUser.value;
- }
-
- isUserInRole(roles: string | string[]) {
- if (typeof roles === 'string') {
- roles = [ roles ];
- }
- return this.isLoggedIn() ? this.loginUser.value.roles.filter(x => roles.includes(x)).length > 0 : false;
- }
-
- getLoginUser() {
- return this.loginUser.asObservable();
- }
-
- isLoggedIn() {
- return this.loginUser.asObservable().pipe(map(user => user !== null));
- }
-
- setLoginUser(user: User) {
- this.loginUser.next(user);
- localStorage.setItem(this.currentUserKey, JSON.stringify(user));
- }
-
- unsetLoginUser() {
- this.loginUser.next(null);
- localStorage.removeItem(this.currentUserKey);
- }
-
- clearLoginState() {
- this.unsetLoginUser();
- }
-}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authguard.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authguard.ts
new file mode 100644
index 0000000..fc126f5
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authguard.ts
@@ -0,0 +1,23 @@
+import { Injectable } from '@angular/core';
+import { CanActivate } from '@angular/router/src/utils/preactivation';
+import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
+
+import { AuthenticationService } from './authentication.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthGuard implements CanActivate {
+ path: ActivatedRouteSnapshot[];
+ route: ActivatedRouteSnapshot;
+
+ constructor(private authStateService: AuthenticationService) { }
+
+ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+ if (route.data.roles) {
+ // check if current user has any role the route requires
+ return this.authStateService.isUserInRole(route.data.roles);
+ }
+ return true;
+ }
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer/xy-chart-viewer.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer/xy-chart-viewer.component.html
index e11cbfc..7273cff 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer/xy-chart-viewer.component.html
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer/xy-chart-viewer.component.html
@@ -31,7 +31,7 @@
</div>
<!-- Chart / Table -->
<div [ngClass]="showSelection ? 'p-col-9' : 'p-col-12'">
- <p-chart #xyChart type="scatter" [data]="data" [options]="options"></p-chart>
+ <p-chart #xyChart type="scatter" [data]="data"></p-chart>
</div>
</div>
<div class="p-grid">
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.html
index 4a357c9..eec690f 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.html
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.html
@@ -21,7 +21,7 @@
<div *ngIf="contextComponents" class="mdm-details-attributes">
- <!-- <button (click)="changeTreeEdit(true, false)" *ngIf="!editMode && canEdit()" class="btn btn-mdm" style="margin-bottom: 5px;">
+ <!-- <button (click)="changeTreeEdit(true, false)" *ngIf="!editMode && canEdit" class="btn btn-mdm" style="margin-bottom: 5px;">
<span class="fa fa-pencil"></span>
{{ 'details.mdm-detail-descriptive-data.btn-edit' | translate }}
</button>
@@ -39,7 +39,7 @@
<ng-template pTemplate="caption">
<div (click)="toggleTreeNodeState(ttref)" class="clickable">
<button (click)="onEdit($event)"
- *ngIf="!editMode && canEdit()"
+ *ngIf="!editMode && canEdit"
class="btn btn-mdm"
title="{{ 'details.mdm-detail-descriptive-data.btn-edit' | translate }}">
<span class="fa fa-pencil"></span>
@@ -78,7 +78,7 @@
<!-- ordered -->
<td ttEditableColumn [ttEditableColumnDisabled]="!editMode" class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}"
- [ngClass]="{'clickable': canEdit() && editMode}">
+ [ngClass]="{'clickable': canEdit && editMode}">
<p-treeTableCellEditor>
<ng-template pTemplate="input">
<attribute-editor
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts
index 34c9c98..8c2c0e1 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts
@@ -32,6 +32,7 @@
import { Sensor, Components, Context, ContextAttributeIdentifier} from '../../model/details.model';
import { ContextService } from '../../services/context.service';
+import { Role } from 'src/app/authentication/authentication.service';
@Component({
selector: 'mdm-detail-context',
@@ -75,8 +76,10 @@
private fileService: FileService) {
}
- ngOnInit() {
+ public canEdit: boolean;
+ ngOnInit() {
+ this.authenticationService.isUserInRole([Role.Admin.toString(), Role.User.toString()]).subscribe(b => this.canEdit = b);
this.status = this.StatusLoading;
this.editMode = false;
@@ -426,14 +429,6 @@
);
}
- /**
- * Can edit attributes
- */
- public canEdit() {
- // Read the role 'write-user' from the web.xml configuration mapping
- return this.authenticationService.hasRole('write-user');
- }
-
onEdit(event: Event) {
event.stopPropagation();
this.editMode = true;
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/en.json b/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/en.json
index 0d63d9b..bf20d77 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/en.json
+++ b/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/en.json
@@ -134,7 +134,7 @@
},
"mdm-detail-panel": {
"btn-into-shopping-basket": "Into shopping basket",
- "cannot-update-node": "Cannot update node.",
+ "cannot-update-node": "Cannot update node.",
"tblhdr-attribute": "Attribute",
"tblhdr-unit": "Unit",
"tblhdr-value": "Value"
@@ -217,8 +217,8 @@
"modules": {
"mdm-modules": {
"details": "Details",
- "mdm-search": "MDM Search",
- "file-explorer": "Files"
+ "mdm-search": "MDM Search",
+ "file-explorer": "Files"
}
},
"navigator-view": {
@@ -245,11 +245,11 @@
"nodeprovider": {
"err-cannot-load-node-provider-from-settings": "Cannot load node provider from settings",
"err-unsupported-type": "Type {{ type }} is not supported. Type {{ typeToUse }} should be used instead."
- },
- "attribute-value": {
- "msg-no-files-attached": "No files attached",
- "msg-one-file-attached": "1 file attached",
- "msg-x-files-attached": "{{numberOfFiles}} files attached"
+ },
+ "attribute-value": {
+ "msg-no-files-attached": "No files attached",
+ "msg-one-file-attached": "1 file attached",
+ "msg-x-files-attached": "{{numberOfFiles}} files attached"
}
},
"search": {
@@ -295,7 +295,7 @@
"search-attributes-from": "Search attributes from",
"search-filter-name": "Filter name",
"title-save-search-filter-as": "Save search filter as",
- "select-search-filter": "Select search filter",
+ "select-search-filter": "Select search filter",
"tooltip-add-selection-to-shopping-basket": "Add selection to shopping basket",
"tooltip-clear-search-results": "Clear search results",
"tooltip-create-new-search-filter": "Create new search filter",
@@ -398,7 +398,6 @@
}
},
"file-explorer": {
- "file-explorer": {
"file-explorer": {
"btn-add-files": "Add files",
"btn-delete-file": "Delete file",
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UserResource.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UserResource.java
index 26737fb..b842fd3 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UserResource.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UserResource.java
@@ -14,20 +14,28 @@
package org.eclipse.mdm.businessobjects.boundary;
-import org.eclipse.mdm.businessobjects.control.MDMEntityAccessException;
-import org.eclipse.mdm.businessobjects.entity.User;
+import java.util.ArrayList;
+import java.util.List;
import javax.annotation.security.PermitAll;
-import javax.security.auth.Subject;
-import javax.security.jacc.PolicyContext;
-import javax.security.jacc.PolicyContextException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
-import javax.ws.rs.core.*;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.List;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+import org.eclipse.mdm.businessobjects.entity.User;
+
+import com.google.common.base.Splitter;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
@Path("/user")
@PermitAll
@@ -38,37 +46,29 @@
@GET
@Path("/current")
+ @Operation(summary = "Read authenticated user information", description = "Read the current user name and which of the given roles the user has.", responses = {
+ @ApiResponse(description = "The authenticated user.", content = @Content(schema = @Schema(implementation = User.class))),
+ @ApiResponse(responseCode = "400", description = "Error") })
@Produces(MediaType.APPLICATION_JSON)
- public Response currentUser() {
- User user = new User();
- user.setUsername(securityContext.getUserPrincipal().getName());
- user.setRoles(getRolesInGlassfish());
- return Response.ok(user).build();
- }
-
- /**
- * Getting the roles of a user is heavily dependent on the used application
- * server. This method will only work with Glassfish. (It was tested with
- * Glassfish 4.1.2). The following blog entry gives a good overview of the
- * issues concerning JAAS.
- *
- * @see https://arjan-tijms.omnifaces.org/2014/02/jaas-in-java-ee-is-not-universal.html
- * @return list of rules of the logged in user.
- */
- private static List<String> getRolesInGlassfish() {
- List<String> list = new ArrayList<>();
-
- try {
- Subject subject = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");
- for (Principal principal : subject.getPrincipals()) {
- if ("org.glassfish.security.common.Group".equals(principal.getClass().getName())) {
- Principal role = (Principal) principal;
- list.add(role.getName());
+ public Response currentUser(
+ @Parameter(description = "Comma separated list of roles. This method checks, if the user belongs to each role. If the user does not belong to a role or the role is not queried, it will not be returned.", required = true) @QueryParam("roles") String roles) {
+ /*
+ * There is no reliable way in getting the (possibly mapped) roles of a user.
+ * Thus we only check, in which of the given roles the user is.
+ */
+ List<String> roleList = new ArrayList<>();
+ if (roles != null) {
+ for (String role : Splitter.on(",").trimResults().split(roles)) {
+ if (securityContext.isUserInRole(role)) {
+ roleList.add(role);
}
}
- } catch (PolicyContextException e) {
- throw new MDMEntityAccessException("Could not load roles for user!", e);
}
- return list;
+
+ User user = new User();
+ securityContext.isUserInRole(null);
+ user.setUsername(securityContext.getUserPrincipal().getName());
+ user.setRoles(roleList);
+ return Response.ok(user).build();
}
}