You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2021/10/03 18:36:33 UTC
[incubator-streampipes] 01/03: [STREAMPIPES-437] Add initial
authorization support
This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch STREAMPIPES-426
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git
commit 7c233106458466299945b4e4e81f92b1d942d69c
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Sun Oct 3 18:00:31 2021 +0200
[STREAMPIPES-437] Add initial authorization support
---
.../client/user/JwtAuthenticationResponse.java | 17 +--
.../apache/streampipes/model/client/user/Role.java | 15 ++-
.../streampipes/model/client/user/UserAccount.java | 4 +-
.../streampipes/model/client/user/UserInfo.java | 7 +-
.../setup/UserRegistrationInstallationStep.java | 3 +-
.../streampipes/rest/impl/Authentication.java | 28 ++---
.../streampipes/security/jwt/JwtTokenUtils.java | 20 +++-
.../user/management/jwt/JwtTokenProvider.java | 37 ++++++-
ui/deployment/app-routing.module.mst | 7 +-
ui/deployment/base-navigation.component.mst | 95 +++++++++-------
ui/deployment/home.service.mst | 12 +-
ui/deployment/modules.yml | 11 +-
ui/deployment/prebuild.js | 1 +
.../src/app/_enums/page-name.enum.ts | 24 ++--
.../src/app/_enums/user-role.enum.ts | 20 ++--
.../src/app/_guards/page-auth.can-active.guard.ts | 22 ++--
ui/src/app/app-routing.module.ts | 23 ++--
.../core/components/base-navigation.component.ts | 123 +++++++++++++--------
.../core/components/iconbar/iconbar.component.html | 26 +++--
.../core/components/iconbar/iconbar.component.ts | 4 +-
.../core/components/toolbar/toolbar.component.ts | 15 +--
ui/src/app/home/home.component.ts | 2 +-
ui/src/app/services/auth.service.ts | 75 ++++++++++++-
23 files changed, 392 insertions(+), 199 deletions(-)
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/JwtAuthenticationResponse.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/JwtAuthenticationResponse.java
index f3d1bce..ace0e87 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/JwtAuthenticationResponse.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/JwtAuthenticationResponse.java
@@ -3,17 +3,13 @@ package org.apache.streampipes.model.client.user;
public class JwtAuthenticationResponse {
private String accessToken;
- private UserInfo userInfo;
- public static JwtAuthenticationResponse from(String accessToken,
- UserInfo userInfo) {
- return new JwtAuthenticationResponse(accessToken, userInfo);
+ public static JwtAuthenticationResponse from(String accessToken) {
+ return new JwtAuthenticationResponse(accessToken);
}
- private JwtAuthenticationResponse(String accessToken,
- UserInfo userInfo) {
+ private JwtAuthenticationResponse(String accessToken) {
this.accessToken = accessToken;
- this.userInfo = userInfo;
}
public String getAccessToken() {
@@ -24,11 +20,4 @@ public class JwtAuthenticationResponse {
this.accessToken = accessToken;
}
- public UserInfo getUserInfo() {
- return userInfo;
- }
-
- public void setUserInfo(UserInfo userInfo) {
- this.userInfo = userInfo;
- }
}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
index da1f2d2..ca87883 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
@@ -19,11 +19,14 @@
package org.apache.streampipes.model.client.user;
public enum Role {
- SYSTEM_ADMINISTRATOR,
- MANAGER,
- OPERATOR,
- DIMENSION_OPERATOR,
- USER_DEMO,
- BUSINESS_ANALYST
+ ADMIN,
+ PIPELINE_ADMIN,
+ DASHBOARD_ADMIN,
+ DATA_EXPLORER_ADMIN,
+ CONNECT_ADMIN,
+ DASHBOARD_USER,
+ DATA_EXPLORER_USER,
+ PIPELINE_USER,
+ APP_USER
}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java
index c082dd6..c6bfd34 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
@TsModel
public class UserAccount extends Principal {
@@ -162,7 +163,6 @@ public class UserAccount extends Principal {
this.hideTutorial = hideTutorial;
}
-
public boolean isDarkMode() {
return darkMode;
}
@@ -173,7 +173,7 @@ public class UserAccount extends Principal {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
- return null;
+ return roles.stream().map(Enum::toString).map(r -> (GrantedAuthority) () -> r).collect(Collectors.toList());
}
@Override
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserInfo.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserInfo.java
index 0f8bc98..d12a1ec 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserInfo.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserInfo.java
@@ -21,6 +21,7 @@ package org.apache.streampipes.model.client.user;
import org.apache.streampipes.model.shared.annotation.TsModel;
import java.util.List;
+import java.util.Set;
@TsModel
public class UserInfo {
@@ -28,7 +29,7 @@ public class UserInfo {
private String userId;
private String displayName;
private String email;
- private List<String> roles;
+ private Set<String> roles;
private boolean showTutorial;
private boolean darkMode;
@@ -59,11 +60,11 @@ public class UserInfo {
this.email = email;
}
- public List<String> getRoles() {
+ public Set<String> getRoles() {
return roles;
}
- public void setRoles(List<String> roles) {
+ public void setRoles(Set<String> roles) {
this.roles = roles;
}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/UserRegistrationInstallationStep.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/UserRegistrationInstallationStep.java
index 90e70f5..92737b1 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/UserRegistrationInstallationStep.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/UserRegistrationInstallationStep.java
@@ -47,8 +47,7 @@ public class UserRegistrationInstallationStep extends InstallationStep {
this.initialServiceAccountName = initialServiceAccountName;
this.initialServiceAccountSecret = initialServiceAccountSecret;
roles = new HashSet<>();
- roles.add(Role.SYSTEM_ADMINISTRATOR);
- roles.add(Role.USER_DEMO);
+ roles.add(Role.ADMIN);
}
@Override
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Authentication.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Authentication.java
index fdd0824..0ac40fb 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Authentication.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Authentication.java
@@ -55,8 +55,7 @@ public class Authentication extends AbstractRestResource {
try {
org.springframework.security.core.Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(token.getUsername(), token.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
- JwtAuthenticationResponse tokenResp = makeJwtResponse(authentication);
- return ok(tokenResp);
+ return processAuth(authentication);
} catch (BadCredentialsException e) {
return unauthorized();
}
@@ -70,8 +69,7 @@ public class Authentication extends AbstractRestResource {
public Response doLogin() {
try {
org.springframework.security.core.Authentication auth = SecurityContextHolder.getContext().getAuthentication();
- JwtAuthenticationResponse tokenResp = makeJwtResponse(auth);
- return ok(tokenResp);
+ return processAuth(auth);
} catch (BadCredentialsException e) {
return ok(new ErrorMessage(NotificationType.LOGIN_FAILED.uiNotification()));
}
@@ -94,18 +92,20 @@ public class Authentication extends AbstractRestResource {
}
}
+ private Response processAuth(org.springframework.security.core.Authentication auth) {
+ Principal principal = (Principal) auth.getPrincipal();
+ if (principal instanceof UserAccount) {
+ JwtAuthenticationResponse tokenResp = makeJwtResponse(auth);
+ return ok(tokenResp);
+ } else {
+ throw new BadCredentialsException("Could not create auth token");
+ }
+ }
+
private JwtAuthenticationResponse makeJwtResponse(org.springframework.security.core.Authentication auth) {
String jwt = new JwtTokenProvider().createToken(auth);
- UserAccount localUser = (UserAccount) auth.getPrincipal();
- return JwtAuthenticationResponse.from(jwt, toUserInfo(localUser));
+ return JwtAuthenticationResponse.from(jwt);
}
- private UserInfo toUserInfo(UserAccount localUser) {
- UserInfo userInfo = new UserInfo();
- userInfo.setUserId("id");
- userInfo.setEmail(localUser.getEmail());
- userInfo.setDisplayName(localUser.getUsername());
- userInfo.setShowTutorial(!localUser.isHideTutorial());
- return userInfo;
- }
+
}
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
index 5efe10a..beafb4b 100644
--- a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
+import java.util.Map;
public class JwtTokenUtils {
@@ -35,6 +36,23 @@ public class JwtTokenUtils {
String tokenSecret,
Date expirationDate) {
+ return prepareJwtToken(subject, tokenSecret, expirationDate).compact();
+
+ }
+
+ public static String makeJwtToken(String subject,
+ String tokenSecret,
+ Map<String, Object> claims,
+ Date expirationDate) {
+
+ JwtBuilder builder = prepareJwtToken(subject, tokenSecret, expirationDate);
+
+ return builder.addClaims(claims).compact();
+ }
+
+ private static JwtBuilder prepareJwtToken(String subject,
+ String tokenSecret,
+ Date expirationDate) {
SecretKey key = Keys.hmacShaKeyFor(tokenSecret.getBytes(StandardCharsets.UTF_8));
return Jwts
@@ -42,7 +60,7 @@ public class JwtTokenUtils {
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(expirationDate)
- .signWith(key).compact();
+ .signWith(key);
}
public static String getUserIdFromToken(String tokenSecret,
diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
index e22f51b..3dc0857 100644
--- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
+++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
@@ -3,13 +3,22 @@ package org.apache.streampipes.user.management.jwt;
import org.apache.streampipes.config.backend.BackendConfig;
import org.apache.streampipes.config.backend.model.LocalAuthConfig;
import org.apache.streampipes.model.client.user.Principal;
+import org.apache.streampipes.model.client.user.UserAccount;
+import org.apache.streampipes.model.client.user.UserInfo;
import org.apache.streampipes.security.jwt.JwtTokenUtils;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
public class JwtTokenProvider {
+ public static final String CLAIM_USER = "user";
+
private BackendConfig config;
public JwtTokenProvider() {
@@ -18,9 +27,24 @@ public class JwtTokenProvider {
public String createToken(Authentication authentication) {
Principal userPrincipal = (Principal) authentication.getPrincipal();
+ Set<String> roles = authentication
+ .getAuthorities()
+ .stream()
+ .map(GrantedAuthority::getAuthority)
+ .collect(Collectors.toSet());
+
Date tokenExpirationDate = makeExpirationDate();
+ Map<String, Object> claims = makeClaims(userPrincipal, roles);
- return JwtTokenUtils.makeJwtToken(userPrincipal.getPrincipalName(), tokenSecret(), tokenExpirationDate);
+ return JwtTokenUtils.makeJwtToken(userPrincipal.getPrincipalName(), tokenSecret(), claims, tokenExpirationDate);
+ }
+
+ private Map<String, Object> makeClaims(Principal principal,
+ Set<String> roles) {
+ Map<String, Object> claims = new HashMap<>();
+ claims.put(CLAIM_USER, toUserInfo((UserAccount) principal, roles));
+
+ return claims;
}
public String getUserIdFromToken(String token) {
@@ -48,4 +72,15 @@ public class JwtTokenProvider {
Date now = new Date();
return new Date(now.getTime() + authConfig().getTokenExpirationTimeMillis());
}
+
+ private UserInfo toUserInfo(UserAccount localUser,
+ Set<String> roles) {
+ UserInfo userInfo = new UserInfo();
+ userInfo.setUserId("id");
+ userInfo.setEmail(localUser.getEmail());
+ userInfo.setDisplayName(localUser.getUsername());
+ userInfo.setShowTutorial(!localUser.isHideTutorial());
+ userInfo.setRoles(roles);
+ return userInfo;
+ }
}
diff --git a/ui/deployment/app-routing.module.mst b/ui/deployment/app-routing.module.mst
index 4ddc5e2..b313701 100644
--- a/ui/deployment/app-routing.module.mst
+++ b/ui/deployment/app-routing.module.mst
@@ -32,6 +32,8 @@ import {InfoComponent} from "./info/info.component";
import {NotificationsComponent} from "./notifications/notifications.component";
import {ProfileComponent} from "./profile/profile.component";
import {ApidocsComponent} from "./apidocs/apidocs.component";
+import { PageName } from './_enums/page-name.enum';
+import { PageAuthGuard } from './_guards/page-auth.can-active.guard';
{{#modulesActive}}
{{#ng5}}
@@ -50,14 +52,14 @@ const routes: Routes = [
{ path: '', component: HomeComponent, canActivate: [ConfiguredCanActivateGuard] },
{{#modulesActive}}
{{#ng5}}
- { path: '{{{link}}}', component: {{{ng5_component}}} },
+ { path: '{{{link}}}', component: {{{ng5_component}}}, data: { authPageNames: [{{{pageNames}}}]}},
{{/ng5}}
{{/modulesActive}}
{ path: 'notifications', component: NotificationsComponent },
{ path: 'info', component: InfoComponent },
{ path: 'pipeline-details', component: PipelineDetailsComponent },
{ path: 'profile', component: ProfileComponent},
- ], canActivateChild: [AuthCanActivateChildrenGuard] }
+ ], canActivateChild: [AuthCanActivateChildrenGuard, PageAuthGuard] }
];
@NgModule({
@@ -67,6 +69,7 @@ const routes: Routes = [
AuthCanActivateChildrenGuard,
AlreadyConfiguredCanActivateGuard,
ConfiguredCanActivateGuard,
+ PageAuthGuard
]
})
export class AppRoutingModule { }
diff --git a/ui/deployment/base-navigation.component.mst b/ui/deployment/base-navigation.component.mst
index 457c9cf..0ba3d1e 100644
--- a/ui/deployment/base-navigation.component.mst
+++ b/ui/deployment/base-navigation.component.mst
@@ -18,6 +18,8 @@
import {NavigationEnd, Router} from "@angular/router";
+import { PageName } from '../../_enums/page-name.enum';
+import { AuthService } from '../../services/auth.service';
export abstract class BaseNavigationComponent {
@@ -30,14 +32,18 @@ export abstract class BaseNavigationComponent {
{
link: '',
title: 'Home',
- icon: 'home'
+ icon: 'home',
+ pagesNames: [PageName.HOME],
+ visible: false
},
{{#modulesActive}}
{{^admin}}
{
link: '{{{link}}}',
title: '{{{title}}}',
- icon: '{{{icon}}}'
+ icon: '{{{icon}}}',
+ pageNames: [{{{pageNames}}}],
+ visible: false
},
{{/admin}}
{{/modulesActive}}
@@ -49,54 +55,65 @@ export abstract class BaseNavigationComponent {
{
link: '{{{link}}}',
title: '{{{title}}}',
- icon: '{{{icon}}}'
+ icon: '{{{icon}}}',
+ pageNames: [{{{pageNames}}}],
+ visible: false
},
{{/admin}}
{{/modulesActive}}
];
- constructor(protected Router: Router) {
+ constructor(protected authService: AuthService,
+ protected router: Router) {
- }
+ }
- onInit() {
- this.activePage = this.Router.url.replace("/", "");
- this.activePageName = this.getPageTitle(this.activePage);
- this.Router.events.subscribe(event => {
- if (event instanceof NavigationEnd) {
- this.activePage = event.url.replace("/", "");
- this.activePageName = this.getPageTitle(this.activePage);
- }
- });
- }
+ onInit() {
+ this.authService.user$.subscribe(user => {
+ this.menu.forEach(m => m.visible = this.isNavItemVisible(m.pageNames));
+ this.admin.forEach(m => m.visible = this.isNavItemVisible(m.pageNames));
+ });
+ this.activePage = this.router.url.replace('/', '');
+ this.activePageName = this.getPageTitle(this.activePage);
+ this.router.events.subscribe(event => {
+ if (event instanceof NavigationEnd) {
+ this.activePage = event.url.replace('/', '');
+ this.activePageName = this.getPageTitle(this.activePage);
+ }
+ });
+ }
- getActivePage() {
- return this.activePage;
- }
+ getActivePage() {
+ return this.activePage;
+ }
+
+ getPageTitle(path) {
+ const allMenuItems = this.menu.concat(this.admin);
+ let currentTitle = 'StreamPipes';
+ allMenuItems.forEach(m => {
+ if (m.link === path) {
+ currentTitle = m.title;
+ }
+ });
+ if (path === 'pipeline-details') {
+ currentTitle = 'Pipeline Details';
+ }
+ return currentTitle;
+ }
- getPageTitle(path) {
- var allMenuItems = this.menu.concat(this.admin);
- var currentTitle = 'StreamPipes';
- allMenuItems.forEach(m => {
- if (m.link === path) {
- currentTitle = m.title;
+ go(path, payload?) {
+ if (payload === undefined) {
+ this.router.navigateByUrl(path);
+ this.activePage = path;
+ } else {
+ this.router.navigateByUrl(path, payload);
+ this.activePage = path;
}
- });
- if (path == 'pipeline-details') {
- currentTitle = 'Pipeline Details';
+ this.activePageName = this.getPageTitle(this.activePage);
}
- return currentTitle;
- }
- go(path, payload?) {
- if (payload === undefined) {
- this.Router.navigateByUrl(path);
- this.activePage = path;
- } else {
- this.Router.navigateByUrl(path, payload);
- this.activePage = path;
+ public isNavItemVisible(pageNames?: PageName[]): boolean {
+ return this.authService.isAnyAccessGranted(pageNames);
}
- this.activePageName = this.getPageTitle(this.activePage);
- };
-}
\ No newline at end of file
+}
diff --git a/ui/deployment/home.service.mst b/ui/deployment/home.service.mst
index eab84e7..8d69d72 100644
--- a/ui/deployment/home.service.mst
+++ b/ui/deployment/home.service.mst
@@ -17,11 +17,17 @@
*/
import { Injectable } from '@angular/core';
+import { PageName } from '../_enums/page-name.enum';
+import { AuthService } from '../services/auth.service';
@Injectable()
export class HomeService {
- constructor() {
+ constructor(private authService: AuthService) {
+ }
+
+ getFilteredServiceLinks() {
+ return this.getServiceLinks().filter(s => this.authService.isAnyAccessGranted(s.pageNames));
}
getServiceLinks() {
@@ -32,6 +38,7 @@ export class HomeService {
description: "{{description}}",
imageUrl: "{{{homeImage}}}",
icon: "{{{icon}}}",
+ pageNames: [{{{pageNames}}}],
link: {
newWindow: false,
value: "{{{link}}}"
@@ -43,6 +50,7 @@ export class HomeService {
description: "The notification module lets you view all notifications generated by pipelines.",
imageUrl: "assets/img/home/notifications.png",
icon: "chat",
+ pageNames: [PageName.NOTIFICATIONS],
link: {
newWindow: false,
value: "notifications"
@@ -50,4 +58,4 @@ export class HomeService {
}
];
}
-}
\ No newline at end of file
+}
diff --git a/ui/deployment/modules.yml b/ui/deployment/modules.yml
index 74f1189..63e1b52 100644
--- a/ui/deployment/modules.yml
+++ b/ui/deployment/modules.yml
@@ -28,6 +28,7 @@ spEditor:
icon: 'dashboard'
homeImage: '/assets/img/home/editor.png'
admin: false
+ pageNames: 'PageName.PIPELINE_EDITOR'
spConnect:
ng5: True
ng1_templateUrl: ''
@@ -42,6 +43,7 @@ spConnect:
icon: 'power'
homeImage: '/assets/img/home/editor.png'
admin: False
+ pageNames: 'PageName.CONNECT'
spPipelines:
ng5: True
ng1_templateUrl: ''
@@ -56,6 +58,7 @@ spPipelines:
icon: 'play_arrow'
homeImage: '/assets/img/home/pipelines.png'
admin: False
+ pageNames: 'PageName.PIPELINE_OVERVIEW'
spSensors:
ng5: False
ng1_templateUrl: '../sensors/sensors.html'
@@ -83,6 +86,7 @@ spAdd:
icon: 'cloud_download'
homeImage: '/assets/img/home/add.png'
admin: True
+ pageNames: 'PageName.INSTALL_PIPELINE_ELEMENTS'
spConfiguration:
ng5: True
ng1_templateUrl: ''
@@ -97,6 +101,7 @@ spConfiguration:
description: 'In the configuration module, basic StreamPipes settings and services can be configured.'
icon: 'settings'
homeImage: '/assets/img/home/configuration.png'
+ pageNames: 'PageName.SETTINGS'
admin: True
spAppOverview:
ng5: True
@@ -113,6 +118,7 @@ spAppOverview:
icon: 'apps'
homeImage: '/assets/img/home/configuration.png'
admin: false
+ pageNames: 'PageName.APPS'
spDashboard:
ng5: True
ng1_templateUrl: ''
@@ -128,6 +134,7 @@ spDashboard:
icon: 'insert_chart'
homeImage: '/assets/img/home/configuration.png'
admin: false
+ pageNames: 'PageName.DASHBOARD'
spDataExplorer:
ng5: True
ng1_templateUrl: ''
@@ -143,6 +150,7 @@ spDataExplorer:
icon: 'search'
homeImage: '/assets/img/home/configuration.png'
admin: false
+ pageNames: 'PageName.DATA_EXPLORER'
spFiles:
ng5: True
ng1_templateUrl: ''
@@ -157,4 +165,5 @@ spFiles:
description: 'The file management module lets you upload and manage files that are used by adapters or pipeline elements.'
icon: 'folder'
homeImage: '/assets/img/home/configuration.png'
- admin: true
\ No newline at end of file
+ admin: true
+ pageNames: 'PageName.FILE_UPLOAD'
diff --git a/ui/deployment/prebuild.js b/ui/deployment/prebuild.js
index fcde036..87b0f46 100644
--- a/ui/deployment/prebuild.js
+++ b/ui/deployment/prebuild.js
@@ -69,6 +69,7 @@ for (let module of config.modules) {
ng5_componentPath: modules[module]['ng5_componentPath'],
path: modules[module]['path'],
link: modules[module]['link'],
+ pageNames: modules[module]['pageNames'],
url: modules[module]['url'],
title: modules[module]['title'],
icon: modules[module]['icon'],
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java b/ui/src/app/_enums/page-name.enum.ts
similarity index 78%
copy from streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
copy to ui/src/app/_enums/page-name.enum.ts
index da1f2d2..472780a 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
+++ b/ui/src/app/_enums/page-name.enum.ts
@@ -16,14 +16,18 @@
*
*/
-package org.apache.streampipes.model.client.user;
-
-public enum Role {
- SYSTEM_ADMINISTRATOR,
- MANAGER,
- OPERATOR,
- DIMENSION_OPERATOR,
- USER_DEMO,
- BUSINESS_ANALYST
-
+export enum PageName {
+ HOME,
+ PIPELINE_EDITOR,
+ CONNECT,
+ PIPELINE_OVERVIEW,
+ PIPELINE_DETAILS,
+ DASHBOARD,
+ DATA_EXPLORER,
+ APPS,
+ NOTIFICATIONS,
+ INSTALL_PIPELINE_ELEMENTS,
+ FILE_UPLOAD,
+ PROFILE,
+ SETTINGS,
}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java b/ui/src/app/_enums/user-role.enum.ts
similarity index 82%
copy from streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
copy to ui/src/app/_enums/user-role.enum.ts
index da1f2d2..681f625 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
+++ b/ui/src/app/_enums/user-role.enum.ts
@@ -16,14 +16,14 @@
*
*/
-package org.apache.streampipes.model.client.user;
-
-public enum Role {
- SYSTEM_ADMINISTRATOR,
- MANAGER,
- OPERATOR,
- DIMENSION_OPERATOR,
- USER_DEMO,
- BUSINESS_ANALYST
-
+export enum UserRole {
+ ADMIN,
+ PIPELINE_ADMIN,
+ DASHBOARD_ADMIN,
+ DATA_EXPLORER_ADMIN,
+ CONNECT_ADMIN,
+ DASHBOARD_USER,
+ DATA_EXPLORER_USER,
+ PIPELINE_USER,
+ APP_USER
}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java b/ui/src/app/_guards/page-auth.can-active.guard.ts
similarity index 56%
copy from streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
copy to ui/src/app/_guards/page-auth.can-active.guard.ts
index da1f2d2..0fe5f05 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
+++ b/ui/src/app/_guards/page-auth.can-active.guard.ts
@@ -16,14 +16,20 @@
*
*/
-package org.apache.streampipes.model.client.user;
+import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router } from '@angular/router';
+import { AuthService } from '../services/auth.service';
+import { PageName } from '../_enums/page-name.enum';
+import { Injectable } from '@angular/core';
-public enum Role {
- SYSTEM_ADMINISTRATOR,
- MANAGER,
- OPERATOR,
- DIMENSION_OPERATOR,
- USER_DEMO,
- BUSINESS_ANALYST
+@Injectable()
+export class PageAuthGuard implements CanActivateChild {
+ constructor(private router: Router,
+ private authService: AuthService) {}
+
+ canActivateChild(activatedRouteSnapshot: ActivatedRouteSnapshot): boolean {
+ const pageNames: PageName[] = activatedRouteSnapshot.data.authPageNames;
+
+ return this.authService.isAnyAccessGranted(pageNames);
+ }
}
diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts
index 251238f..b9d63bb 100644
--- a/ui/src/app/app-routing.module.ts
+++ b/ui/src/app/app-routing.module.ts
@@ -32,6 +32,8 @@ import {InfoComponent} from "./info/info.component";
import {NotificationsComponent} from "./notifications/notifications.component";
import {ProfileComponent} from "./profile/profile.component";
import {ApidocsComponent} from "./apidocs/apidocs.component";
+import { PageName } from './_enums/page-name.enum';
+import { PageAuthGuard } from './_guards/page-auth.can-active.guard';
import { EditorComponent } from './editor/editor.component';
import { PipelinesComponent } from './pipelines/pipelines.component';
@@ -52,20 +54,20 @@ const routes: Routes = [
{ path: 'standalone/:dashboardId', component: StandaloneDashboardComponent },
{ path: '', component: StreampipesComponent, children: [
{ path: '', component: HomeComponent, canActivate: [ConfiguredCanActivateGuard] },
- { path: 'editor', component: EditorComponent },
- { path: 'pipelines', component: PipelinesComponent },
- { path: 'connect', component: ConnectComponent },
- { path: 'dashboard', component: DashboardComponent },
- { path: 'dataexplorer', component: DataExplorerComponent },
- { path: 'app-overview', component: AppOverviewComponent },
- { path: 'add', component: AddComponent },
- { path: 'files', component: FilesComponent },
- { path: 'configuration', component: ConfigurationComponent },
+ { path: 'editor', component: EditorComponent, data: { authPageNames: [PageName.PIPELINE_EDITOR]}},
+ { path: 'pipelines', component: PipelinesComponent, data: { authPageNames: [PageName.PIPELINE_OVERVIEW]}},
+ { path: 'connect', component: ConnectComponent, data: { authPageNames: [PageName.CONNECT]}},
+ { path: 'dashboard', component: DashboardComponent, data: { authPageNames: [PageName.DASHBOARD]}},
+ { path: 'dataexplorer', component: DataExplorerComponent, data: { authPageNames: [PageName.DATA_EXPLORER]}},
+ { path: 'app-overview', component: AppOverviewComponent, data: { authPageNames: [PageName.APPS]}},
+ { path: 'add', component: AddComponent, data: { authPageNames: [PageName.INSTALL_PIPELINE_ELEMENTS]}},
+ { path: 'files', component: FilesComponent, data: { authPageNames: [PageName.FILE_UPLOAD]}},
+ { path: 'configuration', component: ConfigurationComponent, data: { authPageNames: [PageName.SETTINGS]}},
{ path: 'notifications', component: NotificationsComponent },
{ path: 'info', component: InfoComponent },
{ path: 'pipeline-details', component: PipelineDetailsComponent },
{ path: 'profile', component: ProfileComponent},
- ], canActivateChild: [AuthCanActivateChildrenGuard] }
+ ], canActivateChild: [AuthCanActivateChildrenGuard, PageAuthGuard] }
];
@NgModule({
@@ -75,6 +77,7 @@ const routes: Routes = [
AuthCanActivateChildrenGuard,
AlreadyConfiguredCanActivateGuard,
ConfiguredCanActivateGuard,
+ PageAuthGuard
]
})
export class AppRoutingModule { }
diff --git a/ui/src/app/core/components/base-navigation.component.ts b/ui/src/app/core/components/base-navigation.component.ts
index e1a9ab0..c1301f1 100644
--- a/ui/src/app/core/components/base-navigation.component.ts
+++ b/ui/src/app/core/components/base-navigation.component.ts
@@ -18,6 +18,8 @@
import {NavigationEnd, Router} from "@angular/router";
+import { PageName } from '../../_enums/page-name.enum';
+import { AuthService } from '../../services/auth.service';
export abstract class BaseNavigationComponent {
@@ -30,37 +32,51 @@ export abstract class BaseNavigationComponent {
{
link: '',
title: 'Home',
- icon: 'home'
+ icon: 'home',
+ pagesNames: [PageName.HOME],
+ visible: false
},
{
link: 'editor',
title: 'Pipeline Editor',
- icon: 'dashboard'
+ icon: 'dashboard',
+ pageNames: [PageName.PIPELINE_EDITOR],
+ visible: false
},
{
link: 'pipelines',
title: 'Pipelines',
- icon: 'play_arrow'
+ icon: 'play_arrow',
+ pageNames: [PageName.PIPELINE_OVERVIEW],
+ visible: false
},
{
link: 'connect',
title: 'Connect',
- icon: 'power'
+ icon: 'power',
+ pageNames: [PageName.CONNECT],
+ visible: false
},
{
link: 'dashboard',
title: 'Dashboard',
- icon: 'insert_chart'
+ icon: 'insert_chart',
+ pageNames: [PageName.DASHBOARD],
+ visible: false
},
{
link: 'dataexplorer',
title: 'Data Explorer',
- icon: 'search'
+ icon: 'search',
+ pageNames: [PageName.DATA_EXPLORER],
+ visible: false
},
{
link: 'app-overview',
title: 'Apps',
- icon: 'apps'
+ icon: 'apps',
+ pageNames: [PageName.APPS],
+ visible: false
},
];
@@ -68,62 +84,77 @@ export abstract class BaseNavigationComponent {
{
link: 'add',
title: 'Install Pipeline Elements',
- icon: 'cloud_download'
+ icon: 'cloud_download',
+ pageNames: [PageName.INSTALL_PIPELINE_ELEMENTS],
+ visible: false
},
{
link: 'files',
title: 'File Management',
- icon: 'folder'
+ icon: 'folder',
+ pageNames: [PageName.FILE_UPLOAD],
+ visible: false
},
{
link: 'configuration',
title: 'Configuration',
- icon: 'settings'
+ icon: 'settings',
+ pageNames: [PageName.SETTINGS],
+ visible: false
},
];
- constructor(protected Router: Router) {
+ constructor(protected authService: AuthService,
+ protected router: Router) {
- }
+ }
- onInit() {
- this.activePage = this.Router.url.replace("/", "");
- this.activePageName = this.getPageTitle(this.activePage);
- this.Router.events.subscribe(event => {
- if (event instanceof NavigationEnd) {
- this.activePage = event.url.replace("/", "");
- this.activePageName = this.getPageTitle(this.activePage);
- }
- });
- }
+ onInit() {
+ this.authService.user$.subscribe(user => {
+ this.menu.forEach(m => m.visible = this.isNavItemVisible(m.pageNames));
+ this.admin.forEach(m => m.visible = this.isNavItemVisible(m.pageNames));
+ });
+ this.activePage = this.router.url.replace('/', '');
+ this.activePageName = this.getPageTitle(this.activePage);
+ this.router.events.subscribe(event => {
+ if (event instanceof NavigationEnd) {
+ this.activePage = event.url.replace('/', '');
+ this.activePageName = this.getPageTitle(this.activePage);
+ }
+ });
+ }
- getActivePage() {
- return this.activePage;
- }
+ getActivePage() {
+ return this.activePage;
+ }
+
+ getPageTitle(path) {
+ const allMenuItems = this.menu.concat(this.admin);
+ let currentTitle = 'StreamPipes';
+ allMenuItems.forEach(m => {
+ if (m.link === path) {
+ currentTitle = m.title;
+ }
+ });
+ if (path === 'pipeline-details') {
+ currentTitle = 'Pipeline Details';
+ }
+ return currentTitle;
+ }
- getPageTitle(path) {
- var allMenuItems = this.menu.concat(this.admin);
- var currentTitle = 'StreamPipes';
- allMenuItems.forEach(m => {
- if (m.link === path) {
- currentTitle = m.title;
+ go(path, payload?) {
+ if (payload === undefined) {
+ this.router.navigateByUrl(path);
+ this.activePage = path;
+ } else {
+ this.router.navigateByUrl(path, payload);
+ this.activePage = path;
}
- });
- if (path == 'pipeline-details') {
- currentTitle = 'Pipeline Details';
+ this.activePageName = this.getPageTitle(this.activePage);
}
- return currentTitle;
- }
- go(path, payload?) {
- if (payload === undefined) {
- this.Router.navigateByUrl(path);
- this.activePage = path;
- } else {
- this.Router.navigateByUrl(path, payload);
- this.activePage = path;
+ public isNavItemVisible(pageNames?: PageName[]): boolean {
+ return this.authService.isAnyAccessGranted(pageNames);
}
- this.activePageName = this.getPageTitle(this.activePage);
- };
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/core/components/iconbar/iconbar.component.html b/ui/src/app/core/components/iconbar/iconbar.component.html
index b5a74ee..38ffe54 100644
--- a/ui/src/app/core/components/iconbar/iconbar.component.html
+++ b/ui/src/app/core/components/iconbar/iconbar.component.html
@@ -17,14 +17,15 @@
-->
<div style="padding-top:0px;" fxFlex="100" fxLayout="column">
- <div [ngClass]="item.link === activePage ? 'sp-navbar-item-selected': 'sp-navbar-item'"
- *ngFor="let item of menu" style="min-width:0px;padding:0px;padding-top:5px;padding-bottom:5px;">
+ <div *ngFor="let item of menu">
+ <div *ngIf="item.visible" style="min-width:0px;padding:0px;padding-top:5px;padding-bottom:5px;" [ngClass]="item.link === activePage ? 'sp-navbar-item-selected': 'sp-navbar-item'">
<button mat-button mat-icon-button class="button-margin-iconbar iconbar-size" (click)="go(item.link)"
- matTooltip="{{item.title}}" matTooltipPosition="right">
+ matTooltip="{{item.title}}" matTooltipPosition="right" >
<mat-icon [ngClass]="item.link === activePage ? 'sp-navbar-icon-selected' : 'sp-navbar-icon'">
{{item.icon}}
</mat-icon>
</button>
+ </div>
</div>
<div [ngClass]="'notifications' === activePage ? 'sp-navbar-item-selected' : 'sp-navbar-item'"
style="padding-top:5px;padding-bottom:5px;">
@@ -38,13 +39,16 @@
</button>
</div>
<mat-divider style="border-top-color:var(--color-navigation-text);"></mat-divider>
- <div [ngClass]="item.link === activePage ? 'sp-navbar-item-selected' : 'sp-navbar-item'"
- *ngFor="let item of admin" style="padding-top:5px;padding-bottom:5px;">
- <button mat-button mat-icon-button class="md-icon-button button-margin-iconbar iconbar-size"
- (click)="go(item.link)"
- matTooltip="{{item.title}}">
- <mat-icon
- [ngClass]="item.link === activePage ?'sp-navbar-icon-selected' : 'sp-navbar-icon'">{{item.icon}}</mat-icon>
- </button>
+ <div *ngFor="let item of admin" >
+ <div [ngClass]="item.link === activePage ? 'sp-navbar-item-selected' : 'sp-navbar-item'"
+ style="padding-top:5px;padding-bottom:5px;"
+ *ngIf="item.visible">
+ <button mat-button mat-icon-button class="md-icon-button button-margin-iconbar iconbar-size"
+ (click)="go(item.link)"
+ matTooltip="{{item.title}}" >
+ <mat-icon
+ [ngClass]="item.link === activePage ?'sp-navbar-icon-selected' : 'sp-navbar-icon'">{{item.icon}}</mat-icon>
+ </button>
+ </div>
</div>
</div>
diff --git a/ui/src/app/core/components/iconbar/iconbar.component.ts b/ui/src/app/core/components/iconbar/iconbar.component.ts
index 8e20f84..5c818b1 100644
--- a/ui/src/app/core/components/iconbar/iconbar.component.ts
+++ b/ui/src/app/core/components/iconbar/iconbar.component.ts
@@ -34,9 +34,9 @@ export class IconbarComponent extends BaseNavigationComponent implements OnInit
unreadNotifications = 0;
constructor(router: Router,
- private authService: AuthService,
+ authService: AuthService,
public notificationCountService: NotificationCountService) {
- super(router);
+ super(authService, router);
}
ngOnInit(): void {
diff --git a/ui/src/app/core/components/toolbar/toolbar.component.ts b/ui/src/app/core/components/toolbar/toolbar.component.ts
index a8604cb..3cd5c33 100644
--- a/ui/src/app/core/components/toolbar/toolbar.component.ts
+++ b/ui/src/app/core/components/toolbar/toolbar.component.ts
@@ -47,8 +47,8 @@ export class ToolbarComponent extends BaseNavigationComponent implements OnInit
private profileService: ProfileService,
private restApi: RestApi,
private overlay: OverlayContainer,
- private authService: AuthService) {
- super(router);
+ authService: AuthService) {
+ super(authService, router);
}
ngOnInit(): void {
@@ -89,23 +89,18 @@ export class ToolbarComponent extends BaseNavigationComponent implements OnInit
}
openInfo() {
- this.Router.navigate(['info']);
+ this.router.navigate(['info']);
this.activePage = 'Info';
}
openProfile() {
- this.Router.navigate(['profile']);
+ this.router.navigate(['profile']);
this.activePage = 'Profile';
}
logout() {
this.authService.logout();
- this.Router.navigate(['login']);
- // this.RestApi.logout().subscribe(() => {
- // this.AuthStatusService.user = undefined;
- // this.AuthStatusService.authenticated = false;
- // this.Router.navigateByUrl('login');
- // });
+ this.router.navigate(['login']);
}
getVersion() {
diff --git a/ui/src/app/home/home.component.ts b/ui/src/app/home/home.component.ts
index a00a516..e0fba3a 100644
--- a/ui/src/app/home/home.component.ts
+++ b/ui/src/app/home/home.component.ts
@@ -34,7 +34,7 @@ export class HomeComponent {
private sanitizer: DomSanitizer,
private router: Router,
public appConstants: AppConstants) {
- this.serviceLinks = this.homeService.getServiceLinks();
+ this.serviceLinks = this.homeService.getFilteredServiceLinks();
}
getBackground(url) {
diff --git a/ui/src/app/services/auth.service.ts b/ui/src/app/services/auth.service.ts
index 209747b..cf2e33c 100644
--- a/ui/src/app/services/auth.service.ts
+++ b/ui/src/app/services/auth.service.ts
@@ -22,9 +22,11 @@ import { BehaviorSubject, Observable, timer } from 'rxjs';
import { JwtHelperService } from '@auth0/angular-jwt';
import { JwtTokenStorageService } from './jwt-token-storage.service';
import { UserInfo } from '../core-model/gen/streampipes-model-client';
-import { filter, map, switchMap, tap } from 'rxjs/operators';
+import { filter, map, switchMap } from 'rxjs/operators';
import { Router } from '@angular/router';
import { LoginService } from '../login/services/login.service';
+import { PageName } from '../_enums/page-name.enum';
+import { UserRole } from '../_enums/user-role.enum';
@Injectable()
export class AuthService {
@@ -51,10 +53,13 @@ export class AuthService {
}
public login(data) {
+ const jwtHelper: JwtHelperService = new JwtHelperService({});
+ const decodedToken = jwtHelper.decodeToken(data.accessToken);
+ console.log(decodedToken);
this.tokenStorage.saveToken(data.accessToken);
- this.tokenStorage.saveUser(data.userInfo);
+ this.tokenStorage.saveUser(decodedToken.user);
this.authToken$.next(data.accessToken);
- this.user$.next(data.userInfo);
+ this.user$.next(decodedToken.user);
}
public logout() {
@@ -63,7 +68,7 @@ export class AuthService {
}
public getCurrentUser(): UserInfo {
- return this.tokenStorage.getUser();
+ return this.user$.getValue() || this.tokenStorage.getUser();
}
public authenticated(): boolean {
@@ -114,4 +119,66 @@ export class AuthService {
this.router.navigate(['login']);
});
}
+
+ getUserRoles(): string[] {
+ return this.getCurrentUser().roles;
+ }
+
+ public hasRole(role: UserRole): boolean {
+ return this.getUserRoles().includes(UserRole[role]);
+ }
+
+ public hasAnyRole(roles: UserRole[]): boolean {
+ if (Array.isArray(roles)) {
+ return roles.reduce((aggregator: false, role: UserRole) => aggregator || this.hasRole(role), false);
+ }
+
+ return false;
+ }
+
+ isAnyAccessGranted(pageNames: PageName[]): boolean {
+ if (!pageNames || pageNames.length === 0) {
+ return true;
+ }
+
+ const result = pageNames.some(pageName => this.isAccessGranted(pageName));
+ if (!result) {
+ this.router.navigate(['']);
+ }
+ console.log(pageNames);
+ console.log(result);
+ return result;
+ }
+
+ isAccessGranted(pageName: PageName) {
+ console.log(pageName);
+ console.log(this.hasRole(UserRole.ADMIN));
+ if (this.hasRole(UserRole.ADMIN)) {
+ return true;
+ }
+ switch (pageName) {
+ case PageName.HOME:
+ return true;
+ case PageName.PIPELINE_EDITOR:
+ return this.hasAnyRole([UserRole.PIPELINE_ADMIN]);
+ case PageName.PIPELINE_OVERVIEW:
+ return this.hasAnyRole([UserRole.PIPELINE_ADMIN]);
+ case PageName.CONNECT:
+ return this.hasAnyRole([UserRole.CONNECT_ADMIN]);
+ case PageName.DASHBOARD:
+ return this.hasAnyRole([UserRole.DASHBOARD_USER, UserRole.DASHBOARD_ADMIN]);
+ case PageName.DATA_EXPLORER:
+ return this.hasAnyRole([UserRole.DATA_EXPLORER_ADMIN, UserRole.DATA_EXPLORER_USER]);
+ case PageName.APPS:
+ return this.hasAnyRole([UserRole.APP_USER]);
+ case PageName.FILE_UPLOAD:
+ return this.hasAnyRole([UserRole.CONNECT_ADMIN, UserRole.PIPELINE_ADMIN]);
+ case PageName.INSTALL_PIPELINE_ELEMENTS:
+ return this.hasAnyRole([UserRole.ADMIN]);
+ case PageName.SETTINGS:
+ return this.hasAnyRole([UserRole.ADMIN]);
+ default:
+ return true;
+ }
+ }
}