diff --git a/caosdb-proto b/caosdb-proto index 4e0552f3bd0e40c96ec534f605f38ed7650bcdf1..9c22dfb9fbaf32d4072f84e4e04aae891aff5919 160000 --- a/caosdb-proto +++ b/caosdb-proto @@ -1 +1 @@ -Subproject commit 4e0552f3bd0e40c96ec534f605f38ed7650bcdf1 +Subproject commit 9c22dfb9fbaf32d4072f84e4e04aae891aff5919 diff --git a/src/main/java/org/caosdb/server/accessControl/Role.java b/src/main/java/org/caosdb/server/accessControl/Role.java index 1938d9f18926601a9eb4a0fb081c9738d5c7f501..32ffb4476dff71cf5b6250ade9968da5346e4a7e 100644 --- a/src/main/java/org/caosdb/server/accessControl/Role.java +++ b/src/main/java/org/caosdb/server/accessControl/Role.java @@ -24,15 +24,17 @@ package org.caosdb.server.accessControl; import java.io.Serializable; import java.util.LinkedList; +import org.caosdb.server.database.proto.ProtoUser; import org.caosdb.server.permissions.PermissionRule; import org.jdom2.Element; public class Role implements Serializable { - private static final long serialVersionUID = 8968219504349206982L; + private static final long serialVersionUID = -243913823L; public String name = null; public String description = null; public LinkedList<PermissionRule> permission_rules = null; + public LinkedList<ProtoUser> users = null; public Element toElement() { final Element ret = new Element("Role"); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteRole.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteRole.java index 8fd8d35fda6b99f7d79b245a10ace4ed9521e0ce..006e8d4eb266dd895eb3b2b6b717ab3606e978c7 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteRole.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteRole.java @@ -24,9 +24,11 @@ package org.caosdb.server.database.backend.implementation.MySQL; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.sql.SQLIntegrityConstraintViolationException; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.DeleteRoleImpl; import org.caosdb.server.database.exceptions.TransactionException; +import org.caosdb.server.utils.ServerMessages; public class MySQLDeleteRole extends MySQLTransaction implements DeleteRoleImpl { @@ -42,6 +44,8 @@ public class MySQLDeleteRole extends MySQLTransaction implements DeleteRoleImpl final PreparedStatement stmt = prepareStatement(STMT_DELETE_ROLE); stmt.setString(1, role); stmt.execute(); + } catch (final SQLIntegrityConstraintViolationException e) { + throw new TransactionException(ServerMessages.ROLE_CANNOT_BE_DELETED); } catch (final SQLException e) { throw new TransactionException(e); } catch (final ConnectionException e) { diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveRole.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveRole.java index 95dfd2cfeecceb6d3751cba876484c7f5d7a66f9..b622967516cea869483a74f7fd39a7bd779f5f06 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveRole.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveRole.java @@ -31,6 +31,7 @@ import org.caosdb.server.accessControl.Role; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.RetrieveRoleImpl; import org.caosdb.server.database.exceptions.TransactionException; +import org.caosdb.server.database.proto.ProtoUser; import org.caosdb.server.permissions.PermissionRule; import org.eclipse.jetty.util.ajax.JSON; @@ -42,31 +43,53 @@ public class MySQLRetrieveRole extends MySQLTransaction implements RetrieveRoleI public static final String STMT_RETRIEVE_ROLE = "SELECT r.description AS description, p.permissions AS permissions FROM roles AS r LEFT JOIN permissions AS p ON (r.name = p.role) WHERE r.name=?"; + public static final String STMT_RETRIEVE_USERS = + "SELECT u.realm, u.user FROM user_roles AS u WHERE u.role = ?"; @Override public Role retrieve(final String role) throws TransactionException { - try { - final PreparedStatement stmt = prepareStatement(STMT_RETRIEVE_ROLE); + Role ret = null; + try (final PreparedStatement stmt = prepareStatement(STMT_RETRIEVE_ROLE)) { stmt.setString(1, role); final ResultSet rs = stmt.executeQuery(); - try { - if (rs.next()) { - final Role ret = new Role(); - ret.name = role; - ret.description = rs.getString("description"); - ret.permission_rules = parse(rs.getString("permissions")); - return ret; - } else { - return null; + if (rs.next()) { + ret = new Role(); + ret.name = role; + ret.description = rs.getString("description"); + ret.permission_rules = parse(rs.getString("permissions")); + } else { + return null; + } + } catch (final SQLException e) { + throw new TransactionException(e); + } catch (final ConnectionException e) { + throw new TransactionException(e); + } + + try (final PreparedStatement stmt = prepareStatement(STMT_RETRIEVE_USERS)) { + stmt.setString(1, role); + final ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + ret.users = new LinkedList<>(); + + ret.users.add(nextUser(rs)); + while (rs.next()) { + ret.users.add(nextUser(rs)); } - } finally { - rs.close(); } } catch (final SQLException e) { throw new TransactionException(e); } catch (final ConnectionException e) { throw new TransactionException(e); } + return ret; + } + + private ProtoUser nextUser(ResultSet rs) throws SQLException { + ProtoUser nextUser = new ProtoUser(); + nextUser.realm = rs.getString("realm"); + nextUser.name = rs.getString("user"); + return nextUser; } @SuppressWarnings("unchecked") diff --git a/src/main/java/org/caosdb/server/database/proto/ProtoUser.java b/src/main/java/org/caosdb/server/database/proto/ProtoUser.java index cafa32d2580c8a09a97e1cd3ee48c2ec59a454cf..91dfe9aa63f4f3e04581e068bb1494543c504bff 100644 --- a/src/main/java/org/caosdb/server/database/proto/ProtoUser.java +++ b/src/main/java/org/caosdb/server/database/proto/ProtoUser.java @@ -23,19 +23,19 @@ package org.caosdb.server.database.proto; import java.io.Serializable; -import java.util.Set; +import java.util.HashSet; import org.caosdb.server.accessControl.UserStatus; public class ProtoUser implements Serializable { public ProtoUser() {} - private static final long serialVersionUID = -2704114543883567439L; + private static final long serialVersionUID = 5381234723597234L; public UserStatus status = null; public String name = null; public String email = null; public Integer entity = null; public String realm = null; - public Set<String> roles = null; + public HashSet<String> roles = null; } diff --git a/src/main/java/org/caosdb/server/entity/Message.java b/src/main/java/org/caosdb/server/entity/Message.java index 4c89e1ee23839483f7bed1099c52e8c24a56a6a2..e206137e177cc761bf26b34790985b43b2a7e64a 100644 --- a/src/main/java/org/caosdb/server/entity/Message.java +++ b/src/main/java/org/caosdb/server/entity/Message.java @@ -35,6 +35,11 @@ public class Message extends Exception implements Comparable<Message>, ToElement private final String description; private final String body; + @Override + public String getMessage() { + return description; + } + @Deprecated private static final Map<String, String> legacy_codes_mapping = new HashMap<>(); static void init() { diff --git a/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java b/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java index 182d5cb5dd194db4475f3a923a9bf207eb7ab498..dbfa1512aa14bfd1652eefb5065498e654435294 100644 --- a/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java +++ b/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java @@ -1,14 +1,21 @@ package org.caosdb.server.grpc; +import io.grpc.Status; +import io.grpc.StatusException; import io.grpc.stub.StreamObserver; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authz.UnauthorizedException; +import org.apache.shiro.subject.Subject; import org.caosdb.api.acm.v1alpha1.AccessControlManagementServiceGrpc.AccessControlManagementServiceImplBase; import org.caosdb.api.acm.v1alpha1.CreateSingleRoleRequest; import org.caosdb.api.acm.v1alpha1.CreateSingleRoleResponse; import org.caosdb.api.acm.v1alpha1.CreateSingleUserRequest; import org.caosdb.api.acm.v1alpha1.CreateSingleUserResponse; +import org.caosdb.api.acm.v1alpha1.DeleteSingleRoleRequest; +import org.caosdb.api.acm.v1alpha1.DeleteSingleRoleResponse; import org.caosdb.api.acm.v1alpha1.ListRolesRequest; import org.caosdb.api.acm.v1alpha1.ListRolesResponse; import org.caosdb.api.acm.v1alpha1.ListUsersRequest; @@ -18,16 +25,22 @@ import org.caosdb.api.acm.v1alpha1.RetrieveSingleRoleRequest; import org.caosdb.api.acm.v1alpha1.RetrieveSingleRoleResponse; import org.caosdb.api.acm.v1alpha1.RetrieveSingleUserRequest; import org.caosdb.api.acm.v1alpha1.RetrieveSingleUserResponse; +import org.caosdb.api.acm.v1alpha1.UpdateSingleRoleRequest; +import org.caosdb.api.acm.v1alpha1.UpdateSingleRoleResponse; import org.caosdb.api.acm.v1alpha1.User; import org.caosdb.api.acm.v1alpha1.UserStatus; +import org.caosdb.server.accessControl.AuthenticationUtils; import org.caosdb.server.accessControl.Role; import org.caosdb.server.database.proto.ProtoUser; +import org.caosdb.server.transaction.DeleteRoleTransaction; import org.caosdb.server.transaction.InsertRoleTransaction; import org.caosdb.server.transaction.InsertUserTransaction; import org.caosdb.server.transaction.ListRolesTransaction; import org.caosdb.server.transaction.ListUsersTransaction; import org.caosdb.server.transaction.RetrieveRoleTransaction; import org.caosdb.server.transaction.RetrieveUserTransaction; +import org.caosdb.server.transaction.UpdateRoleTransaction; +import org.caosdb.server.utils.ServerMessages; public class AccessControlManagementServiceImpl extends AccessControlManagementServiceImplBase { @@ -70,16 +83,16 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS private User.Builder convert(ProtoUser user) { User.Builder result = User.newBuilder(); - result.setStatus(convert(user.status)); result.setRealm(user.realm); result.setName(user.name); + if (user.status != null) result.setStatus(convert(user.status)); if (user.email != null) { result.setEmail(user.email); } if (user.entity != null) { result.setEntityId(Integer.toString(user.entity)); } - if (user.roles != null) { + if (user.roles != null && !user.roles.isEmpty()) { result.addAllRoles(user.roles); } return result; @@ -167,7 +180,31 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS RetrieveRoleTransaction transaction = new RetrieveRoleTransaction(request.getName()); transaction.execute(); - return RetrieveSingleRoleResponse.newBuilder().setRole(convert(transaction.getRole())).build(); + Role role = transaction.getRole(); + RetrieveSingleRoleResponse.Builder builder = + RetrieveSingleRoleResponse.newBuilder().setRole(convert(transaction.getRole())); + if (role.users != null && !role.users.isEmpty()) + role.users.forEach( + (u) -> { + builder.addUsers(convert(u)); + }); + return builder.build(); + } + + private DeleteSingleRoleResponse deleteSingleRoleTransaction(DeleteSingleRoleRequest request) + throws Exception { + DeleteRoleTransaction transaction = new DeleteRoleTransaction(request.getName()); + transaction.execute(); + + return DeleteSingleRoleResponse.newBuilder().build(); + } + + private UpdateSingleRoleResponse updateSingleRoleTransaction(UpdateSingleRoleRequest request) + throws Exception { + Role role = convert(request.getRole()); + UpdateRoleTransaction transaction = new UpdateRoleTransaction(role); + transaction.execute(); + return UpdateSingleRoleResponse.newBuilder().build(); } ////////////////// ... for users @@ -208,8 +245,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS responseObserver.onCompleted(); } catch (final Exception e) { - e.printStackTrace(); - responseObserver.onError(e); + handleException(responseObserver, e); } } @@ -222,8 +258,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS responseObserver.onCompleted(); } catch (final Exception e) { - e.printStackTrace(); - responseObserver.onError(e); + handleException(responseObserver, e); } } @@ -236,8 +271,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS responseObserver.onCompleted(); } catch (final Exception e) { - e.printStackTrace(); - responseObserver.onError(e); + handleException(responseObserver, e); } } @@ -251,8 +285,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS responseObserver.onCompleted(); } catch (final Exception e) { - e.printStackTrace(); - responseObserver.onError(e); + handleException(responseObserver, e); } } @@ -265,8 +298,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS responseObserver.onCompleted(); } catch (final Exception e) { - e.printStackTrace(); - responseObserver.onError(e); + handleException(responseObserver, e); } } @@ -280,8 +312,57 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS responseObserver.onCompleted(); } catch (final Exception e) { - e.printStackTrace(); - responseObserver.onError(e); + handleException(responseObserver, e); + } + } + + @Override + public void deleteSingleRole( + DeleteSingleRoleRequest request, StreamObserver<DeleteSingleRoleResponse> responseObserver) { + try { + final DeleteSingleRoleResponse response = deleteSingleRoleTransaction(request); + responseObserver.onNext(response); + responseObserver.onCompleted(); + + } catch (final Exception e) { + handleException(responseObserver, e); + } + } + + @Override + public void updateSingleRole( + UpdateSingleRoleRequest request, StreamObserver<UpdateSingleRoleResponse> responseObserver) { + try { + final UpdateSingleRoleResponse response = updateSingleRoleTransaction(request); + responseObserver.onNext(response); + responseObserver.onCompleted(); + + } catch (final Exception e) { + handleException(responseObserver, e); + } + } + + private void handleException(StreamObserver<?> responseObserver, Exception e) { + if (e instanceof UnauthorizedException) { + Subject subject = SecurityUtils.getSubject(); + if (AuthenticationUtils.isAnonymous(subject)) { + responseObserver.onError( + new StatusException(Status.UNAUTHENTICATED.withDescription("Please login!"))); + return; + } else { + responseObserver.onError( + new StatusException( + Status.PERMISSION_DENIED.withCause(e).withDescription(e.getMessage()))); + return; + } + } else if (e == ServerMessages.ROLE_DOES_NOT_EXIST + || e == ServerMessages.ACCOUNT_DOES_NOT_EXIST) { + responseObserver.onError( + new StatusException(Status.NOT_FOUND.withDescription(e.getMessage()).withCause(e))); + return; } + e.printStackTrace(); + responseObserver.onError( + new StatusException(Status.UNKNOWN.withDescription(e.getMessage()).withCause(e))); } } diff --git a/src/main/java/org/caosdb/server/transaction/DeleteRoleTransaction.java b/src/main/java/org/caosdb/server/transaction/DeleteRoleTransaction.java index ad9a9925f8bed2ef03a1bcf9cd6b8c79adca6345..23c3126108de6f5a03039adb2459d91c4d1bf22a 100644 --- a/src/main/java/org/caosdb/server/transaction/DeleteRoleTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/DeleteRoleTransaction.java @@ -27,6 +27,8 @@ import org.caosdb.server.accessControl.ACMPermissions; import org.caosdb.server.database.backend.transaction.DeleteRole; import org.caosdb.server.database.backend.transaction.RetrieveRole; import org.caosdb.server.database.backend.transaction.SetPermissionRules; +import org.caosdb.server.database.exceptions.TransactionException; +import org.caosdb.server.entity.Message; import org.caosdb.server.utils.ServerMessages; public class DeleteRoleTransaction extends AccessControlTransaction { @@ -41,10 +43,19 @@ public class DeleteRoleTransaction extends AccessControlTransaction { protected void transaction() throws Exception { SecurityUtils.getSubject().checkPermission(ACMPermissions.PERMISSION_DELETE_ROLE(this.name)); + if (this.name == "administration" || this.name == "anonymous") { + throw ServerMessages.SPECIAL_ROLE_CANNOT_BE_DELETED; + } if (execute(new RetrieveRole(this.name), getAccess()).getRole() == null) { throw ServerMessages.ROLE_DOES_NOT_EXIST; } execute(new SetPermissionRules(this.name, null), getAccess()); - execute(new DeleteRole(this.name), getAccess()); + try { + execute(new DeleteRole(this.name), getAccess()); + } catch (TransactionException e) { + if (e.getCause() == ServerMessages.ROLE_CANNOT_BE_DELETED) { + throw (Message) e.getCause(); + } + } } } diff --git a/src/main/java/org/caosdb/server/utils/ServerMessages.java b/src/main/java/org/caosdb/server/utils/ServerMessages.java index 4197f2cf78cedb3fcf9dc0ea6648b6c3335a9da8..cc844fee1399cb03e893efd6adbc7bde6b64dcc9 100644 --- a/src/main/java/org/caosdb/server/utils/ServerMessages.java +++ b/src/main/java/org/caosdb/server/utils/ServerMessages.java @@ -267,6 +267,18 @@ public class ServerMessages { MessageCode.MESSAGE_CODE_UNKNOWN, "Role name is already in use. Choose a different name."); + public static final Message ROLE_CANNOT_BE_DELETED = + new Message( + MessageType.Error, + MessageCode.MESSAGE_CODE_UNKNOWN, + "Role cannot be deleted because there are still users with this role."); + + public static final Message SPECIAL_ROLE_CANNOT_BE_DELETED = + new Message( + MessageType.Error, + MessageCode.MESSAGE_CODE_UNKNOWN, + "This special role cannot be deleted. Ever."); + public static final Message QUERY_EXCEPTION = new Message( MessageType.Error,