diff --git a/caosdb-proto b/caosdb-proto index a66508445c9fe9c4bc3b55eb3bdaff147670bd39..2f3e4ad1cf515450fcfedb300f66198b82122b7e 160000 --- a/caosdb-proto +++ b/caosdb-proto @@ -1 +1 @@ -Subproject commit a66508445c9fe9c4bc3b55eb3bdaff147670bd39 +Subproject commit 2f3e4ad1cf515450fcfedb300f66198b82122b7e diff --git a/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java b/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java index 84844e892297e68d2ad22cbf5dad935612d1c4f4..264af44456586fc84e4d391047d5f9c18c8cc28a 100644 --- a/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java +++ b/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,79 +18,269 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.accessControl; -public class ACMPermissions { +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import org.caosdb.server.jobs.core.AccessControl; +import org.caosdb.server.jobs.core.CheckStateTransition; +import org.caosdb.server.scripting.ScriptingPermissions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ACMPermissions implements Comparable<ACMPermissions> { + + private static Logger LOGGER = LoggerFactory.getLogger(ACMPermissions.class); + public static final String USER_PARAMETER = "?USER?"; + public static final String REALM_PARAMETER = "?REALM?"; + public static final String ROLE_PARAMETER = "?ROLE?"; + public static Set<ACMPermissions> ALL = new HashSet<>(); + public static final ACMPermissions GENERIC_ACM_PERMISSION = + new ACMPermissions( + "ACM:*", + "Permissions to administrate the access controll management system. That includes managing users, roles, and assigning permissions to roles and roles to users."); + + protected final String permission; + protected final String description; + + public ACMPermissions(String permission, String description) { + if (permission == null) { + throw new NullPointerException("Permission must no be null"); + } + this.permission = permission; + this.description = description; + ALL.add(this); + } + + @Override + public final boolean equals(Object obj) { + if (obj instanceof ACMPermissions) { + ACMPermissions that = (ACMPermissions) obj; + return this.permission.equals(that.permission); + } + return false; + } + + @Override + public final int hashCode() { + return permission.hashCode(); + } + + @Override + public final String toString() { + return this.permission; + } + + public final String getDescription() { + return description; + } + + public static final class UserPermission extends ACMPermissions { + + public static final ACMPermissions GENERIC_USER_PERMISSION = + new ACMPermissions( + "ACM:USER:*", + "Permissions to manage users, i.e. create, retrieve, update and delete users."); + + public UserPermission(String permission, String description) { + super(permission, description); + } + + public String toString(String realm) { + return toString().replace(REALM_PARAMETER, realm); + } + + public String toString(String realm, String user) { + return toString(realm).replace(USER_PARAMETER, user); + } + } + + public static final class RolePermission extends ACMPermissions { + + public static final ACMPermissions GENERIC_ROLE_PERMISSION = + new ACMPermissions( + "ACM:ROLE:*", + "Permissions to manage roles, i.e. create, retrieve, update and delete roles and assign them to users."); + + public RolePermission(String permission, String description) { + super(permission, description); + } + + public String toString(String role) { + return toString().replace(ROLE_PARAMETER, role); + } + } + + public static final String PERMISSION_ACCESS_SERVER_PROPERTIES = + new ACMPermissions("ACCESS_SERVER_PROPERTIES", "Permission to read the server properties.") + .toString(); + + @Deprecated + public static final String PERMISSION_RETRIEVE_SERVERLOGS = + new ACMPermissions("SERVERLOGS:RETRIEVE", "Permission to read the server logs. (DEPRECATED)") + .toString(); - public static final String PERMISSION_ACCESS_SERVER_PROPERTIES = "ACCESS_SERVER_PROPERTIES"; - public static final String PERMISSION_RETRIEVE_SERVERLOGS = "SERVERLOGS:RETRIEVE"; + private static UserPermission retrieve_user_roles = + new UserPermission( + "ACM:USER:RETRIEVE:ROLES:" + REALM_PARAMETER + ":" + USER_PARAMETER, + "Permission to retrieve the roles of a user"); public static final String PERMISSION_RETRIEVE_USER_ROLES( final String realm, final String username) { - return "ACM:USER:RETRIEVE:ROLES:" + realm + ":" + username; + return retrieve_user_roles.toString(realm, username); } + private static UserPermission retrieve_user_info = + new UserPermission( + "ACM:USER:RETRIEVE:INFO:" + REALM_PARAMETER + ":" + USER_PARAMETER, + "Permission to retrieve the user info (email, entity, status)"); + public static final String PERMISSION_RETRIEVE_USER_INFO( final String realm, final String username) { - return "ACM:USER:RETRIEVE:INFO:" + realm + ":" + username; + return retrieve_user_info.toString(realm, username); } + private static UserPermission delete_user = + new UserPermission( + "ACM:USER:DELETE:" + REALM_PARAMETER + ":" + USER_PARAMETER, + "Permission to delete a user"); + public static String PERMISSION_DELETE_USER(final String realm, final String username) { - return "ACM:USER:DELETE:" + realm + ":" + username; + return delete_user.toString(realm, username); } + private static final UserPermission insert_user = + new UserPermission( + "ACM:USER:INSERT:" + REALM_PARAMETER, "Permission to create a user in the given realm"); + public static String PERMISSION_INSERT_USER(final String realm) { - return "ACM:USER:INSERT:" + realm; + return insert_user.toString(realm); } + private static final UserPermission update_user_password = + new UserPermission( + "ACM:USER:UPDATE_PASSWORD:" + REALM_PARAMETER + ":" + USER_PARAMETER, + "Permission to set the password of a user."); + public static String PERMISSION_UPDATE_USER_PASSWORD(final String realm, final String username) { - return "ACM:USER:UPDATE_PASSWORD:" + realm + ":" + username; + return update_user_password.toString(realm, username); } + private static final UserPermission update_user_email = + new UserPermission( + "ACM:USER:UPDATE:EMAIL:" + REALM_PARAMETER + ":" + USER_PARAMETER, + "Permission to update the email address of a user."); + public static String PERMISSION_UPDATE_USER_EMAIL(final String realm, final String username) { - return "ACM:USER:UPDATE:EMAIL:" + realm + ":" + username; + return update_user_email.toString(realm, username); } + private static final UserPermission update_user_status = + new UserPermission( + "ACM:USER:UPDATE:STATUS:" + REALM_PARAMETER + ":" + USER_PARAMETER, + "Permission to update the status of a user, i.e. marking them as ACTIVE or INACTIVE."); + public static String PERMISSION_UPDATE_USER_STATUS(final String realm, final String username) { - return "ACM:USER:UPDATE:STATUS:" + realm + ":" + username; + return update_user_status.toString(realm, username); } + private static final UserPermission update_user_entity = + new UserPermission( + "ACM:USER:UPDATE:ENTITY:" + REALM_PARAMETER + ":" + USER_PARAMETER, + "Permission to set the entity which is associated with a user."); + public static String PERMISSION_UPDATE_USER_ENTITY(final String realm, final String username) { - return "ACM:USER:UPDATE:ENTITY:" + realm + ":" + username; + return update_user_entity.toString(realm, username); } + private static final UserPermission update_user_roles = + new UserPermission( + "ACM:USER:UPDATE:ROLES:" + REALM_PARAMETER + ":" + USER_PARAMETER, + "Permission to change the roles of a user."); + public static String PERMISSION_UPDATE_USER_ROLES(final String realm, final String username) { - return "ACM:USER:UPDATE:ROLES:" + realm + ":" + username; + return update_user_roles.toString(realm, username); } + private static final RolePermission insert_role = + new RolePermission("ACM:ROLE:INSERT", "Permission to create a new role."); + public static String PERMISSION_INSERT_ROLE() { - return "ACM:ROLE:INSERT"; + return insert_role.toString(); } + private static final RolePermission update_role_description = + new RolePermission( + "ACM:ROLE:UPDATE:DESCRIPTION:" + ROLE_PARAMETER, + "Permission to update the description of a role."); + public static String PERMISSION_UPDATE_ROLE_DESCRIPTION(final String role) { - return "ACM:ROLE:UPDATE:DESCRIPTION:" + role; + return update_role_description.toString(role); } + private static final RolePermission retrieve_role_description = + new RolePermission( + "ACM:ROLE:RETRIEVE:DESCRIPTION:" + ROLE_PARAMETER, + "Permission to retrieve the description of a role."); + public static String PERMISSION_RETRIEVE_ROLE_DESCRIPTION(final String role) { - return "ACM:ROLE:RETRIEVE:DESCRIPTION:" + role; + return retrieve_role_description.toString(role); } + private static final RolePermission delete_role = + new RolePermission("ACM:ROLE:DELETE:" + ROLE_PARAMETER, "Permission to delete a role."); + public static String PERMISSION_DELETE_ROLE(final String role) { - return "ACM:ROLE:DELETE:" + role; + return delete_role.toString(role); } + private static final RolePermission update_role_permissions = + new RolePermission( + "ACM:ROLE:UPDATE:PERMISSIONS:" + ROLE_PARAMETER, + "Permission to set the permissions of a role."); + public static String PERMISSION_UPDATE_ROLE_PERMISSIONS(final String role) { - return "ACM:ROLE:UPDATE:PERMISSIONS:" + role; + return update_role_permissions.toString(role); } + private static final RolePermission retrieve_role_permissions = + new RolePermission( + "ACM:ROLE:RETRIEVE:PERMISSIONS:" + ROLE_PARAMETER, + "Permission to read the permissions of a role."); + public static String PERMISSION_RETRIEVE_ROLE_PERMISSIONS(final String role) { - return "ACM:ROLE:RETRIEVE:PERMISSIONS:" + role; + return retrieve_role_permissions.toString(role); } + private static final RolePermission assign_role = + new RolePermission( + "ACM:ROLE:ASSIGN:" + ROLE_PARAMETER, "Permission to assign a role (to a user)."); + public static String PERMISSION_ASSIGN_ROLE(final String role) { - return "ACM:ROLE:ASSIGN:" + role; + return assign_role.toString(role); + } + + static { + // trigger adding all permissions to ALL + LOGGER.debug("Register permissions: ", ScriptingPermissions.PERMISSION_EXECUTION("*")); + LOGGER.debug("Register permissions: ", CheckStateTransition.STATE_PERMISSIONS.toString()); + LOGGER.debug( + "Register permissions: ", CheckStateTransition.PERMISSION_STATE_FORCE_FINAL.toString()); + LOGGER.debug("Register permissions: ", AccessControl.TRANSACTION_PERMISSIONS.toString()); + } + + @Override + public int compareTo(ACMPermissions that) { + return this.toString().compareToIgnoreCase(that.toString()); + } + + public static List<ACMPermissions> getAll() { + LinkedList<ACMPermissions> result = new LinkedList<>(ALL); + Collections.sort(result); + return result; } } diff --git a/src/main/java/org/caosdb/server/accessControl/AnonymousRealm.java b/src/main/java/org/caosdb/server/accessControl/AnonymousRealm.java index 006400bf649619390d5eaa642642a73b6a94f337..92b37c0ec15b110670a55ea46b6efa1a418bb597 100644 --- a/src/main/java/org/caosdb/server/accessControl/AnonymousRealm.java +++ b/src/main/java/org/caosdb/server/accessControl/AnonymousRealm.java @@ -27,12 +27,19 @@ import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher; import org.apache.shiro.realm.AuthenticatingRealm; +import org.caosdb.server.CaosDBServer; +import org.caosdb.server.ServerProperties; public class AnonymousRealm extends AuthenticatingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { - return new SimpleAuthenticationInfo(token.getPrincipal(), null, getName()); + + if (CaosDBServer.getServerProperty(ServerProperties.KEY_AUTH_OPTIONAL) + .equalsIgnoreCase("true")) { + return new SimpleAuthenticationInfo(token.getPrincipal(), null, getName()); + } + return null; } public AnonymousRealm() { diff --git a/src/main/java/org/caosdb/server/accessControl/Principal.java b/src/main/java/org/caosdb/server/accessControl/Principal.java index fc96fb99670b51d0a128710722e2c10effed152a..7d4144557d89ddf90ec99edb190abb66ec0119aa 100644 --- a/src/main/java/org/caosdb/server/accessControl/Principal.java +++ b/src/main/java/org/caosdb/server/accessControl/Principal.java @@ -88,6 +88,6 @@ public class Principal implements ResponsibleAgent { @Override public String toString() { - return "[[" + this.realm + "]]" + this.username; + return this.username + REALM_SEPARATOR + this.realm; } } diff --git a/src/main/java/org/caosdb/server/accessControl/SessionTokenRealm.java b/src/main/java/org/caosdb/server/accessControl/SessionTokenRealm.java index d78ffb405a8470a005da54736e25fbd69340b7e5..270565be3dd8fc25408a1d7992638ef24bcfaa1c 100644 --- a/src/main/java/org/caosdb/server/accessControl/SessionTokenRealm.java +++ b/src/main/java/org/caosdb/server/accessControl/SessionTokenRealm.java @@ -36,7 +36,7 @@ public class SessionTokenRealm extends AuthenticatingRealm { final SelfValidatingAuthenticationToken sessionToken = (SelfValidatingAuthenticationToken) token; - if (sessionToken.isValid()) { + if (sessionToken.isValid() && UserSources.isActive(sessionToken)) { return new SimpleAuthenticationInfo(sessionToken, null, getName()); } return null; diff --git a/src/main/java/org/caosdb/server/accessControl/UserSources.java b/src/main/java/org/caosdb/server/accessControl/UserSources.java index 760c3e901d85c863504d1d2732083656f4502194..ad6173618770b3093e3e64d16791999b893a1a65 100644 --- a/src/main/java/org/caosdb/server/accessControl/UserSources.java +++ b/src/main/java/org/caosdb/server/accessControl/UserSources.java @@ -92,6 +92,11 @@ public class UserSources extends HashMap<String, UserSource> { if (principal.getRealm().equals(OneTimeAuthenticationToken.REALM_NAME)) { return true; } + if (principal.toString().equals(AnonymousAuthenticationToken.PRINCIPAL.toString()) + && CaosDBServer.getServerProperty(ServerProperties.KEY_AUTH_OPTIONAL) + .equalsIgnoreCase("true")) { + return true; + } UserSource userSource = instance.get(principal.getRealm()); if (userSource != null) { return userSource.isUserExisting(principal.getUsername()); @@ -315,4 +320,17 @@ public class UserSources extends HashMap<String, UserSource> { throw new AuthenticationException(e); } } + + public static boolean isActive(Principal principal) { + if (principal.getRealm().equals(OneTimeAuthenticationToken.REALM_NAME)) { + return true; + } + if (principal.getUsername().equals(AnonymousAuthenticationToken.PRINCIPAL.getUsername()) + && principal.getRealm().equals(AnonymousAuthenticationToken.PRINCIPAL.getRealm()) + && CaosDBServer.getServerProperty(ServerProperties.KEY_AUTH_OPTIONAL) + .equalsIgnoreCase("true")) { + return true; + } + return isActive(principal.getRealm(), principal.getUsername()); + } } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java index b1f445e2b8ad2d79b756e1dd662296d8e6c9a49c..568e0e53a7b80a76c533229e13c6700c4c4ff47f 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java @@ -35,7 +35,7 @@ import org.caosdb.server.accessControl.Principal; import org.caosdb.server.database.misc.DBHelper; import org.caosdb.server.transaction.ChecksumUpdater; import org.caosdb.server.transaction.TransactionInterface; -import org.caosdb.server.transaction.WriteTransaction; +import org.caosdb.server.transaction.WriteTransactionInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,7 +55,7 @@ public class MySQLHelper implements DBHelper { * * <p>In the database, this adds a row to the transaction table with SRID, user and timestamp. */ - public void initTransaction(Connection connection, WriteTransaction transaction) + public void initTransaction(Connection connection, WriteTransactionInterface transaction) throws SQLException { try (CallableStatement call = connection.prepareCall("CALL set_transaction(?,?,?,?,?)")) { @@ -86,10 +86,10 @@ public class MySQLHelper implements DBHelper { if (transaction instanceof ChecksumUpdater) { connection.setReadOnly(false); connection.setAutoCommit(false); - } else if (transaction instanceof WriteTransaction) { + } else if (transaction instanceof WriteTransactionInterface) { connection.setReadOnly(false); connection.setAutoCommit(false); - initTransaction(connection, (WriteTransaction) transaction); + initTransaction(connection, (WriteTransactionInterface) transaction); } else { connection.setReadOnly(false); connection.setAutoCommit(true); diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveRole.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveRole.java index 1e855663590d85610860fcee246a4f1ed78a31b2..f4059832095acc38d3c65b828412c09cc58b3194 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveRole.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveRole.java @@ -24,6 +24,7 @@ */ package org.caosdb.server.database.backend.transaction; +import java.util.Set; import org.apache.commons.jcs.access.behavior.ICacheAccess; import org.caosdb.server.accessControl.Role; import org.caosdb.server.caching.Cache; @@ -66,4 +67,8 @@ public class RetrieveRole extends CacheableBackendTransaction<String, Role> { public static void removeCached(final String name) { cache.remove(name); } + + public static void removeCached(Set<String> roles) { + roles.forEach(RetrieveRole::removeCached); + } } diff --git a/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java b/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java index cd8e1b8791fa7ceafcc7ccc96c3d915b173fe637..88429828f27ac843903cfcfc801847c35eedb607 100644 --- a/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java +++ b/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java @@ -230,23 +230,13 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS private Iterable<PermissionDescription> listKnownPermissions() { List<PermissionDescription> result = new LinkedList<>(); - result.add( - PermissionDescription.newBuilder() - .setPermission(ACMPermissions.PERMISSION_INSERT_ROLE()) - .setDescription("Create a new user role.") - .build()); - result.add( - PermissionDescription.newBuilder() - .setPermission(ACMPermissions.PERMISSION_INSERT_USER("?REALM?")) - .setDescription("Create a new user in the given realm.") - .build()); - result.add( - PermissionDescription.newBuilder() - .setPermission(ACMPermissions.PERMISSION_ACCESS_SERVER_PROPERTIES) - .setDescription( - "Read and set server properties on the fly when the server is in debug mode.") - .build()); - // TODO + for (ACMPermissions p : ACMPermissions.getAll()) { + result.add( + PermissionDescription.newBuilder() + .setPermission(p.toString()) + .setDescription(p.getDescription()) + .build()); + } return result; } diff --git a/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java b/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java index 4bd8f2950003e7bec8eae88bf51ed454d8b6b705..ce8a62210d3c8e849397600fc112cd3103f15df4 100644 --- a/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java +++ b/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java @@ -124,14 +124,6 @@ public class AuthInterceptor implements ServerInterceptor { final Metadata headers, final ServerCallHandler<ReqT, RespT> next) { ThreadContext.remove(); - Subject user = SecurityUtils.getSubject(); - System.out.println( - "interceptCall: " - + Long.toString(Thread.currentThread().getId()) - + " " - + Thread.currentThread().getName() - + " subject: " - + user.toString()); String authentication = headers.get(AUTHENTICATION_HEADER); if (authentication == null) { diff --git a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java index 6d1beea5de3ed107d45e03fe6461e9999402b3a2..913f677f20124a602fed834ba7738941c928e89e 100644 --- a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java +++ b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java @@ -31,6 +31,7 @@ import org.caosdb.api.entity.v1.Entity; import org.caosdb.api.entity.v1.Entity.Builder; import org.caosdb.api.entity.v1.EntityACL; import org.caosdb.api.entity.v1.EntityPermissionRule; +import org.caosdb.api.entity.v1.EntityPermissionRuleCapability; import org.caosdb.api.entity.v1.EntityResponse; import org.caosdb.api.entity.v1.EntityRole; import org.caosdb.api.entity.v1.Importance; @@ -473,24 +474,45 @@ public class CaosDBToGrpcConverters { EntityACL.Builder builder = EntityACL.newBuilder(); builder.setId(e.getId().toString()); if (e.hasEntityACL()) { - builder.addAllRules(convert(e.getEntityACL())); + builder.addAllRules(convert(e.getEntityACL(), true)); } - builder.addAllRules(convert(org.caosdb.server.permissions.EntityACL.GLOBAL_PERMISSIONS)); + builder.addAllRules(convert(org.caosdb.server.permissions.EntityACL.GLOBAL_PERMISSIONS, false)); + builder.addAllPermissions(getCurrentACLPermissions(e)); // TODO errors? return builder.build(); } + private Iterable<? extends org.caosdb.api.entity.v1.EntityPermission> getCurrentACLPermissions( + EntityInterface e) { + List<org.caosdb.api.entity.v1.EntityPermission> result = new LinkedList<>(); + if (e.hasPermission(EntityPermission.EDIT_ACL)) { + org.caosdb.api.entity.v1.EntityPermission.Builder builder = + org.caosdb.api.entity.v1.EntityPermission.newBuilder(); + result.add(builder.setName(EntityPermission.EDIT_ACL.getShortName()).build()); + } + if (e.hasPermission(EntityPermission.EDIT_PRIORITY_ACL)) { + org.caosdb.api.entity.v1.EntityPermission.Builder builder = + org.caosdb.api.entity.v1.EntityPermission.newBuilder(); + result.add(builder.setName(EntityPermission.EDIT_PRIORITY_ACL.getShortName()).build()); + } + return result; + } + private Iterable<? extends EntityPermissionRule> convert( - org.caosdb.server.permissions.EntityACL entityACL) { + org.caosdb.server.permissions.EntityACL entityACL, boolean deletable) { List<EntityPermissionRule> result = new LinkedList<>(); for (EntityACI aci : entityACL.getRules()) { - result.add( + EntityPermissionRule.Builder builder = EntityPermissionRule.newBuilder() .setGrant(aci.isGrant()) .setPriority(aci.isPriority()) .setRole(aci.getResponsibleAgent().toString()) - .addAllPermissions(convert(aci)) - .build()); + .addAllPermissions(convert(aci)); + if (deletable) { + builder.addCapabilities( + EntityPermissionRuleCapability.ENTITY_PERMISSION_RULE_CAPABILITY_DELETE); + } + result.add(builder.build()); } return result; } diff --git a/src/main/java/org/caosdb/server/grpc/GrpcToCaosDBConverters.java b/src/main/java/org/caosdb/server/grpc/GrpcToCaosDBConverters.java index 5ec896fd44899cd59520f2f45c6ceec6c55042a5..5420a8d8e809194ce2dbe894faaded794cc3a632 100644 --- a/src/main/java/org/caosdb/server/grpc/GrpcToCaosDBConverters.java +++ b/src/main/java/org/caosdb/server/grpc/GrpcToCaosDBConverters.java @@ -333,9 +333,16 @@ public class GrpcToCaosDBConverters { } private EntityInterface convert(EntityACL acl) { - UpdateEntity result = new UpdateEntity(Integer.parseInt(acl.getId()), null); - result.setEntityACL(convertAcl(acl)); - return result; + try { + Integer id = getId(acl.getId()); + UpdateEntity result = new UpdateEntity(id, null); + result.setEntityACL(convertAcl(acl)); + return result; + } catch (NumberFormatException exc) { + UpdateEntity result = new UpdateEntity(null, null); + result.addError(ServerMessages.ENTITY_DOES_NOT_EXIST); + return result; + } } private org.caosdb.server.permissions.EntityACL convertAcl(EntityACL acl) { diff --git a/src/main/java/org/caosdb/server/jobs/core/AccessControl.java b/src/main/java/org/caosdb/server/jobs/core/AccessControl.java index 408384f416e1249501d5383ea1028ced56914ded..dc596e50883dd5cd2210332b7cbab954a1e76c92 100644 --- a/src/main/java/org/caosdb/server/jobs/core/AccessControl.java +++ b/src/main/java/org/caosdb/server/jobs/core/AccessControl.java @@ -24,6 +24,7 @@ package org.caosdb.server.jobs.core; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; +import org.caosdb.server.accessControl.ACMPermissions; import org.caosdb.server.database.backend.transaction.RetrieveSparseEntity; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.wrapper.Parent; @@ -37,12 +38,47 @@ import org.caosdb.server.utils.ServerMessages; @JobAnnotation(stage = TransactionStage.INIT) public class AccessControl extends ContainerJob { + public static class TransactionPermission extends ACMPermissions { + + public static final String ENTITY_ROLE_PARAMETER = "?ENTITY_ROLE?"; + + public TransactionPermission(String permission, String description) { + super(permission, description); + } + + public final String toString(String entityRole) { + return toString().replace(ENTITY_ROLE_PARAMETER, entityRole); + } + + public final String toString(String transaction, String entityRole) { + return "TRANSACTION:" + transaction + (entityRole != null ? (":" + entityRole) : ""); + } + } + + public static final TransactionPermission TRANSACTION_PERMISSIONS = + new TransactionPermission( + "TRANSACTION:*", + "Permission to execute any writable transaction. This permission only allows to execute these transactions in general. The necessary entities permissions are not implied."); + public static final TransactionPermission UPDATE = + new TransactionPermission( + "TRANSACTION:UPDATE:" + TransactionPermission.ENTITY_ROLE_PARAMETER, + "Permission to update entities of a given role (e.g. Record, File, RecordType, or Property)."); + public static final TransactionPermission DELETE = + new TransactionPermission( + "TRANSACTION:DELETE:" + TransactionPermission.ENTITY_ROLE_PARAMETER, + "Permission to delete entities of a given role (e.g. Record, File, RecordType, or Property)."); + public static final TransactionPermission INSERT = + new TransactionPermission( + "TRANSACTION:INSERT:" + TransactionPermission.ENTITY_ROLE_PARAMETER, + "Permission to insert entities of a given role (e.g. Record, File, RecordType, or Property)."); + @Override protected void run() { final Subject subject = SecurityUtils.getSubject(); // subject has complete permissions for this kind of transaction - if (subject.isPermitted("TRANSACTION:" + getTransaction().getClass().getSimpleName())) { + if (subject.isPermitted( + TRANSACTION_PERMISSIONS.toString(getTransaction().getClass().getSimpleName(), null))) { return; } @@ -54,10 +90,8 @@ public class AccessControl extends ContainerJob { // per role permission if (subject.isPermitted( - "TRANSACTION:" - + getTransaction().getClass().getSimpleName() - + ":" - + e.getRole().toString())) { + TRANSACTION_PERMISSIONS.toString( + getTransaction().getClass().getSimpleName(), e.getRole().toString()))) { continue; } diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java b/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java index 1a159910044a079baba3b64c62176883ad31b8ef..fa21ed3a72c22237b2d10a51b566e9a753f01480 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java @@ -22,6 +22,7 @@ package org.caosdb.server.jobs.core; import java.util.Map; import org.apache.shiro.authz.AuthorizationException; +import org.caosdb.server.accessControl.ACMPermissions; import org.caosdb.server.entity.DeleteEntity; import org.caosdb.server.entity.Message; import org.caosdb.server.entity.Message.MessageType; @@ -42,9 +43,31 @@ import org.caosdb.server.utils.ServerMessages; @JobAnnotation(stage = TransactionStage.POST_CHECK, transaction = WriteTransaction.class) public class CheckStateTransition extends EntityStateJob { - private static final String PERMISSION_STATE_FORCE_FINAL = "STATE:FORCE:FINAL"; - private static final String PERMISSION_STATE_UNASSIGN = "STATE:UNASSIGN:"; - private static final String PERMISSION_STATE_ASSIGN = "STATE:ASSIGN:"; + public static final class StateModelPermission extends ACMPermissions { + + public static final String STATE_MODEL_PARAMETER = "?STATE_MODEL?"; + + public StateModelPermission(String permission, String description) { + super(permission, description); + } + + public final String toString(String state_model) { + return toString().replace(STATE_MODEL_PARAMETER, state_model); + } + } + + public static final StateModelPermission PERMISSION_STATE_FORCE_FINAL = + new StateModelPermission( + "STATE:FORCE:FINAL", + "Permission to force to leave a state models specified life-cycle even though the currrent state isn't a final state in the that model."); + public static final StateModelPermission PERMISSION_STATE_UNASSIGN = + new StateModelPermission( + "STATE:UNASSIGN:" + StateModelPermission.STATE_MODEL_PARAMETER, + "Permission to unassign a state model."); + public static final StateModelPermission PERMISSION_STATE_ASSIGN = + new StateModelPermission( + "STATE:ASSIGN:" + StateModelPermission.STATE_MODEL_PARAMETER, + "Permission to assign a state model."); private static final Message TRANSITION_NOT_ALLOWED = new Message(MessageType.Error, "Transition not allowed."); private static final Message INITIAL_STATE_NOT_ALLOWED = @@ -167,12 +190,12 @@ public class CheckStateTransition extends EntityStateJob { private void checkFinalState(State oldState) throws Message { if (!oldState.isFinal()) { if (isForceFinal()) { - getUser().checkPermission(PERMISSION_STATE_FORCE_FINAL); + getUser().checkPermission(PERMISSION_STATE_FORCE_FINAL.toString()); } else { throw FINAL_STATE_NOT_ALLOWED; } } - getUser().checkPermission(PERMISSION_STATE_UNASSIGN + oldState.getStateModelName()); + getUser().checkPermission(PERMISSION_STATE_UNASSIGN.toString(oldState.getStateModelName())); } /** @@ -185,7 +208,7 @@ public class CheckStateTransition extends EntityStateJob { if (!newState.isInitial()) { throw INITIAL_STATE_NOT_ALLOWED; } - getUser().checkPermission(PERMISSION_STATE_ASSIGN + newState.getStateModelName()); + getUser().checkPermission(PERMISSION_STATE_ASSIGN.toString(newState.getStateModelName())); } private boolean isForceFinal() { diff --git a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java index e2f59a8827a682df508a26176a45a9fc1a874b4e..16fe593654aad760edc2b8efc2e6889222bfa78e 100644 --- a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java +++ b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java @@ -33,6 +33,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import org.apache.shiro.subject.Subject; +import org.caosdb.server.accessControl.ACMPermissions; import org.caosdb.server.database.exceptions.EntityDoesNotExistException; import org.caosdb.server.datatype.AbstractCollectionDatatype; import org.caosdb.server.datatype.CollectionValue; @@ -82,6 +83,19 @@ import org.jdom2.Element; */ public abstract class EntityStateJob extends EntityJob { + public static final class TransitionPermission extends ACMPermissions { + + public static final String TRANSITION_PARAMETER = "?TRANSITION?"; + + public TransitionPermission(String permission, String description) { + super(permission, description); + } + + public final String toString(String transition) { + return toString().replace(TRANSITION_PARAMETER, transition); + } + } + protected static final String SERVER_PROPERTY_EXT_ENTITY_STATE = "EXT_ENTITY_STATE"; public static final String TO_STATE_PROPERTY_NAME = "to"; @@ -102,7 +116,13 @@ public abstract class EntityStateJob extends EntityJob { public static final String STATE_ATTRIBUTE_DESCRIPTION = "description"; public static final String STATE_ATTRIBUTE_ID = "id"; public static final String ENTITY_STATE_ROLE_MARKER = "?STATE?"; - public static final String PERMISSION_STATE_TRANSION = "STATE:TRANSITION:"; + public static final ACMPermissions STATE_PERMISSIONS = + new ACMPermissions( + "STATE:*", "Permissions to manage state models and the states of entities."); + public static final TransitionPermission PERMISSION_STATE_TRANSION = + new TransitionPermission( + "STATE:TRANSITION:" + TransitionPermission.TRANSITION_PARAMETER, + "Permission to initiate a transition."); public static final Message STATE_MODEL_NOT_FOUND = new Message(MessageType.Error, "StateModel not found."); @@ -253,7 +273,7 @@ public abstract class EntityStateJob extends EntityJob { } public boolean isPermitted(Subject user) { - return user.isPermitted(PERMISSION_STATE_TRANSION + this.name); + return user.isPermitted(PERMISSION_STATE_TRANSION.toString(this.name)); } } diff --git a/src/main/java/org/caosdb/server/permissions/EntityPermission.java b/src/main/java/org/caosdb/server/permissions/EntityPermission.java index 1f84dce34a611f3b25bea5ddaab0d40c9723a280..31e9cc471632ce1908e77df27b7121fdb1e6915e 100644 --- a/src/main/java/org/caosdb/server/permissions/EntityPermission.java +++ b/src/main/java/org/caosdb/server/permissions/EntityPermission.java @@ -37,6 +37,10 @@ public class EntityPermission extends Permission { private static final long serialVersionUID = -8935713878537140286L; private static List<EntityPermission> instances = new ArrayList<>(); private final int bitNumber; + public static final Permission EDIT_PRIORITY_ACL = + new Permission( + "ADMIN:ENTITY:EDIT:PRIORITY_ACL", + "The permission to edit (add/delete) the prioritized rules of an acl of an entity."); public static ToElementable getAllEntityPermissions() { final Element entityPermissionsElement = new Element("EntityPermissions"); diff --git a/src/main/java/org/caosdb/server/permissions/Permission.java b/src/main/java/org/caosdb/server/permissions/Permission.java index 44eee1c68a073c405233195aee491bcf10fcd38c..e8f33a3ae3f9eb0956bae6bc7c7eceefdc5663b7 100644 --- a/src/main/java/org/caosdb/server/permissions/Permission.java +++ b/src/main/java/org/caosdb/server/permissions/Permission.java @@ -28,11 +28,6 @@ public class Permission extends WildcardPermission { private static final long serialVersionUID = -7471830472441416012L; - public static final org.apache.shiro.authz.Permission EDIT_PRIORITY_ACL = - new Permission( - "ADMIN:ENTITY:EDIT:PRIORITY_ACL", - "The permission to edit (add/delete) the prioritized rules of an acl of an entity."); - private final String description; private final String shortName; diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java index 95f853d607edffa29b69e4f84657560ba3a52a48..07a165a352ba9a07a345018e7d287cd961f4a00b 100644 --- a/src/main/java/org/caosdb/server/query/Query.java +++ b/src/main/java/org/caosdb/server/query/Query.java @@ -47,6 +47,7 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.apache.commons.jcs.access.behavior.ICacheAccess; import org.apache.shiro.subject.Subject; import org.caosdb.api.entity.v1.MessageCode; +import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.CaosDBServer; import org.caosdb.server.ServerProperties; import org.caosdb.server.caching.Cache; @@ -1047,4 +1048,9 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac return -1; } } + + @Override + public UTCDateTime getTimestamp() { + return null; + } } diff --git a/src/main/java/org/caosdb/server/scripting/ScriptingPermissions.java b/src/main/java/org/caosdb/server/scripting/ScriptingPermissions.java index 78ad99d77d06b681838e455ad391c62e7ce85ca6..ff596a794a6b646003422e63e86b78cf80c475c4 100644 --- a/src/main/java/org/caosdb/server/scripting/ScriptingPermissions.java +++ b/src/main/java/org/caosdb/server/scripting/ScriptingPermissions.java @@ -1,11 +1,25 @@ package org.caosdb.server.scripting; -public class ScriptingPermissions { +import org.caosdb.server.accessControl.ACMPermissions; + +public class ScriptingPermissions extends ACMPermissions { + + public static final String PATH_PARAMETER = "?PATH?"; + + public ScriptingPermissions(String permission, String description) { + super(permission, description); + } + + public String toString(String path) { + return toString().replace(PATH_PARAMETER, path.replace("/", ":")); + } + + private static final ScriptingPermissions execution = + new ScriptingPermissions( + "SCRIPTING:EXECUTE:" + PATH_PARAMETER, + "Permission to execute a server-side script under the given path. Note that, for utilizing the wild cards feature, you have to use ':' as path separator. E.g. 'SCRIPTING:EXECUTE:my_scripts:*' would be the permission to execute all executables below the my_scripts directory."); public static final String PERMISSION_EXECUTION(final String call) { - StringBuilder ret = new StringBuilder(18 + call.length()); - ret.append("SCRIPTING:EXECUTE:"); - ret.append(call.replace("/", ":")); - return ret.toString(); + return execution.toString(call); } } diff --git a/src/main/java/org/caosdb/server/transaction/AccessControlTransaction.java b/src/main/java/org/caosdb/server/transaction/AccessControlTransaction.java index 4897cd65620577e36a98f2db5e5530a42b3f43d0..1cf9f8f70df2eaa384e4074cc3b80a4816a88142 100644 --- a/src/main/java/org/caosdb/server/transaction/AccessControlTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/AccessControlTransaction.java @@ -22,6 +22,7 @@ */ package org.caosdb.server.transaction; +import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.database.DatabaseAccessManager; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.misc.RollBackHandler; @@ -30,9 +31,11 @@ import org.caosdb.server.entity.Message; public abstract class AccessControlTransaction implements TransactionInterface { private Access access; + private UTCDateTime timestamp; @Override public final void execute() throws Exception { + this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis()); this.access = DatabaseAccessManager.getAccountAccess(this); try { @@ -54,4 +57,9 @@ public abstract class AccessControlTransaction implements TransactionInterface { } protected abstract void transaction() throws Exception; + + @Override + public UTCDateTime getTimestamp() { + return timestamp; + } } diff --git a/src/main/java/org/caosdb/server/transaction/DeleteUserTransaction.java b/src/main/java/org/caosdb/server/transaction/DeleteUserTransaction.java index f9e8ab74ff99bd283755fe891c15ea5a9e55f8a7..24aac16b9c529130520518822e6bf3a6a3e8b72d 100644 --- a/src/main/java/org/caosdb/server/transaction/DeleteUserTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/DeleteUserTransaction.java @@ -24,8 +24,10 @@ package org.caosdb.server.transaction; import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; import org.caosdb.server.accessControl.ACMPermissions; import org.caosdb.server.accessControl.CredentialsValidator; +import org.caosdb.server.accessControl.Principal; import org.caosdb.server.accessControl.UserSources; import org.caosdb.server.database.backend.transaction.DeletePassword; import org.caosdb.server.database.backend.transaction.DeleteUser; @@ -50,8 +52,11 @@ public class DeleteUserTransaction extends AccessControlTransaction { @Override protected void transaction() throws Exception { - SecurityUtils.getSubject() - .checkPermission(ACMPermissions.PERMISSION_DELETE_USER(this.realm, this.name)); + Subject subject = SecurityUtils.getSubject(); + if (subject.getPrincipal().equals(new Principal(realm, name))) { + throw ServerMessages.CANNOT_DELETE_YOURSELF(); + } + subject.checkPermission(ACMPermissions.PERMISSION_DELETE_USER(this.realm, this.name)); final CredentialsValidator<String> validator = execute(new RetrievePasswordValidator(this.name), getAccess()).getValidator(); diff --git a/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java b/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java index e066feb3028eb6dcb9bf645070cadefa9cc45047..1ce1aa566ad28d048a36f91d50bb297b1a709d14 100644 --- a/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java +++ b/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java @@ -53,6 +53,7 @@ public class FileStorageConsistencyCheck extends Thread private Runnable finishRunnable = null; private final String location; private Long ts = null; + private UTCDateTime timestamp = null; public Exception getException() { return this.exception; @@ -60,6 +61,7 @@ public class FileStorageConsistencyCheck extends Thread public FileStorageConsistencyCheck(final String location) { setDaemon(true); + this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis()); this.location = location.startsWith("/") ? location.replaceFirst("^/", "") : location; } @@ -245,4 +247,9 @@ public class FileStorageConsistencyCheck extends Thread public void execute() throws Exception { run(); } + + @Override + public UTCDateTime getTimestamp() { + return timestamp; + } } diff --git a/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java b/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java index c1128135b8620ebfe08328cd4901e9c0e6062053..950d9b8d25688e47833b60c01335f8dd525a0630 100644 --- a/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java @@ -24,6 +24,7 @@ package org.caosdb.server.transaction; import java.util.List; import java.util.logging.LogRecord; +import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.database.DatabaseAccessManager; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.transaction.InsertLogRecord; @@ -31,8 +32,10 @@ import org.caosdb.server.database.backend.transaction.InsertLogRecord; public class InsertLogRecordTransaction implements TransactionInterface { private final List<LogRecord> toBeFlushed; + private UTCDateTime timestamp; public InsertLogRecordTransaction(final List<LogRecord> toBeFlushed) { + this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis()); this.toBeFlushed = toBeFlushed; } @@ -45,4 +48,9 @@ public class InsertLogRecordTransaction implements TransactionInterface { access.release(); } } + + @Override + public UTCDateTime getTimestamp() { + return timestamp; + } } diff --git a/src/main/java/org/caosdb/server/transaction/InsertRoleTransaction.java b/src/main/java/org/caosdb/server/transaction/InsertRoleTransaction.java index b70e2bd71259645a8f619cb25d36998579e3dca7..44288af5888c2dacbf9e97c235a76e0d9992a969 100644 --- a/src/main/java/org/caosdb/server/transaction/InsertRoleTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/InsertRoleTransaction.java @@ -22,11 +22,14 @@ */ package org.caosdb.server.transaction; +import java.util.Set; import org.apache.shiro.SecurityUtils; import org.caosdb.server.accessControl.ACMPermissions; import org.caosdb.server.accessControl.Role; import org.caosdb.server.database.backend.transaction.InsertRole; import org.caosdb.server.database.backend.transaction.RetrieveRole; +import org.caosdb.server.database.backend.transaction.SetPermissionRules; +import org.caosdb.server.entity.Message; import org.caosdb.server.utils.ServerMessages; public class InsertRoleTransaction extends AccessControlTransaction { @@ -37,14 +40,24 @@ public class InsertRoleTransaction extends AccessControlTransaction { this.role = role; } - @Override - protected void transaction() throws Exception { + private void checkPermissions() throws Message { SecurityUtils.getSubject().checkPermission(ACMPermissions.PERMISSION_INSERT_ROLE()); if (execute(new RetrieveRole(this.role.name), getAccess()).getRole() != null) { throw ServerMessages.ROLE_NAME_IS_NOT_UNIQUE; } + } + + @Override + protected void transaction() throws Exception { + checkPermissions(); execute(new InsertRole(this.role), getAccess()); + if (this.role.permission_rules != null) { + execute( + new SetPermissionRules(this.role.name, Set.copyOf(this.role.permission_rules)), + getAccess()); + } + RetrieveRole.removeCached(this.role.name); } } diff --git a/src/main/java/org/caosdb/server/transaction/ListRolesTransaction.java b/src/main/java/org/caosdb/server/transaction/ListRolesTransaction.java index d8c962a000b0d97d6b2c4066d8369b7fd05e7b60..ed3cd3cc5a154e82946e76f9934ee8608fe6980c 100644 --- a/src/main/java/org/caosdb/server/transaction/ListRolesTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/ListRolesTransaction.java @@ -48,7 +48,7 @@ public class ListRolesTransaction extends AccessControlTransaction { ACMPermissions.PERMISSION_RETRIEVE_ROLE_DESCRIPTION(role.name))) .collect(Collectors.toList()); - // remove known users + // remove users. the list will only contain name and description. for (Role role : roles) { if (role.users != null) { Iterator<ProtoUser> iterator = role.users.iterator(); diff --git a/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java b/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java index 7e6c527865566795c397377409b1fc63020ca1dd..2f6cafd6e4e55ab868f69f1d3414a2196f0a4fea 100644 --- a/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.LogRecord; import org.apache.shiro.SecurityUtils; +import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.accessControl.ACMPermissions; import org.caosdb.server.database.DatabaseAccessManager; import org.caosdb.server.database.access.Access; @@ -37,10 +38,12 @@ public class RetrieveLogRecordTransaction implements TransactionInterface { private final String logger; private final Level level; private final String message; + private UTCDateTime timestamp; public RetrieveLogRecordTransaction( final String logger, final Level level, final String message) { this.level = level; + this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis()); if (message != null && message.isEmpty()) { this.message = null; } else if (message != null) { @@ -73,4 +76,9 @@ public class RetrieveLogRecordTransaction implements TransactionInterface { public List<LogRecord> getLogRecords() { return this.logRecords; } + + @Override + public UTCDateTime getTimestamp() { + return timestamp; + } } diff --git a/src/main/java/org/caosdb/server/transaction/RetrieveUserRolesTransaction.java b/src/main/java/org/caosdb/server/transaction/RetrieveUserRolesTransaction.java index 97e518d06ac9ad5766b3801be8b1f1403cf8af3f..e26e29830a6e3c5f135b467f4622febda195da89 100644 --- a/src/main/java/org/caosdb/server/transaction/RetrieveUserRolesTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/RetrieveUserRolesTransaction.java @@ -23,6 +23,7 @@ package org.caosdb.server.transaction; import java.util.Set; +import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.accessControl.Principal; import org.caosdb.server.accessControl.UserSources; import org.caosdb.server.utils.ServerMessages; @@ -33,8 +34,10 @@ public class RetrieveUserRolesTransaction implements TransactionInterface { private final String user; private Set<String> roles; private final String realm; + private UTCDateTime timestamp; public RetrieveUserRolesTransaction(final String realm, final String user) { + this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis()); this.realm = realm; this.user = user; } @@ -67,4 +70,9 @@ public class RetrieveUserRolesTransaction implements TransactionInterface { public Set<String> getRoles() { return this.roles; } + + @Override + public UTCDateTime getTimestamp() { + return timestamp; + } } diff --git a/src/main/java/org/caosdb/server/transaction/TransactionInterface.java b/src/main/java/org/caosdb/server/transaction/TransactionInterface.java index d56407ca854c756a956fe11d9bc98a72f05a4b50..8e01c7d9b407927ebdb6a0528f7dcbc6d5bea40d 100644 --- a/src/main/java/org/caosdb/server/transaction/TransactionInterface.java +++ b/src/main/java/org/caosdb/server/transaction/TransactionInterface.java @@ -22,6 +22,7 @@ */ package org.caosdb.server.transaction; +import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.database.BackendTransaction; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.misc.RollBackHandler; @@ -48,4 +49,6 @@ public interface TransactionInterface { t.executeTransaction(); return t; } + + public UTCDateTime getTimestamp(); } diff --git a/src/main/java/org/caosdb/server/transaction/UpdateACL.java b/src/main/java/org/caosdb/server/transaction/UpdateACL.java index 49125a2b67abac4fd640e7d07b2d4ddc2960c72f..e0b0c5cf7b1d1cfd95ef48f9823e01aaae69afce 100644 --- a/src/main/java/org/caosdb/server/transaction/UpdateACL.java +++ b/src/main/java/org/caosdb/server/transaction/UpdateACL.java @@ -21,6 +21,8 @@ package org.caosdb.server.transaction; +import static org.caosdb.server.query.Query.clearCache; + import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction; import org.caosdb.server.database.backend.transaction.UpdateEntityTransaction; import org.caosdb.server.entity.EntityInterface; @@ -67,23 +69,35 @@ public class UpdateACL extends Transaction<TransactionContainer> RetrieveFullEntityTransaction t = new RetrieveFullEntityTransaction(oldContainer); execute(t, getAccess()); + // the entities in this container only have an id and an ACL. -> Replace + // with full entity and move ACL to full entity if permissions are + // sufficient, otherwise add an error. getContainer() .replaceAll( (e) -> { EntityInterface result = oldContainer.getEntityById(e.getId()); // Check ACL update is permitted (against the old ACL) and set the new ACL afterwards. - EntityACL acl = result.getEntityACL(); - if (acl != null && acl.isPermitted(getTransactor(), EntityPermission.EDIT_ACL)) { - if (acl.equals(e.getEntityACL())) { + EntityACL oldAcl = result.getEntityACL(); + EntityACL newAcl = e.getEntityACL(); + if (oldAcl != null + && oldAcl.isPermitted(getTransactor(), EntityPermission.EDIT_ACL)) { + if (oldAcl.equals(newAcl)) { // nothing to be done result.setEntityStatus(EntityStatus.IGNORE); } else { + if (!oldAcl.getPriorityEntityACL().equals(newAcl.getPriorityEntityACL()) + && !oldAcl.isPermitted(getTransactor(), EntityPermission.EDIT_PRIORITY_ACL)) { + // the user is now permitted to update the prioriy acl. + result.addError(org.caosdb.server.utils.ServerMessages.AUTHORIZATION_ERROR); + } + // we're good to go. set new entity acl - result.setEntityACL(e.getEntityACL()); + result.setEntityACL(newAcl); + result.setEntityStatus(EntityStatus.QUALIFIED); } - } else if (acl != null - && acl.isPermitted(getTransactor(), EntityPermission.RETRIEVE_ENTITY)) { + } else if (oldAcl != null + && oldAcl.isPermitted(getTransactor(), EntityPermission.RETRIEVE_ENTITY)) { // the user knows that this entity exists result.addError(org.caosdb.server.utils.ServerMessages.AUTHORIZATION_ERROR); } else { @@ -116,4 +130,15 @@ public class UpdateACL extends Transaction<TransactionContainer> protected void cleanUp() { getAccess().release(); } + + @Override + protected void commit() throws Exception { + getAccess().commit(); + clearCache(); + } + + @Override + public String getSRID() { + return getContainer().getRequestId(); + } } diff --git a/src/main/java/org/caosdb/server/transaction/UpdateRoleTransaction.java b/src/main/java/org/caosdb/server/transaction/UpdateRoleTransaction.java index b1988ffabe8be60c7c2bc38d82e32dbaacee93e4..a62aff5bb6e90120e6cfd6651acde231cf3519b1 100644 --- a/src/main/java/org/caosdb/server/transaction/UpdateRoleTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/UpdateRoleTransaction.java @@ -25,36 +25,55 @@ package org.caosdb.server.transaction; import java.util.Set; import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; import org.caosdb.server.accessControl.ACMPermissions; import org.caosdb.server.accessControl.Role; -import org.caosdb.server.accessControl.UserSources; import org.caosdb.server.database.backend.transaction.InsertRole; import org.caosdb.server.database.backend.transaction.RetrieveRole; import org.caosdb.server.database.backend.transaction.SetPermissionRules; +import org.caosdb.server.entity.Message; +import org.caosdb.server.permissions.PermissionRule; import org.caosdb.server.utils.ServerMessages; public class UpdateRoleTransaction extends AccessControlTransaction { private final Role role; + private Set<PermissionRule> newPermissionRules = null; public UpdateRoleTransaction(final Role role) { this.role = role; } - @Override - protected void transaction() throws Exception { - SecurityUtils.getSubject() - .checkPermission(ACMPermissions.PERMISSION_UPDATE_ROLE_DESCRIPTION(this.role.name)); + private void checkPermissions() throws Message { + + Subject subject = SecurityUtils.getSubject(); + subject.checkPermission(ACMPermissions.PERMISSION_UPDATE_ROLE_DESCRIPTION(this.role.name)); - if (!UserSources.isRoleExisting(this.role.name)) { + Role oldRole = execute(new RetrieveRole(this.role.name), getAccess()).getRole(); + if (oldRole == null) { throw ServerMessages.ROLE_DOES_NOT_EXIST; } - execute(new InsertRole(this.role), getAccess()); if (this.role.permission_rules != null) { - execute( - new SetPermissionRules(this.role.name, Set.copyOf(this.role.permission_rules)), - getAccess()); + Set<PermissionRule> oldPermissions = Set.copyOf(oldRole.permission_rules); + Set<PermissionRule> newPermissions = Set.copyOf(role.permission_rules); + if (!oldPermissions.equals(newPermissions)) { + if (org.caosdb.server.permissions.Role.ADMINISTRATION.toString().equals(this.role.name)) { + throw ServerMessages.SPECIAL_ROLE_PERMISSIONS_CANNOT_BE_CHANGED(); + } + subject.checkPermission(ACMPermissions.PERMISSION_UPDATE_ROLE_PERMISSIONS(this.role.name)); + this.newPermissionRules = newPermissions; + } + } + } + + @Override + protected void transaction() throws Exception { + checkPermissions(); + + execute(new InsertRole(this.role), getAccess()); + if (this.newPermissionRules != null) { + execute(new SetPermissionRules(this.role.name, newPermissionRules), getAccess()); } RetrieveRole.removeCached(this.role.name); } diff --git a/src/main/java/org/caosdb/server/transaction/UpdateUserTransaction.java b/src/main/java/org/caosdb/server/transaction/UpdateUserTransaction.java index 1fca0525f0e82e60c7124dc541ec0e0c09dabf71..6e945118c9b0eef7460f41b5062ccb308b0fd956 100644 --- a/src/main/java/org/caosdb/server/transaction/UpdateUserTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/UpdateUserTransaction.java @@ -23,12 +23,15 @@ package org.caosdb.server.transaction; +import java.util.HashSet; +import java.util.Set; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.caosdb.server.accessControl.ACMPermissions; import org.caosdb.server.accessControl.Principal; import org.caosdb.server.accessControl.UserSources; import org.caosdb.server.accessControl.UserStatus; +import org.caosdb.server.database.backend.transaction.RetrieveRole; import org.caosdb.server.database.backend.transaction.RetrieveUser; import org.caosdb.server.database.backend.transaction.SetPassword; import org.caosdb.server.database.backend.transaction.UpdateUser; @@ -36,6 +39,7 @@ import org.caosdb.server.database.backend.transaction.UpdateUserRoles; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.ProtoUser; import org.caosdb.server.entity.Entity; +import org.caosdb.server.entity.Message; import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.entity.container.RetrieveContainer; import org.caosdb.server.utils.EntityStatus; @@ -50,6 +54,8 @@ public class UpdateUserTransaction extends AccessControlTransaction { private final String password; private final ProtoUser user; + private HashSet<String> newRoles; + private Set<String> oldRoles; public UpdateUserTransaction( final String realm, @@ -79,9 +85,10 @@ public class UpdateUserTransaction extends AccessControlTransaction { this.password = password; } - @Override - protected void transaction() throws Exception { - if (!UserSources.isUserExisting(new Principal(this.user.realm, this.user.name))) { + private void checkPermissions() throws Message { + Principal principal = new Principal(this.user.realm, this.user.name); + ProtoUser oldUser = execute(new RetrieveUser(principal), getAccess()).getUser(); + if (oldUser == null) { throw ServerMessages.ACCOUNT_DOES_NOT_EXIST; } @@ -111,15 +118,30 @@ public class UpdateUserTransaction extends AccessControlTransaction { } if (this.user.roles != null) { - SecurityUtils.getSubject() - .checkPermission( - ACMPermissions.PERMISSION_UPDATE_USER_ROLES(this.user.realm, this.user.name)); + Set<String> oldRoles = oldUser.roles; + if (!this.user.roles.equals(oldRoles)) { + SecurityUtils.getSubject() + .checkPermission( + ACMPermissions.PERMISSION_UPDATE_USER_ROLES(this.user.realm, this.user.name)); + } + this.oldRoles = oldRoles; + this.newRoles = this.user.roles; } + } + + @Override + protected void transaction() throws Exception { + checkPermissions(); + if (isToBeUpdated()) { execute(new UpdateUser(this.user), getAccess()); } - if (this.user.roles != null) { + if (this.newRoles != null) { execute(new UpdateUserRoles(this.user.realm, this.user.name, this.user.roles), getAccess()); + RetrieveRole.removeCached(newRoles); + if (this.oldRoles != null) { + RetrieveRole.removeCached(oldRoles); + } } } diff --git a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java index 1540ca79fdf557c3998f4b66072b4cb5466633a1..ee9aa81e49c99b15ac30a8164a4b9afc18b666f2 100644 --- a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java @@ -376,7 +376,7 @@ public class WriteTransaction extends Transaction<WritableContainer> .getPriorityEntityACL() .equals(oldEntity.getEntityACL().getPriorityEntityACL())) { // priority acl is to be changed? - oldEntity.checkPermission(Permission.EDIT_PRIORITY_ACL); + oldEntity.checkPermission(EntityPermission.EDIT_PRIORITY_ACL); } updatetable = true; } else if (!newEntity.hasEntityACL()) { @@ -568,6 +568,7 @@ public class WriteTransaction extends Transaction<WritableContainer> return null; } + @Override public String getSRID() { return getContainer().getRequestId(); } diff --git a/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java b/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java index 165acb408776a720c6887d31b550cf57e3d6fa0c..4e2938e18d061cbd1a7575baa59322356731859a 100644 --- a/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java +++ b/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java @@ -1,8 +1,13 @@ package org.caosdb.server.transaction; +import org.apache.shiro.subject.Subject; import org.caosdb.server.database.access.Access; public interface WriteTransactionInterface extends TransactionInterface { public Access getAccess(); + + public Subject getTransactor(); + + public String getSRID(); } diff --git a/src/main/java/org/caosdb/server/utils/Info.java b/src/main/java/org/caosdb/server/utils/Info.java index 18ee09828006c0394dec315ca77483f8298ba86b..644239fc662c1b6a05dc1aac37af1bfe26e7972e 100644 --- a/src/main/java/org/caosdb/server/utils/Info.java +++ b/src/main/java/org/caosdb/server/utils/Info.java @@ -28,6 +28,7 @@ import java.sql.SQLException; import java.util.LinkedList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.CaosDBServer; import org.caosdb.server.FileSystem; import org.caosdb.server.database.DatabaseAccessManager; @@ -46,6 +47,7 @@ public class Info extends AbstractObservable implements Observer, TransactionInt public static final String SYNC_DATABASE_EVENT = "SyncDatabaseEvent"; private final Access access; public Logger logger = LogManager.getLogger(getClass()); + private UTCDateTime timestamp; @Override public boolean notifyObserver(final String e, final Observable o) { @@ -58,6 +60,7 @@ public class Info extends AbstractObservable implements Observer, TransactionInt } private Info() { + this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis()); this.access = DatabaseAccessManager.getInfoAccess(this); try { syncDatabase(); @@ -246,4 +249,9 @@ public class Info extends AbstractObservable implements Observer, TransactionInt public void execute() throws Exception { syncDatabase(); } + + @Override + public UTCDateTime getTimestamp() { + return timestamp; + } } diff --git a/src/main/java/org/caosdb/server/utils/Initialization.java b/src/main/java/org/caosdb/server/utils/Initialization.java index cb1a36307928a72b9da95b48737196e569c43346..4f28f206bd5f272887fb78b661c6dc46d5cff6bc 100644 --- a/src/main/java/org/caosdb/server/utils/Initialization.java +++ b/src/main/java/org/caosdb/server/utils/Initialization.java @@ -24,6 +24,7 @@ */ package org.caosdb.server.utils; +import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.database.DatabaseAccessManager; import org.caosdb.server.database.access.Access; import org.caosdb.server.transaction.TransactionInterface; @@ -31,9 +32,11 @@ import org.caosdb.server.transaction.TransactionInterface; public final class Initialization implements TransactionInterface, AutoCloseable { private Access access; + private UTCDateTime timestamp; private static final Initialization instance = new Initialization(); private Initialization() { + this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis()); this.access = DatabaseAccessManager.getInitAccess(this); } @@ -55,4 +58,9 @@ public final class Initialization implements TransactionInterface, AutoCloseable this.access = null; } } + + @Override + public UTCDateTime getTimestamp() { + return timestamp; + } } diff --git a/src/main/java/org/caosdb/server/utils/ServerMessages.java b/src/main/java/org/caosdb/server/utils/ServerMessages.java index ea5222896895e22ef190ee4d04ea6b3e1c282e1a..151f55face7edbfab793606474f183a65f0468be 100644 --- a/src/main/java/org/caosdb/server/utils/ServerMessages.java +++ b/src/main/java/org/caosdb/server/utils/ServerMessages.java @@ -283,6 +283,13 @@ public class ServerMessages { MessageCode.MESSAGE_CODE_UNKNOWN, "This special role cannot be deleted. Ever."); + public static final Message SPECIAL_ROLE_PERMISSIONS_CANNOT_BE_CHANGED() { + return new Message( + MessageType.Error, + MessageCode.MESSAGE_CODE_UNKNOWN, + "This special role's permissions cannot be changed. Ever."); + } + public static final Message QUERY_EXCEPTION = new Message( MessageType.Error, @@ -604,4 +611,9 @@ public class ServerMessages { MessageCode.MESSAGE_CODE_UNKNOWN, "The user name does not comply with the policies for user names: " + policy); } + + public static final Message CANNOT_DELETE_YOURSELF() { + return new Message( + MessageType.Error, MessageCode.MESSAGE_CODE_UNKNOWN, "You cannot delete yourself."); + } }