diff --git a/security/src/main/java/org/genesys/blocks/security/model/AclAwareModel.java b/security/src/main/java/org/genesys/blocks/security/model/AclAwareModel.java index f1a7bee5f4cac0c5aaf50ee71c84c2df1192fd9f..1783ee4fcab9d0aebeca945183bec1719a868897 100644 --- a/security/src/main/java/org/genesys/blocks/security/model/AclAwareModel.java +++ b/security/src/main/java/org/genesys/blocks/security/model/AclAwareModel.java @@ -15,21 +15,19 @@ */ package org.genesys.blocks.security.model; -import java.io.Serializable; - -import com.fasterxml.jackson.databind.annotation.JsonAppend; - import org.genesys.blocks.model.EntityId; -import org.genesys.blocks.security.serialization.Permissions; import org.genesys.blocks.security.serialization.CurrentPermissionsWriter; +import org.genesys.blocks.security.serialization.Permissions; import org.genesys.blocks.util.JsonClassNameWriter; +import com.fasterxml.jackson.databind.annotation.JsonAppend; + /** * Interface label for entities that require ACL security. */ @JsonAppend(props = { @JsonAppend.Prop(name = "_class", value = JsonClassNameWriter.class, type = String.class), @JsonAppend.Prop(name = "_permissions", value = CurrentPermissionsWriter.class, type = Permissions.class) }) -public interface AclAwareModel extends Serializable, EntityId { +public interface AclAwareModel extends EntityId { /** * Objects belonging to a parent entity can override this method. diff --git a/security/src/main/java/org/genesys/blocks/security/model/AclClass.java b/security/src/main/java/org/genesys/blocks/security/model/AclClass.java index 4ef67cb757baf87396cdea2457ef07a471dae4c0..160c04ac5ddd3594735927d5fa5cc5a994217a75 100644 --- a/security/src/main/java/org/genesys/blocks/security/model/AclClass.java +++ b/security/src/main/java/org/genesys/blocks/security/model/AclClass.java @@ -15,6 +15,7 @@ */ package org.genesys.blocks.security.model; +import javax.persistence.Cacheable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; @@ -26,6 +27,7 @@ import org.genesys.blocks.model.BasicModel; */ @Entity @Table(name = "acl_class") +@Cacheable public class AclClass extends BasicModel { /** The Constant serialVersionUID. */ diff --git a/security/src/main/java/org/genesys/blocks/security/model/AclSid.java b/security/src/main/java/org/genesys/blocks/security/model/AclSid.java index 3c9ce20e7bc8248588a5f513a0f53fdd2fa15ed2..35b539b1a356733e914d22b9627fff31b8efdcc3 100644 --- a/security/src/main/java/org/genesys/blocks/security/model/AclSid.java +++ b/security/src/main/java/org/genesys/blocks/security/model/AclSid.java @@ -17,6 +17,7 @@ package org.genesys.blocks.security.model; import java.util.List; +import javax.persistence.Cacheable; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.DiscriminatorColumn; @@ -53,6 +54,7 @@ import org.hibernate.annotations.DiscriminatorOptions; @DiscriminatorValue(value = "0") @DiscriminatorOptions(force = false) @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "sid") +@Cacheable public class AclSid extends AuditedVersionedModel { /** The Constant serialVersionUID. */ diff --git a/security/src/main/java/org/genesys/blocks/security/service/impl/CustomAclServiceImpl.java b/security/src/main/java/org/genesys/blocks/security/service/impl/CustomAclServiceImpl.java index 824696d474ad8f6292880601119bf87d140af08f..c62cc8b859494dfc04fe55cc928b9d89b86f9989 100644 --- a/security/src/main/java/org/genesys/blocks/security/service/impl/CustomAclServiceImpl.java +++ b/security/src/main/java/org/genesys/blocks/security/service/impl/CustomAclServiceImpl.java @@ -33,6 +33,7 @@ import org.genesys.blocks.security.persistence.AclSidPersistence; import org.genesys.blocks.security.serialization.Permissions; import org.genesys.blocks.security.serialization.SidPermissions; import org.genesys.blocks.security.service.CustomAclService; +import org.genesys.blocks.util.ClassAclOid; import org.hibernate.proxy.HibernateProxyHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -121,12 +122,17 @@ public class CustomAclServiceImpl implements CustomAclService { @Override @Transactional(propagation = Propagation.REQUIRED) public void createOrUpdatePermissions(final AclAwareModel target) { - if ((target == null) || (target.getId() <= 0l)) { + if (target == null || (target.getId() <= 0l && !(target instanceof ClassAclOid))) { LOG.warn("No target specified for ACL permissions, bailing out!"); return; } - final AclClass aclClass = ensureAclClass(target.getClass().getName()); + String className = target.getClass().getName(); + if (target instanceof ClassAclOid) { + className = ((ClassAclOid) target).getClassName(); + } + + final AclClass aclClass = ensureAclClass(className); // save object identity AclObjectIdentity objectIdentity = aclObjectIdentityPersistence.findByObjectIdAndClassname(target.getId(), aclClass.getAclClass()); @@ -416,6 +422,11 @@ public class CustomAclServiceImpl implements CustomAclService { return null; } String className = HibernateProxyHelper.getClassWithoutInitializingProxy(entity).getName(); + + if (entity instanceof ClassAclOid) { + className = ((ClassAclOid) entity).getClassName(); + } + final AclObjectIdentity oid = aclObjectIdentityPersistence.findByObjectIdAndClassname(entity.getId(), className); if (oid == null) { LOG.warn("ACL object identity not found for class={} id={}", className, entity.getId()); @@ -463,7 +474,7 @@ public class CustomAclServiceImpl implements CustomAclService { return getPermissions(entity.getId(), entity.getClass().getName()); } - @Transactional(propagation = Propagation.REQUIRED) + @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_UNCOMMITTED) @Override @PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#entity, 'ADMINISTRATION')") public AclObjectIdentity setPermissions(final AclAwareModel entity, final AclSid sid, final Permissions permissions) { @@ -481,7 +492,7 @@ public class CustomAclServiceImpl implements CustomAclService { return setPermissions(objectIdentity, sid, permissions); } - @Transactional(propagation = Propagation.REQUIRED) + @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_UNCOMMITTED) @Override @PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#objectIdentity.objectIdIdentity, #objectIdentity.aclClass.aclClass, 'ADMINISTRATION')") public AclObjectIdentity setPermissions(AclObjectIdentity objectIdentity, AclSid sid, final Permissions permissions) { @@ -496,33 +507,38 @@ public class CustomAclServiceImpl implements CustomAclService { } try { - if (permissions.isOneGranting()) { - // need to update or add permissions - final List aclEntries = aclEntryPersistence.findBySidAndObjectIdentity(sid, objectIdentity); - if (aclEntries.isEmpty()) { - // add - return addPermissions(objectIdentity, sid, permissions); - } else { - // updarte - for (final AclEntry aclEntry : aclEntries) { - aclEntry.setGranting(permissions.isGranting(aclEntry.getMask())); - } - LOG.info("Saving " + aclEntries); - aclEntryPersistence.save(aclEntries); - return getObjectIdentity(objectIdentity.getId()); - } + final List aclEntries = aclEntryPersistence.findBySidAndObjectIdentity(sid, objectIdentity); + if (aclEntries.isEmpty()) { + // add + return addPermissions(objectIdentity, sid, permissions); } else { - // delete existing - final List aclEntries = aclEntryPersistence.findBySidAndObjectIdentity(sid, objectIdentity); - if (!aclEntries.isEmpty()) { - LOG.info("Deleting " + aclEntries); - aclEntryPersistence.delete(aclEntries); - entityManager.flush(); + // update + for (final AclEntry aclEntry : aclEntries) { + aclEntry.setGranting(permissions.isGranting(aclEntry.getMask())); } + LOG.info("Saving " + aclEntries); + aclEntryPersistence.save(aclEntries); return getObjectIdentity(objectIdentity.getId()); } + + // // This is the original code, one that cleared permissions when none were granted + // // It didn't take into account inherited permissions. + // if (permissions.isOneGranting()) { + // need to update or add permissions + // ... + // } else { + // // delete existing + // final List aclEntries = + // aclEntryPersistence.findBySidAndObjectIdentity(sid, objectIdentity); + // if (!aclEntries.isEmpty()) { + // LOG.info("Deleting " + aclEntries); + // aclEntryPersistence.delete(aclEntries); + // entityManager.flush(); + // } + // return getObjectIdentity(objectIdentity.getId()); + // } } finally { clearAclCache(); } diff --git a/security/src/main/java/org/genesys/blocks/util/ClassAclOid.java b/security/src/main/java/org/genesys/blocks/util/ClassAclOid.java new file mode 100644 index 0000000000000000000000000000000000000000..ff4ce5662f8b84770f42cbf279573be9922b7070 --- /dev/null +++ b/security/src/main/java/org/genesys/blocks/util/ClassAclOid.java @@ -0,0 +1,65 @@ +/* + * Copyright 2018 Global Crop Diversity Trust + * + * Licensed 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.genesys.blocks.util; + +import org.genesys.blocks.security.model.AclAwareModel; + +/** + * The ACL object identity for our Java classes. Mostly used to make entity + * types READ-able to EVERYONE by default. + * + * @param the generic type + */ +public class ClassAclOid implements AclAwareModel { + + private Class clazz; + + @Override + public Long getId() { + return -419l; // We use -419l for ACL OID#id for classes + } + + /** + * For class. + * + * @param the generic type + * @param clazz the clazz + * @return the class acl + */ + public static ClassAclOid forClass(Class clazz) { + ClassAclOid classAcl = new ClassAclOid(); + classAcl.clazz = clazz; + return classAcl; + } + + /** + * Gets the clazz. + * + * @return the clazz + */ + public Class getClazz() { + return clazz; + } + + /** + * Gets the class name. + * + * @return the class name + */ + public String getClassName() { + return clazz.getName(); + } +}