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/04 20:03:03 UTC
[incubator-streampipes] branch STREAMPIPES-426 updated:
[STREAMPIPES-439] Add initial UI to manage users and roles
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
The following commit(s) were added to refs/heads/STREAMPIPES-426 by this push:
new 4eb1de9 [STREAMPIPES-439] Add initial UI to manage users and roles
4eb1de9 is described below
commit 4eb1de9d2dfcda13ae86e00018318fd42f17351d
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Mon Oct 4 22:02:52 2021 +0200
[STREAMPIPES-439] Add initial UI to manage users and roles
---
.../backend/StreamPipesResourceConfig.java | 2 +
.../streampipes/model/client/user/Group.java | 51 +++--
.../streampipes/model/client/user/Principal.java | 12 +-
.../streampipes/model/client/user/Privilege.java | 77 +++++++
.../model/client/user/ServiceAccount.java | 10 +-
.../streampipes/model/client/user/UserAccount.java | 5 +-
.../streampipes/model/client/user/UserInfo.java | 11 +-
.../manager/setup/CouchDbInstallationStep.java | 4 +-
.../setup/PipelineElementInstallationStep.java | 8 +-
.../manager/storage/UserManagementService.java | 4 +-
.../streampipes/manager/storage/UserService.java | 30 +--
.../streampipes/rest/impl/UserGroupResource.java | 67 ++++++
.../apache/streampipes/rest/impl/UserProfile.java | 76 +------
.../apache/streampipes/rest/impl/UserResource.java | 224 +++++++++++++++++++++
.../streampipes/storage/api/INoSqlStorage.java | 2 +
.../streampipes/storage/api/IUserGroupStorage.java | 5 +-
.../streampipes/storage/api/IUserStorage.java | 10 +-
.../storage/couchdb/CouchDbStorageManager.java | 5 +
.../storage/couchdb/impl/UserGroupStorageImpl.java | 58 ++++++
.../storage/couchdb/impl/UserStorage.java | 191 ++++++++++--------
.../streampipes/storage/couchdb/utils/Utils.java | 4 +
.../user/management/jwt/JwtTokenProvider.java | 4 +-
.../user/management/jwt/SpKeyResolver.java | 4 +-
ui/src/app/configuration/configuration.module.ts | 13 +-
.../messaging-configuration.component.html | 141 +++++++------
.../abstract-security-principal-config.ts | 92 +++++++++
.../edit-user-dialog.component.html | 89 ++++++++
.../edit-user-dialog.component.scss | 10 +-
.../edit-user-dialog/edit-user-dialog.component.ts | 141 +++++++++++++
.../security-configuration.component.html | 18 ++
.../security-configuration.component.ts | 3 +
.../security-service-config.component.html | 77 +++++++
.../security-service-config.component.scss | 5 +-
.../security-service-config.component.ts | 47 +++++
.../security-user-config.component.html | 94 +++++++++
.../security-user-config.component.scss | 5 +-
.../security-user-config.component.ts} | 31 ++-
.../app/core-model/gen/streampipes-model-client.ts | 30 ++-
.../split-section/split-section.component.scss | 2 +
.../core/components/toolbar/toolbar.component.ts | 2 +-
ui/src/app/platform-services/apis/user.service.ts | 70 +++++++
ui/src/app/platform-services/platform.module.ts | 4 +-
.../profile/components/basic-profile-settings.ts | 6 +-
.../general/general-profile-settings.component.ts | 6 +-
.../token/token-management-settings.component.ts | 12 +-
ui/src/app/profile/profile.service.ts | 17 +-
ui/src/app/services/auth.service.ts | 5 -
ui/src/scss/sp/widgets.scss | 14 ++
48 files changed, 1443 insertions(+), 355 deletions(-)
diff --git a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesResourceConfig.java b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesResourceConfig.java
index 14d92a1..8448996 100644
--- a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesResourceConfig.java
+++ b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesResourceConfig.java
@@ -93,6 +93,7 @@ public class StreamPipesResourceConfig extends ResourceConfig {
register(Setup.class);
register(ResetResource.class);
register(UserProfile.class);
+ register(UserResource.class);
register(Version.class);
register(PipelineElementAsset.class);
register(DataLakeDashboardResource.class);
@@ -104,6 +105,7 @@ public class StreamPipesResourceConfig extends ResourceConfig {
register(DashboardWidget.class);
register(Dashboard.class);
register(VisualizablePipelineResource.class);
+ register(UserGroupResource.class);
// Serializers
register(GsonWithIdProvider.class);
diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Group.java
similarity index 51%
copy from streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java
copy to streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Group.java
index 4f1c7f8..2a2db99 100644
--- a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Group.java
@@ -15,32 +15,53 @@
* limitations under the License.
*
*/
-package org.apache.streampipes.storage.api;
+package org.apache.streampipes.model.client.user;
-import org.apache.streampipes.model.client.user.Principal;
-import org.apache.streampipes.model.client.user.ServiceAccount;
-import org.apache.streampipes.model.client.user.UserAccount;
+import com.google.gson.annotations.SerializedName;
import java.util.List;
-public interface IUserStorage {
- List<Principal> getAllUsers();
+public class Group {
- List<UserAccount> getAllUserAccounts();
+ protected @SerializedName("_id") String groupId;
+ protected @SerializedName("_rev") String rev;
- List<ServiceAccount> getAllServiceAccounts();
+ private String groupName;
- Principal getUser(String principalName);
+ private List<Role> roles;
- UserAccount getUserAccount(String principalName);
+ public Group() {
+ }
- ServiceAccount getServiceAccount(String principalName);
+ public String getGroupId() {
+ return groupId;
+ }
- void storeUser(Principal user);
+ public void setGroupId(String groupId) {
+ this.groupId = groupId;
+ }
- void updateUser(Principal user);
+ public String getRev() {
+ return rev;
+ }
- boolean emailExists(String email);
+ public void setRev(String rev) {
+ this.rev = rev;
+ }
- boolean checkUser(String username);
+ public String getGroupName() {
+ return groupName;
+ }
+
+ public void setGroupName(String groupName) {
+ this.groupName = groupName;
+ }
+
+ public List<Role> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List<Role> roles) {
+ this.roles = roles;
+ }
}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java
index 29d20e0..a9b9b04 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java
@@ -35,7 +35,7 @@ public abstract class Principal implements UserDetails {
private boolean accountLocked;
private boolean accountExpired;
- private String principalName;
+ protected String username;
protected List<Element> ownSources;
protected List<Element> ownSepas;
@@ -135,12 +135,8 @@ public abstract class Principal implements UserDetails {
this.accountExpired = accountExpired;
}
- public String getPrincipalName() {
- return principalName;
- }
-
- public void setPrincipalName(String principalName) {
- this.principalName = principalName;
+ public void setUsername(String username) {
+ this.username = username;
}
public void setOwnSources(List<Element> ownSources) {
@@ -193,7 +189,7 @@ public abstract class Principal implements UserDetails {
@Override
public String getUsername() {
- return principalName;
+ return username;
}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Privilege.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Privilege.java
new file mode 100644
index 0000000..3bf9649
--- /dev/null
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Privilege.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.model.client.user;
+
+public enum Privilege {
+ // Pipelines
+ PRIVILEGE_CREATE_PIPELINE,
+ PRIVILEGE_READ_PIPELINE,
+ PRIVILEGE_UPDATE_PIPELINE,
+ PRIVILEGE_DELETE_PIPELINE,
+
+ // Adapters
+ PRIVILEGE_CREATE_ADAPTER,
+ PRIVILEGE_READ_ADAPTER,
+ PRIVILEGE_UPDATE_ADAPTER,
+ PRIVILEGE_DELETE_ADAPTER,
+
+ // Pipeline Elements
+ PRIVILEGE_CREATE_PIPELINE_ELEMENT,
+ PRIVILEGE_READ_PIPELINE_ELEMENT,
+ PRIVILEGE_UPDATE_PIPELINE_ELEMENT,
+ PRIVILEGE_DELETE_PIPELINE_ELEMENT,
+
+ // Dashboard
+ PRIVILEGE_CREATE_DASHBOARD,
+ PRIVILEGE_READ_DASHBOARD,
+ PRIVILEGE_UPDATE_DASHBOARD,
+ PRIVILEGE_DELETE_DASHBOARD,
+
+ // Dashboard widget
+ PRIVILEGE_CREATE_DASHBOARD_WIDGET,
+ PRIVILEGE_READ_DASHBOARD_WIDGET,
+ PRIVILEGE_UPDATE_DASHBOARD_WIDGET,
+ PRIVILEGE_DELETE_DASHBOARD_WIDGET,
+
+ // Data Explorer view
+ PRIVILEGE_CREATE_DATA_EXPLORER_VIEW,
+ PRIVILEGE_READ_DATA_EXPLORER_VIEW,
+ PRIVILEGE_UPDATE_DATA_EXPLORER_VIEW,
+ PRIVILEGE_DELETE_DATA_EXPLORER_VIEW,
+
+ // Data Explorer widget
+ PRIVILEGE_CREATE_DATA_EXPLORER_WIDGET,
+ PRIVILEGE_READ_DATA_EXPLORER_WIDGET,
+ PRIVILEGE_UPDATE_DATA_EXPLORER_WIDGET,
+ PRIVILEGE_DELETE_DATA_EXPLORER_WIDGET,
+
+ // Apps
+ PRIVILEGE_READ_APPS,
+
+ // NOTIFICATIONS
+ PRIVILEGE_READ_NOTIFICATIONS,
+
+ // FILES
+ PRIVILEGE_READ_FILES,
+ PRIVILEGE_CREATE_FILES,
+ PRIVILEGE_UPDATE_FILES,
+ PRIVILEGE_DELETE_FILES,
+
+ // Admin
+ PRIVILEGE_ADMIN
+}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ServiceAccount.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ServiceAccount.java
index 7fafdb5..3134d34 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ServiceAccount.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ServiceAccount.java
@@ -17,11 +17,14 @@
*/
package org.apache.streampipes.model.client.user;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.apache.streampipes.model.shared.annotation.TsModel;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
import java.util.Set;
+@TsModel
public class ServiceAccount extends Principal {
private String clientSecret;
@@ -30,7 +33,7 @@ public class ServiceAccount extends Principal {
String clientSecret,
Set<Role> roles) {
ServiceAccount account = new ServiceAccount();
- account.setPrincipalName(serviceAccountName);
+ account.setUsername(serviceAccountName);
account.setClientSecret(clientSecret);
account.setRoles(roles);
account.setAccountEnabled(true);
@@ -51,6 +54,7 @@ public class ServiceAccount extends Principal {
this.clientSecret = clientSecret;
}
+ @JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
@@ -61,8 +65,4 @@ public class ServiceAccount extends Principal {
return null;
}
- @Override
- public String getUsername() {
- return null;
- }
}
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 c6bfd34..c8a6396 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
@@ -18,6 +18,7 @@
package org.apache.streampipes.model.client.user;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import org.apache.streampipes.model.shared.annotation.TsModel;
import org.springframework.security.core.GrantedAuthority;
@@ -32,7 +33,6 @@ public class UserAccount extends Principal {
protected String email;
protected String fullName;
- protected String username;
protected String password;
protected List<String> preferredDataStreams;
@@ -48,7 +48,7 @@ public class UserAccount extends Principal {
String encryptedPassword,
Set<Role> roles) {
UserAccount account = new UserAccount();
- account.setPrincipalName(username);
+ account.setUsername(username);
account.setPassword(encryptedPassword);
account.setRoles(roles);
account.setAccountEnabled(true);
@@ -171,6 +171,7 @@ public class UserAccount extends Principal {
this.darkMode = darkMode;
}
+ @JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(Enum::toString).map(r -> (GrantedAuthority) () -> r).collect(Collectors.toList());
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 d12a1ec..dd12381 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
@@ -20,13 +20,12 @@ 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 {
- private String userId;
+ private String username;
private String displayName;
private String email;
private Set<String> roles;
@@ -36,12 +35,12 @@ public class UserInfo {
public UserInfo() {
}
- public String getUserId() {
- return userId;
+ public String getUsername() {
+ return username;
}
- public void setUserId(String userId) {
- this.userId = userId;
+ public void setUsername(String username) {
+ this.username = username;
}
public String getDisplayName() {
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java
index 7dbe871..3b6b803 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java
@@ -146,10 +146,10 @@ public class CouchDbInstallationStep extends InstallationStep {
Map<String, MapReduce> views = new HashMap<>();
MapReduce passwordFunction = new MapReduce();
- passwordFunction.setMap("function(doc) { if(doc.principalName && doc.principalType === 'USER_ACCOUNT' && doc.password) { emit(doc.principalName, doc.password); } }");
+ passwordFunction.setMap("function(doc) { if(doc.username && doc.principalType === 'USER_ACCOUNT' && doc.password) { emit(doc.username, doc.password); } }");
MapReduce usernameFunction = new MapReduce();
- usernameFunction.setMap("function(doc) { if(doc.principalName) { emit(doc.principalName, doc); } }");
+ usernameFunction.setMap("function(doc) { if(doc.username) { emit(doc.username, doc); } }");
MapReduce tokenFunction = new MapReduce();
tokenFunction.setMap("function(doc) { if (doc.userApiTokens) { doc.userApiTokens.forEach(function(token) { emit(token.hashedToken, doc.email); });}}");
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/PipelineElementInstallationStep.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/PipelineElementInstallationStep.java
index 67fa355..5d6fca8 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/PipelineElementInstallationStep.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/PipelineElementInstallationStep.java
@@ -30,11 +30,11 @@ import java.util.List;
public class PipelineElementInstallationStep extends InstallationStep {
private ExtensionsServiceEndpoint endpoint;
- private String principalName;
+ private String username;
- public PipelineElementInstallationStep(ExtensionsServiceEndpoint endpoint, String principalName) {
+ public PipelineElementInstallationStep(ExtensionsServiceEndpoint endpoint, String username) {
this.endpoint = endpoint;
- this.principalName = principalName;
+ this.username = username;
}
@Override
@@ -43,7 +43,7 @@ public class PipelineElementInstallationStep extends InstallationStep {
List<ExtensionsServiceEndpointItem> items = Operations.getEndpointUriContents(Collections.singletonList(endpoint));
for(ExtensionsServiceEndpointItem item : items) {
statusMessages.add(new EndpointItemParser().parseAndAddEndpointItem(item.getUri(),
- principalName, true, false));
+ username, true, false));
}
if (statusMessages.stream().allMatch(Message::isSuccess)) {
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserManagementService.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserManagementService.java
index 26cbe88..2ea3715 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserManagementService.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserManagementService.java
@@ -45,9 +45,9 @@ public class UserManagementService {
return true;
}
- public static void setHideTutorial(String principalName, boolean hideTutorial) {
+ public static void setHideTutorial(String username, boolean hideTutorial) {
IUserStorage userService = getUserStorage();
- UserAccount user = userService.getUserAccount(principalName);
+ UserAccount user = userService.getUserAccount(username);
user.setHideTutorial(hideTutorial);
userService.updateUser(user);
}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserService.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserService.java
index fbb51c2..90ff3e2 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserService.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserService.java
@@ -202,12 +202,12 @@ public class UserService {
return getUserAccount(username).getPreferredDataSinks();
}
- public List<String> getAvailableActionUris(String principalName) {
- List<String> actions = new ArrayList<>(getOwnActionUris(principalName));
+ public List<String> getAvailableActionUris(String username) {
+ List<String> actions = new ArrayList<>(getOwnActionUris(username));
userStorage
.getAllUsers()
.stream()
- .filter(u -> !(u.getPrincipalName().equals(principalName)))
+ .filter(u -> !(u.getUsername().equals(username)))
.map(u -> u.getOwnActions().stream().filter(p -> p.isPublicElement()).map(p -> p.getElementId()).collect(Collectors.toList())).forEach(actions::addAll);
return actions;
}
@@ -216,18 +216,18 @@ public class UserService {
return userStorage.getUser(username).getOwnSepas().stream().map(r -> r.getElementId()).collect(Collectors.toList());
}
- public List<String> getAvailableSepaUris(String principalName) {
- List<String> sepas = new ArrayList<>(getOwnSepaUris(principalName));
+ public List<String> getAvailableSepaUris(String username) {
+ List<String> sepas = new ArrayList<>(getOwnSepaUris(username));
userStorage
.getAllUsers()
.stream()
- .filter(u -> !(u.getPrincipalName().equals(principalName)))
+ .filter(u -> !(u.getUsername().equals(username)))
.map(u -> u.getOwnSepas().stream().filter(p -> p.isPublicElement()).map(p -> p.getElementId()).collect(Collectors.toList())).forEach(sepas::addAll);
return sepas;
}
- public List<String> getFavoriteSepaUris(String principalName) {
- return getUserAccount(principalName).getPreferredDataProcessors();
+ public List<String> getFavoriteSepaUris(String username) {
+ return getUserAccount(username).getPreferredDataProcessors();
}
public List<String> getOwnSourceUris(String email) {
@@ -239,12 +239,12 @@ public class UserService {
.collect(Collectors.toList());
}
- public List<String> getAvailableSourceUris(String principalName) {
- List<String> sources = new ArrayList<>(getOwnSepaUris(principalName));
+ public List<String> getAvailableSourceUris(String username) {
+ List<String> sources = new ArrayList<>(getOwnSepaUris(username));
userStorage
.getAllUsers()
.stream()
- .filter(u -> !(u.getPrincipalName().equals(principalName)))
+ .filter(u -> !(u.getUsername().equals(username)))
.map(u -> u.getOwnSources()
.stream()
.filter(p -> p.isPublicElement())
@@ -258,12 +258,12 @@ public class UserService {
return getUserAccount(username).getPreferredDataStreams();
}
- public UserAccount getUserAccount(String principalName) {
- return (UserAccount) getPrincipal(principalName);
+ public UserAccount getUserAccount(String username) {
+ return (UserAccount) getPrincipal(username);
}
- private Principal getPrincipal(String principalName) {
- return userStorage.getUser(principalName);
+ private Principal getPrincipal(String username) {
+ return userStorage.getUser(username);
}
private IUserStorage userStorage() {
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserGroupResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserGroupResource.java
new file mode 100644
index 0000000..f02627c
--- /dev/null
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserGroupResource.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.rest.impl;
+
+import org.apache.streampipes.model.client.user.Group;
+import org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
+import org.apache.streampipes.storage.api.IUserGroupStorage;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.Response;
+
+@Path("/v2/users/groups")
+public class UserGroupResource extends AbstractAuthGuardedRestResource {
+
+ @GET
+ public Response getAllUserGroups() {
+ return ok(getUserGroupStorage().getAll());
+ }
+
+ @POST
+ public Response addUserGroup(Group group) {
+ getUserGroupStorage().createElement(group);
+ return ok();
+ }
+
+ @PUT
+ @Path("{groupId}")
+ public Response updateUserGroup(@PathParam("groupId") String groupId,
+ Group group) {
+ if (!groupId.equals(group.getGroupId())) {
+ return badRequest();
+ } else {
+ return ok(getUserGroupStorage().updateElement(group));
+ }
+ }
+
+ @DELETE
+ @Path("{groupId}")
+ public Response deleteUserGroup(@PathParam("groupId") String groupId) {
+ Group group = getUserGroupStorage().getElementById(groupId);
+ if (group != null) {
+ getUserGroupStorage().deleteElement(group);
+ return ok();
+ } else {
+ return badRequest();
+ }
+ }
+
+ private IUserGroupStorage getUserGroupStorage() {
+ return getNoSqlStorage().getUserGroupStorage();
+ }
+}
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserProfile.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserProfile.java
index cb50c1d..d3f2de2 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserProfile.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserProfile.java
@@ -18,86 +18,12 @@
package org.apache.streampipes.rest.impl;
-import org.apache.streampipes.model.client.user.RawUserApiToken;
-import org.apache.streampipes.model.client.user.UserAccount;
-import org.apache.streampipes.model.message.Notifications;
import org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
-import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
-import org.apache.streampipes.user.management.service.TokenService;
-import javax.ws.rs.*;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.util.stream.Collectors;
+import javax.ws.rs.Path;
@Path("/v2/users/profile")
public class UserProfile extends AbstractAuthGuardedRestResource {
- @GET
- @Produces(MediaType.APPLICATION_JSON)
- public Response getUserDetails() {
- UserAccount user = getUser(getAuthenticatedUsername());
- user.setPassword("");
- if (user != null) {
- return ok(user);
- } else {
- return statusMessage(Notifications.error("User not found"));
- }
- }
-
- @Path("/appearance/mode/{darkMode}")
- @PUT
- @Produces(MediaType.APPLICATION_JSON)
- public Response updateAppearanceMode(@PathParam("darkMode") boolean darkMode) {
- String authenticatedUserId = getAuthenticatedUsername();
- if (authenticatedUserId != null) {
- UserAccount user = getUser(authenticatedUserId);
- user.setDarkMode(darkMode);
- getUserStorage().updateUser(user);
-
- return ok(Notifications.success("Appearance updated"));
- } else {
- return statusMessage(Notifications.error("User not found"));
- }
- }
-
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- public Response updateUserDetails(UserAccount user) {
- String authenticatedUserId = getAuthenticatedUsername();
- if (user != null && authenticatedUserId.equals(user.getEmail())) {
- UserAccount existingUser = getUser(user.getEmail());
- user.setPassword(existingUser.getPassword());
- user.setUserApiTokens(existingUser
- .getUserApiTokens()
- .stream()
- .filter(existingToken -> user.getUserApiTokens()
- .stream()
- .anyMatch(updatedToken -> existingToken
- .getTokenId()
- .equals(updatedToken.getTokenId())))
- .collect(Collectors.toList()));
- user.setRev(existingUser.getRev());
- getUserStorage().updateUser(user);
- return ok(Notifications.success("User updated"));
- } else {
- return statusMessage(Notifications.error("User not found"));
- }
- }
-
- private UserAccount getUser(String email) {
- return getUserStorage().getUserAccount(email);
- }
-
- @POST
- @Path("tokens")
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- @JacksonSerialized
- public Response createNewApiToken(RawUserApiToken rawToken) {
- RawUserApiToken generatedToken = new TokenService().createAndStoreNewToken(getAuthenticatedUsername(), rawToken);
- return ok(generatedToken);
- }
}
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserResource.java
new file mode 100644
index 0000000..95a452e
--- /dev/null
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserResource.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.rest.impl;
+
+import org.apache.streampipes.model.client.user.*;
+import org.apache.streampipes.model.message.Notifications;
+import org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
+import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
+import org.apache.streampipes.user.management.service.TokenService;
+import org.apache.streampipes.user.management.util.PasswordUtil;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Path("/v2/users")
+public class UserResource extends AbstractAuthGuardedRestResource {
+
+ @GET
+ @JacksonSerialized
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getAllUsers(@QueryParam("type") String principalType) {
+ List<Principal> allPrincipals = new ArrayList<>();
+ if (principalType != null && principalType.equals(PrincipalType.USER_ACCOUNT.name())) {
+ allPrincipals.addAll(getUserStorage().getAllUserAccounts());
+ } else if (principalType != null && principalType.equals(PrincipalType.SERVICE_ACCOUNT.name())) {
+ allPrincipals.addAll(getUserStorage().getAllServiceAccounts());
+ } else {
+ allPrincipals.addAll(getUserStorage().getAllUsers());
+ }
+ removeCredentials(allPrincipals);
+ return ok(allPrincipals);
+ }
+
+
+ @GET
+ @JacksonSerialized
+ @Path("{username}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getUserDetails(@PathParam("username") String username) {
+ Principal principal = getPrincipal(username);
+ removeCredentials(principal);
+
+ if (principal != null) {
+ return ok(principal);
+ } else {
+ return statusMessage(Notifications.error("User not found"));
+ }
+ }
+
+ @DELETE
+ @JacksonSerialized
+ @Path("{principalId}")
+ public Response deleteUser(@PathParam("principalId") String principalId) {
+ Principal principal = getPrincipalById(principalId);
+
+ if (principal != null) {
+ getUserStorage().deleteUser(principalId);
+ return ok();
+ } else {
+ return statusMessage(Notifications.error("User not found"));
+ }
+ }
+
+ @Path("{userId}/appearance/mode/{darkMode}")
+ @PUT
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateAppearanceMode(@PathParam("userId") String userId,
+ @PathParam("darkMode") boolean darkMode) {
+ String authenticatedUserId = getAuthenticatedUsername();
+ if (authenticatedUserId != null) {
+ UserAccount user = getUser(authenticatedUserId);
+ user.setDarkMode(darkMode);
+ getUserStorage().updateUser(user);
+
+ return ok(Notifications.success("Appearance updated"));
+ } else {
+ return statusMessage(Notifications.error("User not found"));
+ }
+ }
+
+ @POST
+ @Path("/user")
+ @JacksonSerialized
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response registerUser(UserAccount userAccount) {
+ // TODO check if userId is already taken
+ try {
+ if (getUserStorage().getUser(userAccount.getUsername()) == null) {
+ String property = userAccount.getPassword();
+ String encryptedProperty = PasswordUtil.encryptPassword(property);
+ userAccount.setPassword(encryptedProperty);
+ getUserStorage().storeUser(userAccount);
+ return ok();
+ } else {
+ return badRequest(Notifications.error("This user ID already exists. Please choose another address."));
+ }
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ return badRequest();
+ }
+ }
+
+ @POST
+ @Path("/service")
+ @JacksonSerialized
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response registerService(ServiceAccount userAccount) {
+ // TODO check if userId is already taken
+ if (getUserStorage().getUser(userAccount.getUsername()) == null) {
+ getUserStorage().storeUser(userAccount);
+ return ok();
+ } else {
+ return badRequest(Notifications.error("This user ID already exists. Please choose another address."));
+ }
+ }
+
+ @POST
+ @Path("{userId}/tokens")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @JacksonSerialized
+ public Response createNewApiToken(@PathParam("userId") String userId,
+ RawUserApiToken rawToken) {
+ RawUserApiToken generatedToken = new TokenService().createAndStoreNewToken(userId, rawToken);
+ return ok(generatedToken);
+ }
+
+ @PUT
+ @Path("user/{principalId}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateUserAccountDetails(@PathParam("principalId") String principalId,
+ UserAccount user) {
+ String authenticatedUserId = getAuthenticatedUsername();
+ if (user != null && (authenticatedUserId.equals(principalId) || isAdmin())) {
+ Principal existingUser = getPrincipalById(principalId);
+ updateUser((UserAccount) existingUser, user);
+ user.setRev(existingUser.getRev());
+ getUserStorage().updateUser(user);
+ return ok(Notifications.success("User updated"));
+ } else {
+ return statusMessage(Notifications.error("User not found"));
+ }
+ }
+
+ @PUT
+ @Path("service/{principalId}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateServiceAccountDetails(@PathParam("principalId") String principalId,
+ ServiceAccount user) {
+ String authenticatedUserId = getAuthenticatedUsername();
+ if (user != null && (authenticatedUserId.equals(principalId) || isAdmin())) {
+ Principal existingUser = getPrincipalById(principalId);
+ user.setRev(existingUser.getRev());
+ getUserStorage().updateUser(user);
+ return ok(Notifications.success("User updated"));
+ } else {
+ return statusMessage(Notifications.error("User not found"));
+ }
+ }
+
+ private boolean isAdmin() {
+ return SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream().anyMatch(r -> r.getAuthority().equals(Role.ADMIN.name()));
+ }
+
+ private void updateUser(UserAccount existingUser, UserAccount user) {
+ user.setPassword(existingUser.getPassword());
+ user.setUserApiTokens(existingUser
+ .getUserApiTokens()
+ .stream()
+ .filter(existingToken -> user.getUserApiTokens()
+ .stream()
+ .anyMatch(updatedToken -> existingToken
+ .getTokenId()
+ .equals(updatedToken.getTokenId())))
+ .collect(Collectors.toList()));
+ }
+
+ private UserAccount getUser(String username) {
+ return getUserStorage().getUserAccount(username);
+ }
+
+ private Principal getPrincipal(String username) {
+ return getUserStorage().getUser(username);
+ }
+
+ private Principal getPrincipalById(String principalId) {
+ return getUserStorage().getUserById(principalId);
+ }
+
+ private void removeCredentials(List<Principal> principals) {
+ principals.forEach(this::removeCredentials);
+ }
+
+ private void removeCredentials(Principal principal) {
+ if (principal instanceof UserAccount) {
+ ((UserAccount) principal).setPassword("");
+ }
+ }
+}
diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java
index 54f8f27..52a5b80 100644
--- a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java
@@ -25,6 +25,8 @@ public interface INoSqlStorage {
ICategoryStorage getCategoryStorageAPI();
+ IUserGroupStorage getUserGroupStorage();
+
ILabelStorage getLabelStorageAPI();
IPipelineStorage getPipelineStorageAPI();
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/Permission.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserGroupStorage.java
similarity index 82%
rename from streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/Permission.java
rename to streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserGroupStorage.java
index 00340e2..accd125 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/Permission.java
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserGroupStorage.java
@@ -15,8 +15,9 @@
* limitations under the License.
*
*/
+package org.apache.streampipes.storage.api;
-package org.apache.streampipes.model.client.security;
+import org.apache.streampipes.model.client.user.Group;
-public class Permission {
+public interface IUserGroupStorage extends CRUDStorage<String, Group> {
}
diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java
index 4f1c7f8..d7f996a 100644
--- a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java
@@ -30,11 +30,11 @@ public interface IUserStorage {
List<ServiceAccount> getAllServiceAccounts();
- Principal getUser(String principalName);
+ Principal getUser(String username);
- UserAccount getUserAccount(String principalName);
+ UserAccount getUserAccount(String username);
- ServiceAccount getServiceAccount(String principalName);
+ ServiceAccount getServiceAccount(String username);
void storeUser(Principal user);
@@ -43,4 +43,8 @@ public interface IUserStorage {
boolean emailExists(String email);
boolean checkUser(String username);
+
+ void deleteUser(String principalId);
+
+ Principal getUserById(String principalId);
}
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java
index 288ae0f..b26d056 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java
@@ -38,6 +38,11 @@ public enum CouchDbStorageManager implements INoSqlStorage {
public ICategoryStorage getCategoryStorageAPI() { return new CategoryStorageImpl(); }
@Override
+ public IUserGroupStorage getUserGroupStorage() {
+ return new UserGroupStorageImpl();
+ }
+
+ @Override
public ILabelStorage getLabelStorageAPI() { return new LabelStorageImpl(); }
@Override
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserGroupStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserGroupStorageImpl.java
new file mode 100644
index 0000000..ef1db56
--- /dev/null
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserGroupStorageImpl.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.storage.couchdb.impl;
+
+import org.apache.streampipes.model.client.user.Group;
+import org.apache.streampipes.storage.api.IUserGroupStorage;
+import org.apache.streampipes.storage.couchdb.dao.AbstractDao;
+import org.apache.streampipes.storage.couchdb.utils.Utils;
+
+import java.util.List;
+
+public class UserGroupStorageImpl extends AbstractDao<Group> implements IUserGroupStorage {
+
+ public UserGroupStorageImpl() {
+ super(Utils::getCouchDbUserGroupStorage, Group.class);
+ }
+
+ @Override
+ public List<Group> getAll() {
+ return findAll();
+ }
+
+ @Override
+ public void createElement(Group element) {
+ persist(element);
+ }
+
+ @Override
+ public Group getElementById(String s) {
+ return findWithNullIfEmpty(s);
+ }
+
+ @Override
+ public Group updateElement(Group element) {
+ update(element);
+ return getElementById(element.getGroupId());
+ }
+
+ @Override
+ public void deleteElement(Group element) {
+ delete(element.getGroupId());
+ }
+}
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserStorage.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserStorage.java
index c4b6d7f..5e1b157 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserStorage.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserStorage.java
@@ -35,98 +35,111 @@ import java.util.stream.Collectors;
/**
* User Storage.
* Handles operations on user including user-specified pipelines.
- *
- *
*/
public class UserStorage extends AbstractDao<Principal> implements IUserStorage {
- Logger LOG = LoggerFactory.getLogger(UserStorage.class);
-
- public UserStorage() {
- super(Utils::getCouchDbUserClient, Principal.class);
- }
-
- @Override
- public List<Principal> getAllUsers()
- {
- List<Principal> users = findAll();
- return new ArrayList<>(users);
- }
-
- @Override
- public List<UserAccount> getAllUserAccounts() {
- return findAll()
- .stream()
- .filter(u -> u instanceof UserAccount)
- .map(u -> (UserAccount) u)
- .collect(Collectors.toList());
- }
-
- @Override
- public List<ServiceAccount> getAllServiceAccounts() {
- return findAll()
- .stream()
- .filter(u -> u instanceof ServiceAccount)
- .map(u -> (ServiceAccount) u)
- .collect(Collectors.toList());
- }
-
- @Override
- public Principal getUser(String principalName) {
- // TODO improve
- CouchDbClient couchDbClient = couchDbClientSupplier.get();
- List<Principal> users = couchDbClient.view("users/username").key(principalName).includeDocs(true).query(Principal.class);
- if (users.size() != 1) {
- LOG.error("None or to many users with matching username");
- }
- return users.get(0);
+ Logger LOG = LoggerFactory.getLogger(UserStorage.class);
+
+ public UserStorage() {
+ super(Utils::getCouchDbUserClient, Principal.class);
+ }
+
+ @Override
+ public List<Principal> getAllUsers() {
+ List<Principal> users = couchDbClientSupplier
+ .get()
+ .view("users/username")
+ .includeDocs(true)
+ .query(Principal.class);
+ return new ArrayList<>(users);
+ }
+
+ @Override
+ public List<UserAccount> getAllUserAccounts() {
+ return getAllUsers()
+ .stream()
+ .filter(u -> u instanceof UserAccount)
+ .map(u -> (UserAccount) u)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<ServiceAccount> getAllServiceAccounts() {
+ return getAllUsers()
+ .stream()
+ .filter(u -> u instanceof ServiceAccount)
+ .map(u -> (ServiceAccount) u)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Principal getUser(String username) {
+ // TODO improve
+ CouchDbClient couchDbClient = couchDbClientSupplier.get();
+ List<Principal> users = couchDbClient
+ .view("users/username")
+ .key(username)
+ .includeDocs(true)
+ .query(Principal.class);
+ if (users.size() != 1) {
+ LOG.error("None or to many users with matching username");
}
-
- @Override
- public UserAccount getUserAccount(String principalName) {
- return (UserAccount) getUser(principalName);
- }
-
- @Override
- public ServiceAccount getServiceAccount(String principalName) {
- return (ServiceAccount) getUser(principalName);
- }
-
- @Override
- public void storeUser(Principal user) {
- persist(user);
- }
-
- @Override
- public void updateUser(Principal user) {
- update(user);
- }
-
- @Override
- public boolean emailExists(String email)
- {
- List<UserAccount> users = getAllUserAccounts();
- return users
- .stream()
- .filter(u -> u.getEmail() != null)
- .anyMatch(u -> u.getEmail().equals(email));
- }
-
- /**
- *
- * @param username
- * @return True if user exists exactly once, false otherwise
- */
- @Override
- public boolean checkUser(String username) {
- List<Principal> users = couchDbClientSupplier
- .get()
- .view("users/username")
- .key(username)
- .includeDocs(true)
- .query(Principal.class);
-
- return users.size() == 1;
- }
+ return users.size() > 0 ? users.get(0) : null;
+ }
+
+ @Override
+ public UserAccount getUserAccount(String username) {
+ return (UserAccount) getUser(username);
+ }
+
+ @Override
+ public ServiceAccount getServiceAccount(String username) {
+ return (ServiceAccount) getUser(username);
+ }
+
+ @Override
+ public void storeUser(Principal user) {
+ persist(user);
+ }
+
+ @Override
+ public void updateUser(Principal user) {
+ update(user);
+ }
+
+ @Override
+ public boolean emailExists(String email) {
+ List<UserAccount> users = getAllUserAccounts();
+ return users
+ .stream()
+ .filter(u -> u.getEmail() != null)
+ .anyMatch(u -> u.getEmail().equals(email));
+ }
+
+ /**
+ * @param username
+ * @return True if user exists exactly once, false otherwise
+ */
+ @Override
+ public boolean checkUser(String username) {
+ List<Principal> users = couchDbClientSupplier
+ .get()
+ .view("users/username")
+ .key(username)
+ .includeDocs(true)
+ .query(Principal.class);
+
+ return users.size() == 1;
+ }
+
+ @Override
+ public void deleteUser(String principalId) {
+ delete(principalId);
+ }
+
+ @Override
+ public Principal getUserById(String principalId) {
+ return findWithNullIfEmpty(principalId);
+ }
}
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
index ba2aba5..aa4eeb5 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
@@ -76,6 +76,10 @@ public class Utils {
return getCouchDbGsonClient("pipeline");
}
+ public static CouchDbClient getCouchDbUserGroupStorage() {
+ return getCouchDbGsonClient("usergroup");
+ }
+
public static CouchDbClient getCouchDbSepaInvocationClient() {
return getCouchDbGsonClient("invocation");
}
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 3dc0857..e877f5f 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
@@ -36,7 +36,7 @@ public class JwtTokenProvider {
Date tokenExpirationDate = makeExpirationDate();
Map<String, Object> claims = makeClaims(userPrincipal, roles);
- return JwtTokenUtils.makeJwtToken(userPrincipal.getPrincipalName(), tokenSecret(), claims, tokenExpirationDate);
+ return JwtTokenUtils.makeJwtToken(userPrincipal.getUsername(), tokenSecret(), claims, tokenExpirationDate);
}
private Map<String, Object> makeClaims(Principal principal,
@@ -76,7 +76,7 @@ public class JwtTokenProvider {
private UserInfo toUserInfo(UserAccount localUser,
Set<String> roles) {
UserInfo userInfo = new UserInfo();
- userInfo.setUserId("id");
+ userInfo.setUsername(localUser.getUsername());
userInfo.setEmail(localUser.getEmail());
userInfo.setDisplayName(localUser.getUsername());
userInfo.setShowTutorial(!localUser.isHideTutorial());
diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java
index a8c8cf6..bcf3635 100644
--- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java
+++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java
@@ -58,8 +58,8 @@ public class SpKeyResolver implements SigningKeyResolver {
return null;
}
- private Principal getPrincipal(String principalName) {
- return userStorage.getUser(principalName);
+ private Principal getPrincipal(String username) {
+ return userStorage.getUser(username);
}
private boolean isRealUser(Principal principal) {
diff --git a/ui/src/app/configuration/configuration.module.ts b/ui/src/app/configuration/configuration.module.ts
index 3005794..a9cbc59 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -25,7 +25,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FlexLayoutModule } from '@angular/flex-layout';
import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ConfigurationComponent } from './configuration.component';
import { ConfigurationService } from './shared/configuration.service';
@@ -46,6 +46,10 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { SecurityConfigurationComponent } from './security-configuration/security-configuration.component';
import { CoreUiModule } from '../core-ui/core-ui.module';
import { MatDividerModule } from '@angular/material/divider';
+import { SecurityUserConfigComponent } from './security-configuration/security-user-configuration/security-user-config.component';
+import { SecurityServiceConfigComponent } from './security-configuration/security-service-configuration/security-service-config.component';
+import { EditUserDialogComponent } from './security-configuration/edit-user-dialog/edit-user-dialog.component';
+import { PlatformServicesModule } from '../platform-services/platform.module';
@NgModule({
imports: [
@@ -62,7 +66,9 @@ import { MatDividerModule } from '@angular/material/divider';
MatTooltipModule,
FormsModule,
DragDropModule,
- CoreUiModule
+ CoreUiModule,
+ ReactiveFormsModule,
+ PlatformServicesModule,
],
declarations: [
ConfigurationComponent,
@@ -73,8 +79,11 @@ import { MatDividerModule } from '@angular/material/divider';
ConsulConfigsBooleanComponent,
ConsulConfigsNumberComponent,
DeleteDatalakeIndexComponent,
+ EditUserDialogComponent,
PipelineElementConfigurationComponent,
SecurityConfigurationComponent,
+ SecurityUserConfigComponent,
+ SecurityServiceConfigComponent,
MessagingConfigurationComponent,
DatalakeConfigurationComponent
],
diff --git a/ui/src/app/configuration/messaging-configuration/messaging-configuration.component.html b/ui/src/app/configuration/messaging-configuration/messaging-configuration.component.html
index b64ea3a..61d96e3 100644
--- a/ui/src/app/configuration/messaging-configuration/messaging-configuration.component.html
+++ b/ui/src/app/configuration/messaging-configuration/messaging-configuration.component.html
@@ -16,81 +16,78 @@
~
-->
-<div fxLayout="row" class="page-container-padding">
- <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start">
- <sp-split-section title="Kafka Settings"
- subtitle="Manage Kafka settings for pipeline communication">
- <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start" class="page-container-padding-inner">
- <form (ngSubmit)="updateMessagingSettings()" class="form-width" fxFlex="100" fxLayout="column"
- *ngIf="loadingCompleted">
- <mat-form-field class="form-field" fxFlex="100">
- <input matInput [(ngModel)]="messagingSettings.batchSize"
- [placeholder]="'Batch Size'" type="text"
- [ngModelOptions]="{standalone: true}">
- </mat-form-field>
- <mat-form-field class="form-field" fxFlex="100">
- <input matInput [(ngModel)]="messagingSettings.messageMaxBytes"
- [placeholder]="'Message Max Bytes'" type="text"
- [ngModelOptions]="{standalone: true}">
- </mat-form-field>
- <mat-form-field class="form-field" fxFlex="100">
- <input matInput [(ngModel)]="messagingSettings.acks"
- [placeholder]="'Acks'" type="text"
- [ngModelOptions]="{standalone: true}">
- </mat-form-field>
- <mat-form-field class="form-field" fxFlex="100">
- <input matInput [(ngModel)]="messagingSettings.lingerMs"
- [placeholder]="'Linger MS'" type="text"
- [ngModelOptions]="{standalone: true}">
- </mat-form-field>
- <div fxLayoutAlign="start center" class="mt-10">
- <button mat-raised-button color="accent" type="submit"
- class="md-raised md-primary submit-button">Update
- </button>
- </div>
+<div fxLayout="column" class="page-container-padding">
+ <sp-split-section title="Kafka Settings"
+ subtitle="Manage Kafka settings for pipeline communication">
+ <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start" class="page-container-padding-inner">
+ <form (ngSubmit)="updateMessagingSettings()" class="form-width" fxFlex="100" fxLayout="column"
+ *ngIf="loadingCompleted">
+ <mat-form-field class="form-field" fxFlex="100">
+ <input matInput [(ngModel)]="messagingSettings.batchSize"
+ [placeholder]="'Batch Size'" type="text"
+ [ngModelOptions]="{standalone: true}">
+ </mat-form-field>
+ <mat-form-field class="form-field" fxFlex="100">
+ <input matInput [(ngModel)]="messagingSettings.messageMaxBytes"
+ [placeholder]="'Message Max Bytes'" type="text"
+ [ngModelOptions]="{standalone: true}">
+ </mat-form-field>
+ <mat-form-field class="form-field" fxFlex="100">
+ <input matInput [(ngModel)]="messagingSettings.acks"
+ [placeholder]="'Acks'" type="text"
+ [ngModelOptions]="{standalone: true}">
+ </mat-form-field>
+ <mat-form-field class="form-field" fxFlex="100">
+ <input matInput [(ngModel)]="messagingSettings.lingerMs"
+ [placeholder]="'Linger MS'" type="text"
+ [ngModelOptions]="{standalone: true}">
+ </mat-form-field>
+ <div fxLayoutAlign="start center" class="mt-10">
+ <button mat-raised-button color="accent" type="submit"
+ class="md-raised md-primary submit-button">Update
+ </button>
+ </div>
- </form>
- </div>
- </sp-split-section>
- <mat-divider></mat-divider>
- <div class="mt-30">
- <sp-split-section title="Message Formats"
- subtitle="Manage the priority of message formats used">
- <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start" class="page-container-padding-inner">
- <div cdkDropList class="data-format-list" (cdkDropListDropped)="drop($event)"
- *ngIf="loadingCompleted">
- <div class="data-format-box" *ngFor="let format of messagingSettings.prioritizedFormats"
- cdkDrag>
- {{format}}
- </div>
- </div>
- <div fxLayoutAlign="start center" class="mt-10">
- <button mat-raised-button color="accent" type="submit"
- class="md-raised md-primary submit-button" (click)="updateMessagingSettings()">Update
- </button>
- </div>
+ </form>
+ </div>
+ </sp-split-section>
+ <mat-divider></mat-divider>
+
+ <sp-split-section title="Message Formats"
+ subtitle="Manage the priority of message formats used">
+ <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start" class="page-container-padding-inner">
+ <div cdkDropList class="data-format-list" (cdkDropListDropped)="drop($event)"
+ *ngIf="loadingCompleted">
+ <div class="data-format-box" *ngFor="let format of messagingSettings.prioritizedFormats"
+ cdkDrag>
+ {{format}}
</div>
- </sp-split-section>
+ </div>
+ <div fxLayoutAlign="start center" class="mt-10">
+ <button mat-raised-button color="accent" type="submit"
+ class="md-raised md-primary submit-button" (click)="updateMessagingSettings()">Update
+ </button>
+ </div>
</div>
- <mat-divider></mat-divider>
- <div class="mt-30">
- <sp-split-section title="Protocols"
- subtitle="Manage the priority of protocols used">
- <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start" class="page-container-padding-inner">
- <div cdkDropList class="data-format-list" (cdkDropListDropped)="dropProtocol($event)"
- *ngIf="loadingCompleted">
- <div class="data-format-box" *ngFor="let protocol of messagingSettings.prioritizedProtocols"
- cdkDrag>
- {{protocol}}
- </div>
- </div>
- <div fxLayoutAlign="start center" class="mt-10">
- <button mat-raised-button color="accent" type="submit"
- class="md-raised md-primary submit-button" (click)="updateMessagingSettings()">Update
- </button>
- </div>
+ </sp-split-section>
+
+ <mat-divider></mat-divider>
+
+ <sp-split-section title="Protocols"
+ subtitle="Manage the priority of protocols used">
+ <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start" class="page-container-padding-inner">
+ <div cdkDropList class="data-format-list" (cdkDropListDropped)="dropProtocol($event)"
+ *ngIf="loadingCompleted">
+ <div class="data-format-box" *ngFor="let protocol of messagingSettings.prioritizedProtocols"
+ cdkDrag>
+ {{protocol}}
</div>
- </sp-split-section>
+ </div>
+ <div fxLayoutAlign="start center" class="mt-10">
+ <button mat-raised-button color="accent" type="submit"
+ class="md-raised md-primary submit-button" (click)="updateMessagingSettings()">Update
+ </button>
+ </div>
</div>
- </div>
+ </sp-split-section>
</div>
diff --git a/ui/src/app/configuration/security-configuration/abstract-security-principal-config.ts b/ui/src/app/configuration/security-configuration/abstract-security-principal-config.ts
new file mode 100644
index 0000000..be1b03d
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/abstract-security-principal-config.ts
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Directive, OnInit, ViewChild } from '@angular/core';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import { MatTableDataSource } from '@angular/material/table';
+import { UserService } from '../../platform-services/apis/user.service';
+import { Observable } from 'rxjs';
+import { ServiceAccount, UserAccount } from '../../core-model/gen/streampipes-model-client';
+import { PanelType } from '../../core-ui/dialog/base-dialog/base-dialog.model';
+import { DialogService } from '../../core-ui/dialog/base-dialog/base-dialog.service';
+import { EditUserDialogComponent } from './edit-user-dialog/edit-user-dialog.component';
+
+@Directive()
+export abstract class AbstractSecurityPrincipalConfig<T extends (UserAccount | ServiceAccount)> implements OnInit {
+
+ users: T[] = [];
+
+ @ViewChild(MatPaginator) paginator: MatPaginator;
+ pageSize = 1;
+ @ViewChild(MatSort) sort: MatSort;
+
+ dataSource: MatTableDataSource<T>;
+
+ constructor(protected userService: UserService,
+ protected dialogService: DialogService) {
+ }
+
+ ngOnInit(): void {
+ this.load();
+ }
+
+ openEditDialog(user: (UserAccount | ServiceAccount),
+ editMode: boolean) {
+ const dialogRef = this.dialogService.open(EditUserDialogComponent, {
+ panelType: PanelType.SLIDE_IN_PANEL,
+ title: editMode ? 'Edit user ' + user.username : 'Add user',
+ width: '50vw',
+ data: {
+ 'user': user,
+ 'editMode': editMode
+ }
+ });
+
+ dialogRef.afterClosed().subscribe(refresh => {
+ if (refresh) {
+ this.load();
+ }
+ });
+ }
+
+ createUser() {
+ const principal = this.getNewInstance();
+ principal.roles = [];
+ this.openEditDialog(principal, false);
+ }
+
+ load() {
+ this.getObservable().subscribe(response => {
+ this.users = response;
+ this.dataSource = new MatTableDataSource(this.users);
+ });
+ }
+
+ deleteUser(account: UserAccount | ServiceAccount) {
+ this.userService.deleteUser(account.principalId).subscribe(() => {
+ this.load();
+ });
+ }
+
+ abstract getObservable(): Observable<T[]>;
+
+ abstract getNewInstance(): T;
+
+
+}
diff --git a/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.html b/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.html
new file mode 100644
index 0000000..4bdc2a3
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.html
@@ -0,0 +1,89 @@
+<div class="sp-dialog-container">
+ <div class="sp-dialog-content">
+ <div fxFlex="100" fxLayout="column" class="p-15">
+ <form [formGroup]="parentForm" fxFlex="100" fxLayout="column">
+ <div class="general-options-panel" fxLayout="column">
+ <span class="general-options-header">Basics</span>
+ <mat-form-field color="accent">
+ <mat-label>Username</mat-label>
+ <input formControlName="username" fxFlex
+ matInput
+ required>
+ </mat-form-field>
+ <mat-form-field color="accent" *ngIf="isUserAccount">
+ <mat-label>Full Name</mat-label>
+ <input formControlName="fullName" fxFlex
+ matInput
+ required>
+ </mat-form-field>
+ <mat-form-field color="accent" *ngIf="isUserAccount">
+ <mat-label>Email</mat-label>
+ <input formControlName="email" fxFlex
+ matInput
+ required>
+ </mat-form-field>
+ </div>
+ <div fxLayout="column" class="general-options-panel" *ngIf="!editMode && isUserAccount">
+ <span class="general-options-header">Password</span>
+ <mat-form-field color="accent">
+ <mat-label>Initial password</mat-label>
+ <input formControlName="password" fxFlex
+ matInput
+ required>
+ </mat-form-field>
+ <mat-form-field color="accent">
+ <mat-label>Repeat password</mat-label>
+ <input formControlName="repeatPassword" fxFlex
+ matInput
+ required>
+ </mat-form-field>
+ </div>
+ <div fxLayout="column" class="general-options-panel" *ngIf="!isUserAccount">
+ <span class="general-options-header">Authentication</span>
+ <mat-form-field color="accent">
+ <mat-label>Client Secret</mat-label>
+ <input formControlName="clientSecret" fxFlex
+ matInput
+ required>
+ </mat-form-field>
+ </div>
+ <div fxLayout="column" class="general-options-panel">
+ <span class="general-options-header">Groups</span>
+<!-- <mat-checkbox *ngFor="let role of availableRoles" [value]="role"-->
+<!-- [checked]="user.roles.indexOf(role) > -1" (change)="changeRoleAssignment($event)">-->
+<!-- {{role}}-->
+<!-- </mat-checkbox>-->
+ </div>
+ <div fxLayout="column" class="general-options-panel">
+ <span class="general-options-header">Roles</span>
+ <mat-checkbox *ngFor="let role of availableRoles" [value]="role"
+ [checked]="user.roles.indexOf(role) > -1" (change)="changeRoleAssignment($event)">
+ {{role}}
+ </mat-checkbox>
+ </div>
+ <div fxLayout="column" class="general-options-panel">
+ <span class="general-options-header">Account</span>
+ <mat-checkbox formControlName="accountEnabled">
+ Enabled
+ </mat-checkbox>
+ <mat-checkbox formControlName="accountLocked">
+ Locked
+ </mat-checkbox>
+ </div>
+ </form>
+ </div>
+ </div>
+ <mat-divider></mat-divider>
+ <div class="sp-dialog-actions">
+ <div fxLayout="row">
+ <button mat-button mat-raised-button color="accent" (click)="save()" style="margin-right:10px;"
+ [disabled]="!parentForm.valid || clonedUser.roles.length == 0"
+ data-cy="sp-element-edit-user-save">
+ <i class="material-icons">save</i><span> Save</span>
+ </button>
+ <button mat-button mat-raised-button class="mat-basic" (click)="close(false)">
+ Cancel
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/Subject.java b/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.scss
similarity index 83%
rename from streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/Subject.java
rename to ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.scss
index 39caab6..aad1d42 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/Subject.java
+++ b/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.scss
@@ -16,11 +16,13 @@
*
*/
-package org.apache.streampipes.model.client.security;
+@import '../../../../scss/sp/sp-dialog.scss';
-public abstract class Subject {
-
- protected String subjectId;
+.form-field .mat-form-field-wrapper {
+ margin-bottom: -1.25em;
+}
+.form-field .mat-form-field-infix {
+ border-top: 0;
}
diff --git a/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.ts b/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.ts
new file mode 100644
index 0000000..c9cb3f9
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.ts
@@ -0,0 +1,141 @@
+import { AfterViewInit, Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
+import { DialogRef } from '../../../core-ui/dialog/base-dialog/dialog-ref';
+import {
+ Role,
+ ServiceAccount,
+ UserAccount
+} from '../../../core-model/gen/streampipes-model-client';
+import {
+ AbstractControl,
+ FormBuilder,
+ FormControl,
+ FormGroup,
+ ValidationErrors,
+ ValidatorFn,
+ Validators
+} from '@angular/forms';
+import { UserRole } from '../../../_enums/user-role.enum';
+import { MatCheckboxChange } from '@angular/material/checkbox';
+import { UserService } from '../../../platform-services/apis/user.service';
+
+@Component({
+ selector: 'sp-edit-user-dialog',
+ templateUrl: './edit-user-dialog.component.html',
+ styleUrls: ['./edit-user-dialog.component.scss'],
+ encapsulation: ViewEncapsulation.None
+})
+export class EditUserDialogComponent implements OnInit, AfterViewInit {
+
+ @Input()
+ user: any;
+
+ @Input()
+ editMode: boolean;
+
+ isUserAccount: boolean;
+ parentForm: FormGroup;
+ clonedUser: UserAccount | ServiceAccount;
+
+ availableRoles: string[];
+
+ constructor(private dialogRef: DialogRef<EditUserDialogComponent>,
+ private fb: FormBuilder,
+ private userService: UserService) {
+ }
+
+ ngAfterViewInit(): void {
+ }
+
+ ngOnInit(): void {
+ this.availableRoles = Object.values(UserRole).filter(value => typeof value === 'string') as string[];
+ this.clonedUser = this.user instanceof UserAccount ? UserAccount.fromData(this.user, new UserAccount()) : ServiceAccount.fromData(this.user, new ServiceAccount());
+ this.isUserAccount = this.user instanceof UserAccount;
+ this.parentForm = this.fb.group({});
+ this.parentForm.addControl('username', new FormControl(this.clonedUser.username, Validators.required));
+ this.parentForm.addControl('accountEnabled', new FormControl(this.clonedUser.accountEnabled));
+ this.parentForm.addControl('accountLocked', new FormControl(this.clonedUser.accountLocked));
+ if (this.clonedUser instanceof UserAccount) {
+ this.parentForm.addControl('email', new FormControl(this.clonedUser.email));
+ this.parentForm.addControl('fullName', new FormControl(this.clonedUser.fullName));
+ } else {
+ this.parentForm.addControl('clientSecret', new FormControl(this.clonedUser.clientSecret));
+ }
+
+ if (!this.editMode && this.isUserAccount) {
+ this.parentForm.addControl('password', new FormControl(this.clonedUser.password, Validators.required));
+ this.parentForm.addControl('repeatPassword', new FormControl());
+ this.parentForm.setValidators(this.checkPasswords);
+ }
+
+ this.parentForm.valueChanges.subscribe(v => {
+ this.clonedUser.username = v.username;
+ this.clonedUser.accountLocked = v.accountLocked;
+ this.clonedUser.accountEnabled = v.accountEnabled;
+ if (this.clonedUser instanceof UserAccount) {
+ this.clonedUser.email = v.email;
+ this.clonedUser.fullName = v.fullName;
+ } else {
+ this.clonedUser.clientSecret = v.clientSecret;
+ }
+ if (!this.editMode) {
+ this.clonedUser.password = v.password;
+ }
+ });
+
+ }
+
+ checkPasswords: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
+ const pass = group.get('password');
+ const confirmPass = group.get('repeatPassword');
+
+ if (!pass || !confirmPass) {
+ return null;
+ }
+ return pass.value === confirmPass.value ? null : { notMatching: true };
+ }
+
+ save() {
+ if (this.editMode) {
+ if (this.isUserAccount) {
+ this.userService.updateUser(this.clonedUser as UserAccount).subscribe(() => {
+ this.close(true);
+ });
+ } else {
+ this.userService.updateService(this.clonedUser as ServiceAccount).subscribe(() => {
+ this.close(true);
+ });
+ }
+ } else {
+ if (this.isUserAccount) {
+ this.userService.createUser(this.clonedUser as UserAccount).subscribe(() => {
+ this.close(true);
+ });
+ } else {
+ this.userService.createServiceAccount(this.clonedUser as ServiceAccount).subscribe(() => {
+ this.close(true);
+ });
+ }
+ }
+ }
+
+ close(refresh: boolean) {
+ this.dialogRef.close(refresh);
+ }
+
+ changeRoleAssignment(event: MatCheckboxChange) {
+ if (this.clonedUser.roles.indexOf(event.source.value as Role) > -1) {
+ this.removeRole(event.source.value);
+ } else {
+ this.addRole(event.source.value);
+ }
+ }
+
+ removeRole(role: string) {
+ this.clonedUser.roles.splice(this.clonedUser.roles.indexOf(role as Role), 1);
+ }
+
+ addRole(role: string) {
+ this.clonedUser.roles.push(role as Role);
+ }
+
+}
diff --git a/ui/src/app/configuration/security-configuration/security-configuration.component.html b/ui/src/app/configuration/security-configuration/security-configuration.component.html
index e40d375..a1c9218 100644
--- a/ui/src/app/configuration/security-configuration/security-configuration.component.html
+++ b/ui/src/app/configuration/security-configuration/security-configuration.component.html
@@ -16,3 +16,21 @@
~
-->
+<div fxLayout="column" class="page-container-padding">
+ <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start">
+ <sp-split-section title="User Accounts"
+ subtitle="Add and edit user accounts">
+ <div class="subsection-title">Existing user accounts</div>
+ <sp-security-user-config></sp-security-user-config>
+ </sp-split-section>
+ </div>
+ <mat-divider></mat-divider>
+ <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start">
+ <sp-split-section title="Service Accounts"
+ subtitle="Add and edit service accounts">
+ <div class="subsection-title">Existing service accounts</div>
+ <sp-security-service-config></sp-security-service-config>
+ </sp-split-section>
+ </div>
+ <mat-divider></mat-divider>
+</div>
diff --git a/ui/src/app/configuration/security-configuration/security-configuration.component.ts b/ui/src/app/configuration/security-configuration/security-configuration.component.ts
index a4d9ed8..427c982 100644
--- a/ui/src/app/configuration/security-configuration/security-configuration.component.ts
+++ b/ui/src/app/configuration/security-configuration/security-configuration.component.ts
@@ -24,6 +24,9 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./security-configuration.component.scss']
})
export class SecurityConfigurationComponent implements OnInit {
+
+ constructor() {}
+
ngOnInit(): void {
}
diff --git a/ui/src/app/configuration/security-configuration/security-service-configuration/security-service-config.component.html b/ui/src/app/configuration/security-configuration/security-service-configuration/security-service-config.component.html
new file mode 100644
index 0000000..948ffc0
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/security-service-configuration/security-service-config.component.html
@@ -0,0 +1,77 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<div fxLayout="column">
+ <div>
+ <button mat-button mat-raised-button color="accent" (click)="createUser()"><i
+ class="material-icons">add</i><span> New Service Account</span></button>
+ </div>
+ <div fxFlex="100" fxLayout="column">
+ <table
+ fxFlex="100"
+ mat-table
+ data-cy="security-service-config"
+ [dataSource]="dataSource"
+ style="width: 100%;"
+ matSort>
+
+ <ng-container matColumnDef="username">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>Username</th>
+ <td mat-cell *matCellDef="let account">
+ <h4 style="margin-bottom:0px;">{{account.username}}</h4>
+ </td>
+ </ng-container>
+
+ <ng-container matColumnDef="edit">
+ <th mat-header-cell *matHeaderCellDef class="text-right">Actions</th>
+ <td mat-cell *matCellDef="let account">
+ <div fxLayout="row">
+ <span fxFlex fxFlexOrder="3" fxLayout="row" fxLayoutAlign="end center">
+ <div class="mr-15">
+ <button color="accent"
+ mat-button
+ mat-raised-button
+ matTooltip="Edit user"
+ matTooltipPosition="above"
+ data-cy="service-edit-btn"
+ (click)="editService(account)">
+ <i class="material-icons">edit</i>
+ <span> Edit</span>
+ </button>
+ </div>
+ <button color="warn"
+ mat-button
+ mat-raised-button
+ matTooltip="Delete service"
+ matTooltipPosition="above"
+ data-cy="service-delete-btn"
+ (click)="deleteUser(account)">
+ <i class="material-icons">delete</i>
+ <span> Delete</span>
+ </button>
+ </span>
+ </div>
+ </td>
+ </ng-container>
+
+ <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+ <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
+
+ </table>
+ </div>
+</div>
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/User.java b/ui/src/app/configuration/security-configuration/security-service-configuration/security-service-config.component.scss
similarity index 91%
rename from streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/User.java
rename to ui/src/app/configuration/security-configuration/security-service-configuration/security-service-config.component.scss
index 5006702..4c18ca4 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/User.java
+++ b/ui/src/app/configuration/security-configuration/security-service-configuration/security-service-config.component.scss
@@ -16,7 +16,6 @@
*
*/
-package org.apache.streampipes.model.client.security;
-
-public class User {
+.text-right {
+ text-align: right;
}
diff --git a/ui/src/app/configuration/security-configuration/security-service-configuration/security-service-config.component.ts b/ui/src/app/configuration/security-configuration/security-service-configuration/security-service-config.component.ts
new file mode 100644
index 0000000..e6eff68
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/security-service-configuration/security-service-config.component.ts
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component } from '@angular/core';
+import { ServiceAccount } from '../../../core-model/gen/streampipes-model-client';
+import { AbstractSecurityPrincipalConfig } from '../abstract-security-principal-config';
+import { Observable } from 'rxjs';
+
+@Component({
+ selector: 'sp-security-service-config',
+ templateUrl: './security-service-config.component.html',
+ styleUrls: ['./security-service-config.component.scss']
+})
+export class SecurityServiceConfigComponent extends AbstractSecurityPrincipalConfig<ServiceAccount> {
+
+
+ displayedColumns: string[] = ['username', 'edit'];
+
+ getObservable(): Observable<ServiceAccount[]> {
+ return this.userService.getAllServiceAccounts();
+ }
+
+ editService(account: ServiceAccount) {
+ this.openEditDialog(account, true);
+ }
+
+ getNewInstance(): ServiceAccount {
+ return new ServiceAccount();
+ }
+
+
+}
diff --git a/ui/src/app/configuration/security-configuration/security-user-configuration/security-user-config.component.html b/ui/src/app/configuration/security-configuration/security-user-configuration/security-user-config.component.html
new file mode 100644
index 0000000..1120c9d
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/security-user-configuration/security-user-config.component.html
@@ -0,0 +1,94 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<div fxLayout="column">
+ <div>
+ <button mat-button mat-raised-button color="accent" (click)="createUser()"><i
+ class="material-icons">add</i><span> New User</span></button>
+ </div>
+ <div fxFlex="100" fxLayout="column">
+ <table
+ fxFlex="100"
+ mat-table
+ data-cy="security-user-config"
+ [dataSource]="dataSource"
+ style="width: 100%;"
+ matSort>
+
+ <ng-container matColumnDef="username">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>Username</th>
+ <td mat-cell *matCellDef="let account">
+ <h4 style="margin-bottom:0px;">{{account.username}}</h4>
+ </td>
+ </ng-container>
+
+ <ng-container matColumnDef="fullName">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>Full Name</th>
+ <td mat-cell *matCellDef="let account">
+ {{account.fullName}}
+ </td>
+ </ng-container>
+
+ <ng-container matColumnDef="email">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>Email</th>
+ <td
+ mat-cell
+ data-cy="security-email"
+ *matCellDef="let account">
+ {{account.email}}
+ </td>
+ </ng-container>
+
+ <ng-container matColumnDef="edit">
+ <th mat-header-cell *matHeaderCellDef class="text-right">Actions</th>
+ <td mat-cell *matCellDef="let account">
+ <div fxLayout="row">
+ <span fxFlex fxFlexOrder="3" fxLayout="row" fxLayoutAlign="end center">
+ <div class="mr-15">
+ <button color="accent"
+ mat-button
+ mat-raised-button
+ matTooltip="Edit user"
+ matTooltipPosition="above"
+ data-cy="user-edit-btn"
+ (click)="editUser(account)">
+ <i class="material-icons">edit</i>
+ <span> Edit</span>
+ </button>
+ </div>
+ <button color="warn"
+ mat-button
+ mat-raised-button
+ matTooltip="Delete user"
+ matTooltipPosition="above"
+ data-cy="user-delete-btn"
+ (click)="deleteUser(account)">
+ <i class="material-icons">delete</i>
+ <span> Delete</span>
+ </button>
+ </span>
+ </div>
+ </td>
+ </ng-container>
+
+ <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+ <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
+
+ </table>
+ </div>
+</div>
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/Role.java b/ui/src/app/configuration/security-configuration/security-user-configuration/security-user-config.component.scss
similarity index 91%
rename from streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/Role.java
rename to ui/src/app/configuration/security-configuration/security-user-configuration/security-user-config.component.scss
index 99d223f..4c18ca4 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/security/Role.java
+++ b/ui/src/app/configuration/security-configuration/security-user-configuration/security-user-config.component.scss
@@ -16,7 +16,6 @@
*
*/
-package org.apache.streampipes.model.client.security;
-
-public class Role {
+.text-right {
+ text-align: right;
}
diff --git a/ui/src/app/configuration/security-configuration/security-configuration.component.ts b/ui/src/app/configuration/security-configuration/security-user-configuration/security-user-config.component.ts
similarity index 50%
copy from ui/src/app/configuration/security-configuration/security-configuration.component.ts
copy to ui/src/app/configuration/security-configuration/security-user-configuration/security-user-config.component.ts
index a4d9ed8..39d34f1 100644
--- a/ui/src/app/configuration/security-configuration/security-configuration.component.ts
+++ b/ui/src/app/configuration/security-configuration/security-user-configuration/security-user-config.component.ts
@@ -16,15 +16,34 @@
*
*/
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+import { UserAccount } from '../../../core-model/gen/streampipes-model-client';
+import { AbstractSecurityPrincipalConfig } from '../abstract-security-principal-config';
+import { Observable } from 'rxjs';
@Component({
- selector: 'sp-security-configuration',
- templateUrl: './security-configuration.component.html',
- styleUrls: ['./security-configuration.component.scss']
+ selector: 'sp-security-user-config',
+ templateUrl: './security-user-config.component.html',
+ styleUrls: ['./security-user-config.component.scss']
})
-export class SecurityConfigurationComponent implements OnInit {
- ngOnInit(): void {
+export class SecurityUserConfigComponent extends AbstractSecurityPrincipalConfig<UserAccount> {
+
+
+ displayedColumns: string[] = ['username', 'fullName', 'email', 'edit'];
+
+
+ getObservable(): Observable<UserAccount[]> {
+ return this.userService.getAllUserAccounts();
+ }
+
+ editUser(account: UserAccount) {
+ this.openEditDialog(account, true);
+ }
+
+ getNewInstance(): UserAccount {
+ return new UserAccount();
}
+
+
}
diff --git a/ui/src/app/core-model/gen/streampipes-model-client.ts b/ui/src/app/core-model/gen/streampipes-model-client.ts
index f6b10b4..1564e3a 100644
--- a/ui/src/app/core-model/gen/streampipes-model-client.ts
+++ b/ui/src/app/core-model/gen/streampipes-model-client.ts
@@ -19,7 +19,7 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
-// Generated using typescript-generator version 2.27.744 on 2021-10-01 13:23:19.
+// Generated using typescript-generator version 2.27.744 on 2021-10-04 22:00:31.
export class Element {
elementId: string;
@@ -139,7 +139,7 @@ export class Principal implements UserDetails {
ownSources: Element[];
password: string;
principalId: string;
- principalName: string;
+ principalType: PrincipalType;
rev: string;
roles: Role[];
username: string;
@@ -153,19 +153,19 @@ export class Principal implements UserDetails {
instance.password = data.password;
instance.username = data.username;
instance.authorities = __getCopyArrayFn(__identity<GrantedAuthority>())(data.authorities);
+ instance.accountNonLocked = data.accountNonLocked;
instance.accountNonExpired = data.accountNonExpired;
instance.credentialsNonExpired = data.credentialsNonExpired;
- instance.accountNonLocked = data.accountNonLocked;
instance.principalId = data.principalId;
instance.rev = data.rev;
instance.accountEnabled = data.accountEnabled;
instance.accountLocked = data.accountLocked;
instance.accountExpired = data.accountExpired;
- instance.principalName = data.principalName;
instance.ownSources = __getCopyArrayFn(Element.fromData)(data.ownSources);
instance.ownSepas = __getCopyArrayFn(Element.fromData)(data.ownSepas);
instance.ownActions = __getCopyArrayFn(Element.fromData)(data.ownActions);
instance.roles = __getCopyArrayFn(__identity<Role>())(data.roles);
+ instance.principalType = data.principalType;
return instance;
}
}
@@ -189,6 +189,20 @@ export class RawUserApiToken {
}
}
+export class ServiceAccount extends Principal {
+ clientSecret: string;
+
+ static fromData(data: ServiceAccount, target?: ServiceAccount): ServiceAccount {
+ if (!data) {
+ return data;
+ }
+ const instance = target || new ServiceAccount();
+ super.fromData(data, instance);
+ instance.clientSecret = data.clientSecret;
+ return instance;
+ }
+}
+
export class UserAccount extends Principal {
darkMode: boolean;
email: string;
@@ -248,14 +262,14 @@ export class UserInfo {
email: string;
roles: string[];
showTutorial: boolean;
- userId: string;
+ username: string;
static fromData(data: UserInfo, target?: UserInfo): UserInfo {
if (!data) {
return data;
}
const instance = target || new UserInfo();
- instance.userId = data.userId;
+ instance.username = data.username;
instance.displayName = data.displayName;
instance.email = data.email;
instance.roles = __getCopyArrayFn(__identity<string>())(data.roles);
@@ -265,7 +279,9 @@ export class UserInfo {
}
}
-export type Role = "SYSTEM_ADMINISTRATOR" | "MANAGER" | "OPERATOR" | "DIMENSION_OPERATOR" | "USER_DEMO" | "BUSINESS_ANALYST";
+export type PrincipalType = "USER_ACCOUNT" | "SERVICE_ACCOUNT";
+
+export type Role = "ADMIN" | "PIPELINE_ADMIN" | "DASHBOARD_ADMIN" | "DATA_EXPLORER_ADMIN" | "CONNECT_ADMIN" | "DASHBOARD_USER" | "DATA_EXPLORER_USER" | "PIPELINE_USER" | "APP_USER";
function __getCopyArrayFn<T>(itemCopyFn: (item: T) => T): (array: T[]) => T[] {
return (array: T[]) => __copyArray(array, itemCopyFn);
diff --git a/ui/src/app/core-ui/split-section/split-section.component.scss b/ui/src/app/core-ui/split-section/split-section.component.scss
index 2d75a6f..fa1ede2 100644
--- a/ui/src/app/core-ui/split-section/split-section.component.scss
+++ b/ui/src/app/core-ui/split-section/split-section.component.scss
@@ -17,7 +17,9 @@
*/
:host {
+ margin-top: 20px;
width: 100%;
+ margin-bottom: 20px;
}
.split-section-title {
diff --git a/ui/src/app/core/components/toolbar/toolbar.component.ts b/ui/src/app/core/components/toolbar/toolbar.component.ts
index 3cd5c33..ee52a28 100644
--- a/ui/src/app/core/components/toolbar/toolbar.component.ts
+++ b/ui/src/app/core/components/toolbar/toolbar.component.ts
@@ -55,7 +55,7 @@ export class ToolbarComponent extends BaseNavigationComponent implements OnInit
this.getVersion();
this.authService.user$.subscribe(user => {
this.userEmail = user.displayName;
- this.profileService.getUserProfile().subscribe(userInfo => {
+ this.profileService.getUserProfile(user.username).subscribe(userInfo => {
this.darkMode = this.authService.darkMode$.getValue();
this.modifyAppearance(userInfo.darkMode);
});
diff --git a/ui/src/app/platform-services/apis/user.service.ts b/ui/src/app/platform-services/apis/user.service.ts
new file mode 100644
index 0000000..56e9979
--- /dev/null
+++ b/ui/src/app/platform-services/apis/user.service.ts
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { map } from 'rxjs/operators';
+import { Observable } from 'rxjs';
+import { PlatformServicesCommons } from './commons.service';
+import { ServiceAccount, UserAccount } from '../../core-model/gen/streampipes-model-client';
+
+@Injectable()
+export class UserService {
+
+ constructor(private http: HttpClient,
+ private platformServicesCommons: PlatformServicesCommons) {
+ }
+
+ getAllUserAccounts(): Observable<UserAccount[]> {
+ return this.http.get(`${this.usersPath}?type=USER_ACCOUNT`)
+ .pipe(map(response => {
+ return (response as any[]).map(p => UserAccount.fromData(p));
+ }));
+ }
+
+ getAllServiceAccounts(): Observable<ServiceAccount[]> {
+ return this.http.get(`${this.usersPath}?type=SERVICE_ACCOUNT`)
+ .pipe(map(response => {
+ return (response as any[]).map(p => ServiceAccount.fromData(p));
+ }));
+ }
+
+ public updateUser(user: (UserAccount)): Observable<any> {
+ return this.http.put(`${this.usersPath}/user/${user.principalId}`, user);
+ }
+
+ public updateService(user: (ServiceAccount)): Observable<any> {
+ return this.http.put(`${this.usersPath}/service/${user.principalId}`, user);
+ }
+
+ public createUser(user: UserAccount): Observable<any> {
+ return this.http.post(`${this.usersPath}/user`, user);
+ }
+
+ public createServiceAccount(user: ServiceAccount): Observable<any> {
+ return this.http.post(`${this.usersPath}/service`, user);
+ }
+
+ public deleteUser(principalId: string): Observable<any> {
+ return this.http.delete(`${this.usersPath}/${principalId}`);
+ }
+
+ private get usersPath() {
+ return this.platformServicesCommons.apiBasePath + '/users';
+ }
+}
diff --git a/ui/src/app/platform-services/platform.module.ts b/ui/src/app/platform-services/platform.module.ts
index ee9e3a4..8a3ed07 100644
--- a/ui/src/app/platform-services/platform.module.ts
+++ b/ui/src/app/platform-services/platform.module.ts
@@ -28,6 +28,7 @@ import { PipelineMonitoringService } from './apis/pipeline-monitoring.service';
import { SemanticTypesService } from './apis/semantic-types.service';
import { PipelineCanvasMetadataService } from './apis/pipeline-canvas-metadata.service';
import { PipelineTemplateService } from './apis/pipeline-template.service';
+import { UserService } from './apis/user.service';
@NgModule({
imports: [],
@@ -43,7 +44,8 @@ import { PipelineTemplateService } from './apis/pipeline-template.service';
PipelineMonitoringService,
PipelineService,
SemanticTypesService,
- PipelineTemplateService
+ PipelineTemplateService,
+ UserService
],
entryComponents: []
})
diff --git a/ui/src/app/profile/components/basic-profile-settings.ts b/ui/src/app/profile/components/basic-profile-settings.ts
index cb9cc33..ea7ad9b 100644
--- a/ui/src/app/profile/components/basic-profile-settings.ts
+++ b/ui/src/app/profile/components/basic-profile-settings.ts
@@ -21,6 +21,7 @@ import { UserAccount } from '../../core-model/gen/streampipes-model-client';
import { Directive } from '@angular/core';
import { AppConstants } from '../../services/app.constants';
import { JwtTokenStorageService } from '../../services/jwt-token-storage.service';
+import { AuthService } from '../../services/auth.service';
@Directive()
export abstract class BasicProfileSettings {
@@ -32,13 +33,14 @@ export abstract class BasicProfileSettings {
constructor(protected profileService: ProfileService,
public appConstants: AppConstants,
- private tokenService: JwtTokenStorageService) {
+ private tokenService: JwtTokenStorageService,
+ protected authService: AuthService) {
}
receiveUserData() {
this.profileService
- .getUserProfile()
+ .getUserProfile(this.authService.user$.getValue().username)
.subscribe(userData => {
this.userData = userData;
this.onUserDataReceived();
diff --git a/ui/src/app/profile/components/general/general-profile-settings.component.ts b/ui/src/app/profile/components/general/general-profile-settings.component.ts
index 8cefffa..1f0fa45 100644
--- a/ui/src/app/profile/components/general/general-profile-settings.component.ts
+++ b/ui/src/app/profile/components/general/general-profile-settings.component.ts
@@ -34,11 +34,11 @@ export class GeneralProfileSettingsComponent extends BasicProfileSettings implem
originalDarkMode = false;
darkModeChanged = false;
- constructor(private authService: AuthService,
+ constructor(authService: AuthService,
profileService: ProfileService,
appConstants: AppConstants,
tokenService: JwtTokenStorageService) {
- super(profileService, appConstants, tokenService);
+ super(profileService, appConstants, tokenService, authService);
}
ngOnInit(): void {
@@ -62,7 +62,7 @@ export class GeneralProfileSettingsComponent extends BasicProfileSettings implem
}
updateAppearanceMode() {
- this.profileService.updateAppearanceMode(this.darkMode).subscribe(response => {
+ this.profileService.updateAppearanceMode(this.userData.username, this.darkMode).subscribe(response => {
this.darkModeChanged = true;
});
}
diff --git a/ui/src/app/profile/components/token/token-management-settings.component.ts b/ui/src/app/profile/components/token/token-management-settings.component.ts
index dfced55..d44e866 100644
--- a/ui/src/app/profile/components/token/token-management-settings.component.ts
+++ b/ui/src/app/profile/components/token/token-management-settings.component.ts
@@ -16,10 +16,10 @@
*
*/
-import {Component, OnInit} from "@angular/core";
-import {BasicProfileSettings} from "../basic-profile-settings";
-import {RawUserApiToken, UserApiToken} from "../../../core-model/gen/streampipes-model-client";
-import {MatTableDataSource} from "@angular/material/table";
+import { Component, OnInit } from "@angular/core";
+import { BasicProfileSettings } from "../basic-profile-settings";
+import { RawUserApiToken, UserApiToken } from "../../../core-model/gen/streampipes-model-client";
+import { MatTableDataSource } from "@angular/material/table";
@Component({
selector: 'token-management-settings',
@@ -41,7 +41,7 @@ export class TokenManagementSettingsComponent extends BasicProfileSettings imple
requestNewKey() {
let baseToken: RawUserApiToken = this.makeBaseToken();
- this.profileService.requestNewApiToken(baseToken).subscribe(result => {
+ this.profileService.requestNewApiToken(this.userData.username, baseToken).subscribe(result => {
this.newlyCreatedToken = result;
this.newTokenCreated = true;
this.newTokenName = "";
@@ -60,7 +60,7 @@ export class TokenManagementSettingsComponent extends BasicProfileSettings imple
this.userData.userApiTokens.splice(removeIndex, 1);
this.profileService.updateUserProfile(this.userData).subscribe(response => {
this.receiveUserData();
- })
+ });
}
onUserDataReceived() {
diff --git a/ui/src/app/profile/profile.service.ts b/ui/src/app/profile/profile.service.ts
index 262e89c..dc4b7a6 100644
--- a/ui/src/app/profile/profile.service.ts
+++ b/ui/src/app/profile/profile.service.ts
@@ -32,32 +32,33 @@ export class ProfileService {
}
- getUserProfile(): Observable<UserAccount> {
- return this.http.get(this.profilePath).pipe(map(response => {
+ getUserProfile(username: string): Observable<UserAccount> {
+ return this.http.get(this.profilePath + '/' + username).pipe(map(response => {
return UserAccount.fromData(response as any);
}));
}
updateUserProfile(userData: UserAccount): Observable<Message> {
- return this.http.put(this.profilePath, userData).pipe(map(response => {
+ return this.http.put(this.profilePath + '/' + userData.username, userData).pipe(map(response => {
return Message.fromData(response as any);
}));
}
- updateAppearanceMode(darkMode: boolean): Observable<Message> {
- return this.http.put(`${this.profilePath}/appearance/mode/${darkMode}`, {}).pipe(map(response => {
+ updateAppearanceMode(username, darkMode: boolean): Observable<Message> {
+ return this.http.put(`${this.profilePath}/${username}/appearance/mode/${darkMode}`, {}).pipe(map(response => {
return Message.fromData(response as any);
}));
}
- requestNewApiToken(baseToken: RawUserApiToken): Observable<RawUserApiToken> {
- return this.http.post(this.profilePath + '/tokens', baseToken)
+ requestNewApiToken(username: string,
+ baseToken: RawUserApiToken): Observable<RawUserApiToken> {
+ return this.http.post(this.profilePath + '/' + username + '/tokens', baseToken)
.pipe(map(response => {
return RawUserApiToken.fromData(response as any);
}));
}
private get profilePath(): string {
- return this.platformServicesCommons.apiBasePath + '/users/profile';
+ return this.platformServicesCommons.apiBasePath + '/users';
}
}
diff --git a/ui/src/app/services/auth.service.ts b/ui/src/app/services/auth.service.ts
index cf2e33c..c6eff77 100644
--- a/ui/src/app/services/auth.service.ts
+++ b/ui/src/app/services/auth.service.ts
@@ -55,7 +55,6 @@ 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(decodedToken.user);
this.authToken$.next(data.accessToken);
@@ -145,14 +144,10 @@ export class AuthService {
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;
}
diff --git a/ui/src/scss/sp/widgets.scss b/ui/src/scss/sp/widgets.scss
index 26b6539..58f8c6e 100644
--- a/ui/src/scss/sp/widgets.scss
+++ b/ui/src/scss/sp/widgets.scss
@@ -39,3 +39,17 @@
overflow-y: auto;
overflow-x: auto;
}
+
+.general-options-panel {
+ margin-top: 4px;
+ margin-bottom: 4px;
+ border: 1px solid var(--color-bg-3);
+ background: var(--color-bg-1);
+ padding: 5px;
+}
+
+.general-options-header {
+ margin-right: 10px;
+ margin-bottom: 10px;
+ font-weight: bold;
+}