From a8c85aaca97381908d9a4b1d71da27d9ac4b829b Mon Sep 17 00:00:00 2001 From: jensp Date: Mon, 23 Jan 2017 20:39:42 +0000 Subject: [PATCH] CCM NG: Permissions are now written recursivly git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4534 8810af33-2d31-482b-a856-94f89814c4df --- .../contentsection/ItemAttachment.java | 2 + .../categorization/Categorization.java | 15 +++- .../org/libreccm/security/Permission.java | 12 +-- .../libreccm/security/PermissionManager.java | 87 +++++++++++++++++-- .../java/org/libreccm/security/Relation.java | 46 ++++++++++ .../V7_0_0_12__permission_add_inherited.sql | 10 +++ .../org/libreccm/portation/ImportHelper.java | 3 + .../security/PermissionManagerTest.java | 21 ++++- 8 files changed, 179 insertions(+), 17 deletions(-) create mode 100644 ccm-core/src/main/java/org/libreccm/security/Relation.java create mode 100644 ccm-core/src/main/resources/db/migrations/org/libreccm/ccm_core/pgsql/V7_0_0_12__permission_add_inherited.sql diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/ItemAttachment.java b/ccm-cms/src/main/java/org/librecms/contentsection/ItemAttachment.java index 460333e82..629c79219 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/ItemAttachment.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/ItemAttachment.java @@ -19,7 +19,9 @@ package org.librecms.contentsection; import org.hibernate.envers.Audited; +import org.libreccm.core.CcmObject; import org.libreccm.core.Identifiable; +import org.libreccm.security.Relation; import java.io.Serializable; import java.util.Objects; diff --git a/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java b/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java index 30c987946..7c03c26bf 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/Categorization.java @@ -38,7 +38,7 @@ import java.util.Objects; import static org.libreccm.core.CoreConstants.DB_SCHEMA; -import org.libreccm.security.RecursivePermissions; +import org.libreccm.security.Relation; /** * Association class describing the association between a category and an @@ -70,7 +70,7 @@ import org.libreccm.security.RecursivePermissions; + "WHERE c.category = :category " + "AND c.index = TRUE") }) -public class Categorization implements Serializable, Portable { +public class Categorization implements Serializable, Relation, Portable { private static final long serialVersionUID = 201504301320L; @@ -93,7 +93,6 @@ public class Categorization implements Serializable, Portable { /** * The categorised object. */ - @RecursivePermissions @ManyToOne @JoinColumn(name = "OBJECT_ID") @JsonBackReference(value = "object-categorization") @@ -145,6 +144,11 @@ public class Categorization implements Serializable, Portable { return category; } + @Override + public CcmObject getOwner() { + return getCategory(); + } + protected void setCategory(final Category category) { this.category = category; } @@ -152,6 +156,11 @@ public class Categorization implements Serializable, Portable { public CcmObject getCategorizedObject() { return categorizedObject; } + + @Override + public CcmObject getRelatedObject() { + return getCategorizedObject(); + } protected void setCategorizedObject(final CcmObject categorizedObject) { this.categorizedObject = categorizedObject; diff --git a/ccm-core/src/main/java/org/libreccm/security/Permission.java b/ccm-core/src/main/java/org/libreccm/security/Permission.java index a85b49522..5d8e0d2d7 100644 --- a/ccm-core/src/main/java/org/libreccm/security/Permission.java +++ b/ccm-core/src/main/java/org/libreccm/security/Permission.java @@ -162,7 +162,7 @@ public class Permission implements Serializable, Portable { */ @OneToOne @JoinColumn(name = "INHERITED_FROM_ID") - private CcmObject inheritedForm; + private CcmObject inheritedFrom; protected Permission() { //Nothing @@ -236,12 +236,12 @@ public class Permission implements Serializable, Portable { this.inherited = inherited; } - public CcmObject getInheritedForm() { - return inheritedForm; + public CcmObject getInheritedFrom() { + return inheritedFrom; } - protected void setInheritedForm(CcmObject inheritedForm) { - this.inheritedForm = inheritedForm; + protected void setInheritedFrom(CcmObject inheritedFrom) { + this.inheritedFrom = inheritedFrom; } @Override @@ -277,7 +277,7 @@ public class Permission implements Serializable, Portable { if (!Objects.equals(creationDate, other.getCreationDate())) { return false; } - + if (!Objects.equals(creationIp, other.getCreationIp())) { return false; } diff --git a/ccm-core/src/main/java/org/libreccm/security/PermissionManager.java b/ccm-core/src/main/java/org/libreccm/security/PermissionManager.java index aadb13862..380f8f71f 100644 --- a/ccm-core/src/main/java/org/libreccm/security/PermissionManager.java +++ b/ccm-core/src/main/java/org/libreccm/security/PermissionManager.java @@ -18,6 +18,8 @@ */ package org.libreccm.security; +import com.arsdigita.util.UncheckedWrapperException; + import java.util.List; import javax.inject.Inject; @@ -31,6 +33,7 @@ import org.libreccm.core.CoreConstants; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.Collection; import java.util.stream.Collectors; import javax.enterprise.context.RequestScoped; @@ -101,6 +104,69 @@ public class PermissionManager { permission.setObject(object); entityManager.persist(permission); + + grantRecursive(privilege, grantee, object, object.getClass()); + } + } + + private void grantRecursive(final String privilege, + final Role grantee, + final CcmObject object, + final Class clazz) { + final Field[] fields = clazz.getDeclaredFields(); + Arrays.stream(fields) + .filter(field -> field.isAnnotationPresent( + RecursivePermissions.class)) + .forEach(field -> { + field.setAccessible(true); + grantRecursive(privilege, grantee, field, object); + }); + + if (clazz.getSuperclass() != null) { + grantRecursive(privilege, grantee, object, clazz.getSuperclass()); + } + } + + private void grantRecursive(final String privilege, + final Role grantee, + final Field field, + final CcmObject owner) { + final Object value; + try { + value = field.get(owner); + } catch (IllegalAccessException ex) { + throw new UncheckedWrapperException(ex); + } + + if (value == null) { + return; + } + + if (field.getType().equals(Collection.class)) { + final Collection collection = (Collection) value; + collection.stream() + .filter(obj -> obj instanceof CcmObject) + .map(obj -> (CcmObject) obj) + .forEach(obj -> grantPrivilege(privilege, grantee, obj)); + collection.stream() + .filter(obj -> obj instanceof Relation) + .map(obj -> (Relation) obj) + .filter(relation -> relation.getRelatedObject() != null) + .map(relation -> relation.getRelatedObject()) + .forEach(obj -> grantPrivilege(privilege, grantee, obj)); + } else if (field.getType().equals(CcmObject.class)) { + grantPrivilege(privilege, grantee, (CcmObject) value); + } else if (field.getType().equals(Relation.class)) { + final Relation relation = (Relation) value; + if (relation.getRelatedObject() != null) { + grantPrivilege(privilege, grantee, relation.getRelatedObject()); + } + } else { + throw new IllegalArgumentException(String.format( + "Found a field annotated with \"%s\" but the field is not a " + + "collection nor a CcmObject nore a Relation object. This " + + "is not supported.", + RecursivePermissions.class)); } } @@ -166,15 +232,26 @@ public class PermissionManager { } if (existsPermission(privilege, grantee, object)) { - final Query query = entityManager.createQuery( + final Query deleteQuery = entityManager.createQuery( "DELETE FROM Permission p " + "WHERE p.grantedPrivilege = :privilege " + "AND p.grantee = :grantee " + "AND p.object = :object"); - query.setParameter(QUERY_PARAM_PRIVILEGE, privilege); - query.setParameter(QUERY_PARAM_GRANTEE, grantee); - query.setParameter(QUERY_PARAM_OBJECT, object); - query.executeUpdate(); + deleteQuery.setParameter(QUERY_PARAM_PRIVILEGE, privilege); + deleteQuery.setParameter(QUERY_PARAM_GRANTEE, grantee); + deleteQuery.setParameter(QUERY_PARAM_OBJECT, object); + deleteQuery.executeUpdate(); + + final Query deleteInheritedQuery = entityManager.createQuery( + "DELETE FROM Permission p " + + "WHERE p.grantedPrivilege = :privilege " + + "AND p.grantee = :grantee " + + "AND p.inheritedFrom = :object " + + "AND p.inherited = true"); + deleteInheritedQuery.setParameter(QUERY_PARAM_PRIVILEGE, privilege); + deleteInheritedQuery.setParameter(QUERY_PARAM_GRANTEE, grantee); + deleteInheritedQuery.setParameter("p.inheritedFrom", object); + deleteQuery.executeUpdate(); } } diff --git a/ccm-core/src/main/java/org/libreccm/security/Relation.java b/ccm-core/src/main/java/org/libreccm/security/Relation.java new file mode 100644 index 000000000..b6f12dd18 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/security/Relation.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.security; + +import org.libreccm.core.CcmObject; +import org.libreccm.security.RecursivePermissions; + +/** + * If N:M or relation with attributes is annotated with {@link RecursivePermissions} and the + * relation object is not a {@link CcmObject} the relation object must + * implement this interface. + * + * An example are {@link Category#objects} and {@link Categorization}. + * + * @author Jens Pelzetter + */ +public interface Relation { + + /** + * Get the owning object of the relation. + * @return + */ + CcmObject getOwner(); + /** + * Get the related object of the relation. + * @return + */ + CcmObject getRelatedObject(); + +} diff --git a/ccm-core/src/main/resources/db/migrations/org/libreccm/ccm_core/pgsql/V7_0_0_12__permission_add_inherited.sql b/ccm-core/src/main/resources/db/migrations/org/libreccm/ccm_core/pgsql/V7_0_0_12__permission_add_inherited.sql new file mode 100644 index 000000000..4722bd94e --- /dev/null +++ b/ccm-core/src/main/resources/db/migrations/org/libreccm/ccm_core/pgsql/V7_0_0_12__permission_add_inherited.sql @@ -0,0 +1,10 @@ +alter table CCM_CORE.PERMISSIONS + add column INHERITED boolean; + +alter table CCM_CORE.PERMISSIONS + add column INHERITED_FROM_ID int8; + +alter table CCM_CORE.PERMISSIONS + add constraint FKc1x3h1p3o20qiwmonpmva7t5i + foreign key (INHERITED_FROM_ID) + references CCM_CORE.CCM_OBJECTS; diff --git a/ccm-core/src/test/java/org/libreccm/portation/ImportHelper.java b/ccm-core/src/test/java/org/libreccm/portation/ImportHelper.java index 5366c9455..bc567113e 100644 --- a/ccm-core/src/test/java/org/libreccm/portation/ImportHelper.java +++ b/ccm-core/src/test/java/org/libreccm/portation/ImportHelper.java @@ -21,6 +21,7 @@ package org.libreccm.portation; import org.libreccm.categorization.CategorizationMarshaller; import org.libreccm.categorization.Category; import org.libreccm.categorization.CategoryMarshaller; +import org.libreccm.security.Group; import org.libreccm.security.GroupMarshaller; import org.libreccm.security.GroupMembershipMarshaller; import org.libreccm.security.PermissionMarshaller; @@ -34,6 +35,7 @@ import org.libreccm.workflow.WorkflowTemplateMarshaller; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; + import java.util.List; /** @@ -57,6 +59,7 @@ class ImportHelper { @Inject private UserMarshaller userMarshaller; @Inject + @Marshals(Group.class) private GroupMarshaller groupMarshaller; @Inject private GroupMembershipMarshaller groupMembershipMarshaller; diff --git a/ccm-core/src/test/java/org/libreccm/security/PermissionManagerTest.java b/ccm-core/src/test/java/org/libreccm/security/PermissionManagerTest.java index e1caa7293..9205c456c 100644 --- a/ccm-core/src/test/java/org/libreccm/security/PermissionManagerTest.java +++ b/ccm-core/src/test/java/org/libreccm/security/PermissionManagerTest.java @@ -229,7 +229,12 @@ public class PermissionManagerTest { excludeColumns = {"permission_id"}) @InSequence(211) public void grantPermissionRecursively() { - fail(); + final Role role1 = roleRepository.findByName("role1"); + final CcmObject category1 = ccmObjectRepository.findById(-20001L); + + shiro.getSystemUser().execute(() -> { + permissionManager.grantPrivilege("privilege4", role1, category1); + }); } /** @@ -246,7 +251,12 @@ public class PermissionManagerTest { excludeColumns = {"permission_id"}) @InSequence(212) public void grantInheritedPermission() { - fail(); + final Role role1 = roleRepository.findByName("role1"); + final CcmObject category2 = ccmObjectRepository.findById(-20001L); + + shiro.getSystemUser().execute(() -> { + permissionManager.grantPrivilege("privilege4", role1, category2); + }); } /** @@ -480,7 +490,12 @@ public class PermissionManagerTest { + "after-revoke-recursivly.yml") @InSequence(311) public void revokePermissionRecursivly() { - fail(); + final Role role1 = roleRepository.findByName("role1"); + final CcmObject category1 = ccmObjectRepository.findById(-20001L); + + shiro.getSystemUser().execute(() -> { + permissionManager.revokePrivilege("privilege4", role1, category1); + }); } /**