From d043fbea5667ecf1c8cf0dc0ccbceb0860b0b415 Mon Sep 17 00:00:00 2001 From: jensp Date: Fri, 4 Dec 2015 14:53:43 +0000 Subject: [PATCH] CCM NG: Documentation for ccm-core:org.libreccm.security git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3762 8810af33-2d31-482b-a856-94f89814c4df --- .../security/AuthorizationInterceptor.java | 5 +- .../org/libreccm/security/CcmShiroRealm.java | 73 +++--- .../java/org/libreccm/security/Group.java | 10 +- .../security/InheritsPermissions.java | 8 +- .../org/libreccm/security/Permission.java | 2 +- .../libreccm/security/PermissionManager.java | 3 + .../libreccm/security/SecuredCollection.java | 78 ++++-- .../org/libreccm/security/SecuredEntry.java | 29 +- .../security/SecuredEntryIterator.java | 16 ++ .../libreccm/security/SecuredEntrySet.java | 248 ++++++++++-------- .../org/libreccm/security/SecuredHelper.java | 12 +- .../libreccm/security/SecuredIterator.java | 6 - .../org/libreccm/security/SecuredList.java | 30 ++- .../security/SecuredListIterator.java | 27 +- .../org/libreccm/security/SecuredMap.java | 33 ++- .../security/SecuredNavigableMap.java | 30 ++- .../security/SecuredNavigableSet.java | 31 ++- .../org/libreccm/security/SecuredSet.java | 19 +- .../libreccm/security/SecuredSortedMap.java | 25 +- .../libreccm/security/SecuredSortedSet.java | 32 ++- .../main/java/org/libreccm/security/User.java | 5 +- .../org/libreccm/security/package-info.java | 28 +- src/site/apt/auth.apt | 97 +++++++ src/site/site.xml | 2 + 24 files changed, 607 insertions(+), 242 deletions(-) create mode 100644 src/site/apt/auth.apt diff --git a/ccm-core/src/main/java/org/libreccm/security/AuthorizationInterceptor.java b/ccm-core/src/main/java/org/libreccm/security/AuthorizationInterceptor.java index 8a553e6d1..1d21b3b39 100644 --- a/ccm-core/src/main/java/org/libreccm/security/AuthorizationInterceptor.java +++ b/ccm-core/src/main/java/org/libreccm/security/AuthorizationInterceptor.java @@ -74,6 +74,8 @@ public class AuthorizationInterceptor { * @throws Exception If any exception occurs. */ @AroundInvoke + // throws Exception necessary because InvocationContext#proceed has also throws Exception + @SuppressWarnings("PMD.SignatureDeclareThrowsException") public Object intercept(final InvocationContext context) throws Exception { LOGGER.debug("Intercepting method invocation"); @@ -123,6 +125,7 @@ public class AuthorizationInterceptor { * @param parameter The parameter to check. * @param annotations All annotations of the parameter. */ + @SuppressWarnings("PMD.UseVarargs") private void checkParameterPermission(final Object parameter, final Annotation[] annotations) { if (parameter instanceof CcmObject @@ -131,7 +134,7 @@ public class AuthorizationInterceptor { final CcmObject object = (CcmObject) parameter; String requiredPrivilege = null; - for (Annotation annotation : annotations) { + for (final Annotation annotation : annotations) { if (annotation instanceof RequiresPrivilege) { requiredPrivilege = ((RequiresPrivilege) annotation).value(); break; diff --git a/ccm-core/src/main/java/org/libreccm/security/CcmShiroRealm.java b/ccm-core/src/main/java/org/libreccm/security/CcmShiroRealm.java index 0f0e9b984..8c99bf1f7 100644 --- a/ccm-core/src/main/java/org/libreccm/security/CcmShiroRealm.java +++ b/ccm-core/src/main/java/org/libreccm/security/CcmShiroRealm.java @@ -39,16 +39,19 @@ import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.CDI; /** - * Implementation of the Shiro's {@link AuthorizingRealm} to provide Shiro with - * the users, groups, roles and permissions stored in CCM's database. + * Implementation of Shiro's {@link AuthorizingRealm} to provide Shiro with the + * users, groups, roles and permissions stored in CCM's database. * * @author Jens Pelzetter */ +@SuppressWarnings({"PMD.CyclomaticComplexity", + "PMD.ModifiedCyclomaticComplexity", + "PMD.StdCyclomaticComplexity"}) public class CcmShiroRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo( - final PrincipalCollection principals) { + final PrincipalCollection principals) { // Get the pricipal (object identifing the user). final Object principal = principals.getPrimaryPrincipal(); @@ -56,9 +59,9 @@ public class CcmShiroRealm extends AuthorizingRealm { // This realm expects the principal to be a string. if (!(principal instanceof String)) { throw new AuthenticationException(String.format( - "Can' process principal of " - + "type \"%s\".", - principal.getClass().getName())); + "Can' process principal of " + + "type \"%s\".", + principal.getClass().getName())); } // Convert the pricipal to a string. final String userIdentifier = (String) principal; @@ -70,30 +73,31 @@ public class CcmShiroRealm extends AuthorizingRealm { final RoleRepository roleRepository; final BeanManager beanManager = CDI.current().getBeanManager(); final Set> beans = beanManager. - getBeans(RoleRepository.class); + getBeans(RoleRepository.class); final Iterator> iterator = beans.iterator(); if (iterator.hasNext()) { @SuppressWarnings("unchecked") - final Bean bean = (Bean) iterator. - next(); + final Bean bean + = (Bean) iterator. + next(); final CreationalContext ctx = beanManager. - createCreationalContext(bean); + createCreationalContext(bean); roleRepository = (RoleRepository) beanManager.getReference( - bean, RoleRepository.class, ctx); + bean, RoleRepository.class, ctx); } else { throw new AuthenticationException( - "Failed to retrieve RoleRepository"); + "Failed to retrieve RoleRepository"); } - + final List roles = roleRepository.findAll(); - + final SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); - for(final Role role : roles) { + for (final Role role : roles) { info.addRole(role.getName()); } info.addStringPermission("*"); - + return info; } @@ -110,7 +114,7 @@ public class CcmShiroRealm extends AuthorizingRealm { // Add the permissions assigned to the role to the AuthorizatonInfo. for (final Permission permission : roleMembership.getRole() - .getPermissions()) { + .getPermissions()) { info.addStringPermission(permissionToString(permission)); } } @@ -119,13 +123,13 @@ public class CcmShiroRealm extends AuthorizingRealm { for (final GroupMembership membership : user.getGroupMemberships()) { // Get the roles assigned to the group for (final RoleMembership roleMembership : membership.getGroup() - .getRoleMemberships()) { + .getRoleMemberships()) { // Add the role to the AuthorizationInfo info.addRole(roleMembership.getRole().getName()); // Add the permissions assigned to the role to the // AuthorizationInfo for (final Permission permission : roleMembership.getRole() - .getPermissions()) { + .getPermissions()) { info.addStringPermission(permissionToString(permission)); } } @@ -136,8 +140,8 @@ public class CcmShiroRealm extends AuthorizingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo( - final AuthenticationToken token) - throws AuthenticationException { + final AuthenticationToken token) + throws AuthenticationException { // Get the pricipal identifing the user final Object principal = token.getPrincipal(); @@ -145,9 +149,9 @@ public class CcmShiroRealm extends AuthorizingRealm { // This realm expects the pricipal to be a string if (!(principal instanceof String)) { throw new AuthenticationException(String.format( - "Can' process authentication token with a principal of " - + "type \"%s\".", - principal.getClass().getName())); + "Can' process authentication token with a principal of " + + "type \"%s\".", + principal.getClass().getName())); } // Convert the pricipal to a string. @@ -168,9 +172,11 @@ public class CcmShiroRealm extends AuthorizingRealm { * address of the user. * * @param userIdentifier The identifier of the user. + * * @return The User identified by the provided {@code userIdentifier}. + * * @throws AuthenticationException if no user for the provided identifier - * could be retrieved. + * could be retrieved. */ private User findUser(final String userIdentifier) { // For some reason we can't use the the CdiUtil class here, therefore @@ -178,20 +184,20 @@ public class CcmShiroRealm extends AuthorizingRealm { final UserRepository userRepository; final BeanManager beanManager = CDI.current().getBeanManager(); final Set> beans = beanManager.getBeans( - UserRepository.class); + UserRepository.class); final Iterator> iterator = beans.iterator(); if (iterator.hasNext()) { @SuppressWarnings("unchecked") final Bean bean = (Bean) iterator - .next(); + .next(); final CreationalContext ctx = beanManager - .createCreationalContext(bean); + .createCreationalContext(bean); userRepository = (UserRepository) beanManager.getReference( - bean, UserRepository.class, ctx); + bean, UserRepository.class, ctx); } else { throw new AuthenticationException( - "Failed to retrieve UserRepository."); + "Failed to retrieve UserRepository."); } // Depending of the configuration of CCM use the appropriate method @@ -207,9 +213,9 @@ public class CcmShiroRealm extends AuthorizingRealm { // If no matching user is found throw an AuthenticationException if (user == null) { throw new AuthenticationException(String.format( - "No user identified by principal \"%s\" was found. Primary user " - + "identifier is \"%s\".", - userIdentifier, config.getPrimaryUserIdentifier())); + "No user identified by principal \"%s\" was found. Primary user " + + "identifier is \"%s\".", + userIdentifier, config.getPrimaryUserIdentifier())); } return user; @@ -220,6 +226,7 @@ public class CcmShiroRealm extends AuthorizingRealm { * used by Shiro. * * @param permission The permission to convert. + * * @return A Shiro permission string. */ private String permissionToString(final Permission permission) { diff --git a/ccm-core/src/main/java/org/libreccm/security/Group.java b/ccm-core/src/main/java/org/libreccm/security/Group.java index f2590e383..3ba918b05 100644 --- a/ccm-core/src/main/java/org/libreccm/security/Group.java +++ b/ccm-core/src/main/java/org/libreccm/security/Group.java @@ -36,7 +36,7 @@ import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; /** - * A group is bascially a collection of users. + * A group is basically a collection of users. * * Group extends the {@link Party} class. Therefore {@link Role}s can be * assigned to a group. When a {@link Role} is assigned to a group each member @@ -113,6 +113,14 @@ public class Group extends Party implements Serializable { return obj instanceof Group; } + @Override + @SuppressWarnings("PMD.UselessOverridingMethod") + public int hashCode() { + return super.hashCode(); + } + + + @Override public String toString(final String data) { return super.toString(String.format(", members = { %s }%s", diff --git a/ccm-core/src/main/java/org/libreccm/security/InheritsPermissions.java b/ccm-core/src/main/java/org/libreccm/security/InheritsPermissions.java index abab64f3c..023a55968 100644 --- a/ccm-core/src/main/java/org/libreccm/security/InheritsPermissions.java +++ b/ccm-core/src/main/java/org/libreccm/security/InheritsPermissions.java @@ -21,7 +21,13 @@ package org.libreccm.security; import org.libreccm.core.CcmObject; /** - * + * Subclasses of {@link CcmObject} can implement this interface to inherit + * the permissions of their parent object. This annotation is processed by the + * {@link PermissionChecker}. + * + * @see PermissionChecker#checkPermission(java.lang.String, org.libreccm.core.CcmObject) + * @see PermissionChecker#isPermitted(java.lang.String, org.libreccm.core.CcmObject) + * * @author Jens Pelzetter */ public interface InheritsPermissions { 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 f32a7f79b..4e6d7729f 100644 --- a/ccm-core/src/main/java/org/libreccm/security/Permission.java +++ b/ccm-core/src/main/java/org/libreccm/security/Permission.java @@ -43,7 +43,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; /** - * A permission grants a privilege on an object or systemwide to {@link Role}. + * A permission grants a privilege on an object or system wide to {@link Role}. * * @author Jens Pelzetter */ 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 cd892004f..153cca7b0 100644 --- a/ccm-core/src/main/java/org/libreccm/security/PermissionManager.java +++ b/ccm-core/src/main/java/org/libreccm/security/PermissionManager.java @@ -37,8 +37,11 @@ import javax.enterprise.context.RequestScoped; @RequestScoped public class PermissionManager { + @SuppressWarnings("PMD.LongVariable") private static final String QUERY_PARAM_OBJECT = "object"; + @SuppressWarnings("PMD.LongVariable") private static final String QUERY_PARAM_GRANTEE = "grantee"; + @SuppressWarnings("PMD.LongVariable") private static final String QUERY_PARAM_PRIVILEGE = "privilege"; diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredCollection.java b/ccm-core/src/main/java/org/libreccm/security/SecuredCollection.java index 3e7ca6546..b5e837224 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredCollection.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredCollection.java @@ -20,8 +20,6 @@ package org.libreccm.security; import com.arsdigita.util.UncheckedWrapperException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.libreccm.cdi.utils.CdiLookupException; import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.core.CcmObject; @@ -30,24 +28,59 @@ import java.util.Collection; import java.util.Iterator; /** + * A decorator for collections which checks if the current user is permitted to + * access the objects in the collection before returning them. + * + * This implementation of the {@link Collection} interface decorates a given + * collection. The methods which manipulate the collection are simply calling + * the methods of decorated collection. The methods which retrieve objects from + * the collection are first calling the method of the decorated collection, and + * check if the current subject is permitted to access the object. If the + * current subject is permitted to access the object the object is returned. + * Otherwise the object is replaced with a virtual object were the + * {@link CcmObject#displayName} property is set to {@code Access Denied}. + * Methods which return arrays or collections of objects from the decorated + * collection check each object in the array or collection and replace the + * objects which the current subject is not permitted to access with a + * Access denied object. * * @author Jens Pelzetter * - * @param + * @param Type of the objects in the collection. Must extend + * {@link CcmObject}. */ +@SuppressWarnings("PMD.TooManyMethods") public class SecuredCollection implements Collection { - private static final Logger LOGGER = LogManager.getLogger( - SecuredCollection.class); - + /** + * The decorated collection. + */ private final Collection collection; + /** + * The class of the objects in the collection. Required for creating the + * virtual Access denied object + */ private final Class clazz; + /** + * The privilege required for accessing the objects in the collection. + */ private final String requiredPrivilege; - + + /** + * Helper class for creating the Access denied object. + */ private final SecuredHelper securedHelper; + /** + * Create a new secured collection for the provided collection. + * + * @param collection The collection to secure. + * @param clazz The class of the objects in the collection. + * @param requiredPrivilege The privilege required to access the objects + * in the collection. + */ public SecuredCollection(final Collection collection, final Class clazz, final String requiredPrivilege) { @@ -74,7 +107,8 @@ public class SecuredCollection implements Collection { @Override public Iterator iterator() { - return new SecuredIterator<>(collection.iterator(), clazz, requiredPrivilege); + return new SecuredIterator<>(collection.iterator(), clazz, + requiredPrivilege); } @Override @@ -91,16 +125,17 @@ public class SecuredCollection implements Collection { final Object[] objects = collection.toArray(); for (int i = 0; i < objects.length; i++) { - if (!permissionChecker.isPermitted(requiredPrivilege, (E) objects[i])) { + if (!permissionChecker + .isPermitted(requiredPrivilege, (E) objects[i])) { objects[i] = securedHelper.generateAccessDeniedObject(); } } - + return objects; } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "PMD.UseVarargs"}) public T[] toArray(final T[] array) { final PermissionChecker permissionChecker; final CdiUtil cdiUtil = new CdiUtil(); @@ -110,10 +145,11 @@ public class SecuredCollection implements Collection { } catch (CdiLookupException ex) { throw new UncheckedWrapperException(ex); } - + final T[] objects = collection.toArray(array); - for(int i = 0; i < objects.length; i++) { - if (!permissionChecker.isPermitted(requiredPrivilege, (CcmObject) objects[i])) { + for (int i = 0; i < objects.length; i++) { + if (!permissionChecker.isPermitted(requiredPrivilege, + (CcmObject) objects[i])) { objects[i] = (T) securedHelper.generateAccessDeniedObject(); } } @@ -156,18 +192,4 @@ public class SecuredCollection implements Collection { collection.clear(); } -// private E generateAccessDeniedObject(final Class clazz) { -// final E placeholder; -// try { -// placeholder = clazz.newInstance(); -// placeholder.setDisplayName("Access denied"); -// -// return placeholder; -// } catch (InstantiationException | IllegalAccessException ex) { -// LOGGER.error( -// "Failed to create placeholder object. Returing null.", ex); -// return null; -// } -// } - } diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredEntry.java b/ccm-core/src/main/java/org/libreccm/security/SecuredEntry.java index 974d62809..d5e9a6b27 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredEntry.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredEntry.java @@ -23,16 +23,39 @@ import org.libreccm.core.CcmObject; import java.util.Map; /** - * + * A decorator for {@link Map.Entry} which checks if the current subject is + * permitted to access the value before returning it. If the current subject + * is not permitted to access the value it is replaced with an virtual + * Access Denied which is an object of the same class as the value. + * + * This class is not intended for direct use and is therefore only accessible + * from the {@code org.libreccm.security} package. The class is used by the + * {@link SecuredMap}. + * * @author Jens Pelzetter - * @param - * @param + * @param The type of the key of the entry. + * @param The type of the value of the entry. */ class SecuredEntry implements Map.Entry { + /** + * The decorated entry. + */ private final Map.Entry entry; + /** + * {@link SecuredHelper} for creating the virtual Access denied + * object. Provided by the {@link SecuredMap} which creates the + * {@code SecuredEntry}. + */ private final SecuredHelper securedHelper; + /** + * Creates a new secured entry. + * + * @param entry The entry to secure. + * @param securedHelper The {@link SecuredHelper} for creating the + * virtual Access denied object. + */ public SecuredEntry(final Map.Entry entry, final SecuredHelper securedHelper) { this.entry = entry; diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredEntryIterator.java b/ccm-core/src/main/java/org/libreccm/security/SecuredEntryIterator.java index 488721cba..720924a73 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredEntryIterator.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredEntryIterator.java @@ -24,15 +24,31 @@ import java.util.Iterator; import java.util.Map; /** + * A decorator for an iterator of {@link Map.Entry} objects which returns + * {@link SecuredEntry} objects. Used by the {@link SecuredMap}. * * @author Jens Pelzetter */ class SecuredEntryIterator, K, V extends CcmObject> implements Iterator { + /** + * The decorated iterator. + */ private final Iterator iterator; + /** + * The {@link SecuredHelper} for creating the virtual Access denied + * object. Provided by the {@link SecuredMap} creating the iterator. + */ private final SecuredHelper securedHelper; + /** + * Creates a new secured iterator for entries. + * + * @param iterator The iterator to secure. + * @param securedHelper The {@link SecuredHelper} for creating the virtual + * Access denied object. + */ public SecuredEntryIterator(final Iterator iterator, final SecuredHelper securedHelper) { this.iterator = iterator; diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredEntrySet.java b/ccm-core/src/main/java/org/libreccm/security/SecuredEntrySet.java index 551a341e4..4fbc720c5 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredEntrySet.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredEntrySet.java @@ -30,126 +30,146 @@ import java.util.Map; import java.util.Set; /** + * A decorator for a set of {@link Map.Entry} objects which returns + * {@link SecuredEntry} instances. Used by the {@link SecuredMap}. * * @author Jens Pelzetter */ -class SecuredEntrySet, K, V extends CcmObject> +@SuppressWarnings("PMD.TooManyMethods") +class SecuredEntrySet, K, V extends CcmObject> implements Set { - private final Set set; - private final String requiredPrivilege; - private final SecuredHelper securedHelper; - - public SecuredEntrySet(final Set set, - final String requiredPrivilege, - final SecuredHelper securedHelper) { - this.set = set; - this.requiredPrivilege = requiredPrivilege; - this.securedHelper = securedHelper; - } - - @Override - public int size() { - return set.size(); - } - - @Override - public boolean isEmpty() { - return set.isEmpty(); - } - - @Override - public boolean contains(final Object object) { - return set.contains(object); - } - - @Override - public Iterator iterator() { - return new SecuredEntryIterator<>(set.iterator(), securedHelper); - } - - @Override - @SuppressWarnings("unchecked") - public Object[] toArray() { - final PermissionChecker permissionChecker; - final CdiUtil cdiUtil = new CdiUtil(); - try { - permissionChecker = cdiUtil.findBean( - PermissionChecker.class); - } catch (CdiLookupException ex) { - throw new UncheckedWrapperException(ex); - } - - final Object[] entries = set.toArray(); - for (int i = 0; i < entries.length; i++) { - final E entry = (E) entries[i]; - if (!permissionChecker.isPermitted(requiredPrivilege, - entry.getValue())) { - entries[i] = securedHelper.generateAccessDeniedObject(); - } - } - - return entries; - } - - @Override - @SuppressWarnings("unchecked") - public T[] toArray(final T[] array) { - final PermissionChecker permissionChecker; - final CdiUtil cdiUtil = new CdiUtil(); - try { - permissionChecker = cdiUtil.findBean( - PermissionChecker.class); - } catch (CdiLookupException ex) { - throw new UncheckedWrapperException(ex); - } - - final E[] entries = (E[]) set.toArray(array); - for (int i = 0; i < entries.length; i++) { - if (!permissionChecker.isPermitted(requiredPrivilege, - entries[i].getValue())) { - entries[i] = (E) securedHelper.generateAccessDeniedObject(); - } - } - - return (T[]) entries; - } - - @Override - public boolean add(final E element) { - return set.add(element); - } - - @Override - public boolean remove(final Object object) { - return set.remove(object); - } - - @Override - public boolean containsAll(final Collection collection) { - return set.containsAll(collection); - } - - @Override - public boolean addAll(final Collection collection) { - return set.addAll(collection); - } - - @Override - public boolean retainAll(final Collection collection) { - return set.retainAll(collection); - } - - @Override - public boolean removeAll(final Collection collection) { - return set.removeAll(collection); - } - - @Override - public void clear() { - set.clear(); - } + /** + * The decorated set. + */ + private final Set set; + /** + * The privilege required to access the values of the entries in the set. + */ + private final String requiredPrivilege; + /** + * {@link SecuredHelper} for creating the virtual Access denied + * object. + */ + private final SecuredHelper securedHelper; + /** + * Creates a new secured entry set. + * + * @param set The set of entries to secure. + * @param requiredPrivilege The privilege required to access the values of + * the entries. + * @param securedHelper The {@link SecuredHelper} for creating the + * virtual Access denied objects. + */ + public SecuredEntrySet(final Set set, + final String requiredPrivilege, + final SecuredHelper securedHelper) { + this.set = set; + this.requiredPrivilege = requiredPrivilege; + this.securedHelper = securedHelper; } + @Override + public int size() { + return set.size(); + } + @Override + public boolean isEmpty() { + return set.isEmpty(); + } + + @Override + public boolean contains(final Object object) { + return set.contains(object); + } + + @Override + public Iterator iterator() { + return new SecuredEntryIterator<>(set.iterator(), securedHelper); + } + + @Override + @SuppressWarnings("unchecked") + public Object[] toArray() { + final PermissionChecker permissionChecker; + final CdiUtil cdiUtil = new CdiUtil(); + try { + permissionChecker = cdiUtil.findBean( + PermissionChecker.class); + } catch (CdiLookupException ex) { + throw new UncheckedWrapperException(ex); + } + + final Object[] entries = set.toArray(); + for (int i = 0; i < entries.length; i++) { + final E entry = (E) entries[i]; + if (!permissionChecker.isPermitted(requiredPrivilege, + entry.getValue())) { + entries[i] = securedHelper.generateAccessDeniedObject(); + } + } + + return entries; + } + + @Override + @SuppressWarnings({"unchecked", "PMD.UseVarargs"}) + public T[] toArray(final T[] array) { + final PermissionChecker permissionChecker; + final CdiUtil cdiUtil = new CdiUtil(); + try { + permissionChecker = cdiUtil.findBean( + PermissionChecker.class); + } catch (CdiLookupException ex) { + throw new UncheckedWrapperException(ex); + } + + final E[] entries = (E[]) set.toArray(array); + for (int i = 0; i < entries.length; i++) { + if (!permissionChecker.isPermitted(requiredPrivilege, + entries[i].getValue())) { + entries[i] = (E) securedHelper.generateAccessDeniedObject(); + } + } + + return (T[]) entries; + } + + @Override + public boolean add(final E element) { + return set.add(element); + } + + @Override + public boolean remove(final Object object) { + return set.remove(object); + } + + @Override + public boolean containsAll(final Collection collection) { + return set.containsAll(collection); + } + + @Override + public boolean addAll(final Collection collection) { + return set.addAll(collection); + } + + @Override + public boolean retainAll(final Collection collection) { + return set.retainAll(collection); + } + + @Override + public boolean removeAll(final Collection collection) { + return set.removeAll(collection); + } + + @Override + public void clear() { + set.clear(); + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredHelper.java b/ccm-core/src/main/java/org/libreccm/security/SecuredHelper.java index 310a7ab77..1011e663d 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredHelper.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredHelper.java @@ -35,7 +35,13 @@ class SecuredHelper { private final static Logger LOGGER = LogManager.getLogger( SecuredHelper.class); + /** + * Class of the objects in the collection. + */ private final Class clazz; + /** + * Privilege required to access the objects in the collection. + */ private final String requiredPrivilege; protected SecuredHelper(final Class clazz, @@ -51,8 +57,8 @@ class SecuredHelper { * @param object The object to check. * @return The provided {@code object} if the current subject has the * permission to access it with the provided {@code privilege}. Otherwise a - * placeholder object is returned whichs {@link CcmObject#displayName} - * property is set to {@code Access denied}. + * placeholder object is returned. The {@link CcmObject#displayName} + * property of these object is set to {@code Access denied}. */ protected E canAccess(final E object) { if (object == null) { @@ -76,7 +82,7 @@ class SecuredHelper { } /** - * Helper method for creating an "Access denied" placeholder object. + * Helper method for creating an Access denied placeholder object. * * @return An object of the provided {@link #clazz} with it's * {@link CcmObject#displayName} property set to {@code Access denied}. diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredIterator.java b/ccm-core/src/main/java/org/libreccm/security/SecuredIterator.java index 168386b30..eeb4c1832 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredIterator.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredIterator.java @@ -18,12 +18,6 @@ */ package org.libreccm.security; -import com.arsdigita.util.UncheckedWrapperException; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.libreccm.cdi.utils.CdiLookupException; -import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.core.CcmObject; import java.util.Iterator; diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredList.java b/ccm-core/src/main/java/org/libreccm/security/SecuredList.java index 73daa9721..2c1049671 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredList.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredList.java @@ -24,19 +24,43 @@ import java.util.ListIterator; import org.libreccm.core.CcmObject; /** + * A decorator for {@link List}s of {@link CcmObject}s which checks if the + * current subject is permitted to access the objects in the list before + * returning them. * * @author Jens Pelzetter - * @param + * + * @param The type of the objects in the list. */ public class SecuredList - extends SecuredCollection - implements List { + extends SecuredCollection + implements List { + /** + * The decorated list. + */ private final List list; + /** + * The class of the objects in the list. + */ private final Class clazz; + /** + * The privilege required to access the objects in the list. + */ private final String requiredPrivilege; + /** + * {@link SecuredHelper} used by the list. + */ private final SecuredHelper securedHelper; + /** + * Creates a new secured list. + * + * @param list The {@link List} to secure. + * @param clazz Class of the objects in the list. + * @param requiredPrivilege The privilege required to access the objects in + * the list. + */ public SecuredList(final List list, final Class clazz, final String requiredPrivilege) { diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredListIterator.java b/ccm-core/src/main/java/org/libreccm/security/SecuredListIterator.java index 67b9c91a2..993a792c3 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredListIterator.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredListIterator.java @@ -19,25 +19,38 @@ package org.libreccm.security; import java.util.ListIterator; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; + import org.libreccm.core.CcmObject; /** + * A decorator for {@link ListIterator} which checks if the current subject is + * permitted to access an object from the list the iterator iterates over before + * returning it. * * @author Jens Pelzetter * @param */ public class SecuredListIterator - extends SecuredIterator - implements ListIterator { - - private final static Logger LOGGER = LogManager.getLogger( - SecuredListIterator.class); + extends SecuredIterator + implements ListIterator { + /** + * The decorated {@link ListIterator}. + */ private final ListIterator iterator; + /** + * {@link SecuredHelper} used by this iterator. + */ private final SecuredHelper securedHelper; + /** + * Creates a new secured list iterator. + * + * @param iterator The iterator to secure. + * @param clazz The class of the objects in the list. + * @param requiredPrivilege The privilege required to access the objects in + * list. + */ public SecuredListIterator(final ListIterator iterator, final Class clazz, final String requiredPrivilege) { diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredMap.java b/ccm-core/src/main/java/org/libreccm/security/SecuredMap.java index 31279a38d..63786c7df 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredMap.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredMap.java @@ -18,30 +18,49 @@ */ package org.libreccm.security; -import com.arsdigita.util.UncheckedWrapperException; - -import org.libreccm.cdi.utils.CdiLookupException; -import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.core.CcmObject; import java.util.Collection; -import java.util.Iterator; import java.util.Map; import java.util.Set; /** + * Decorator for {@link Map} which checks if the current subject is permitted to + * access the values of the decorated map before returning them. The keys used + * by the map are not checked. * * @author Jens Pelzetter - * @param - * @param + * + * @param Type of the keys + * @param Type of the values. */ public class SecuredMap implements Map { + /** + * The decorated map. + */ private final Map map; + /** + * Class of the values in map. + */ private final Class clazz; + /** + * The privilege required to access the values of the map. + */ private final String requiredPrivilege; + /** + * {@link SecuredHelper} used by the map. + */ private final SecuredHelper securedHelper; + /** + * Creates a new secured map. + * + * @param map The map to secure. + * @param clazz Class of the values in the map. + * @param requiredPrivilege The privilege required to access the values of + * the map. + */ public SecuredMap(final Map map, final Class clazz, final String requiredPrivilege) { diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredNavigableMap.java b/ccm-core/src/main/java/org/libreccm/security/SecuredNavigableMap.java index 4a7951e55..aa8215347 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredNavigableMap.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredNavigableMap.java @@ -24,20 +24,44 @@ import java.util.NavigableMap; import java.util.NavigableSet; /** - * + * A decorator for {@link NavigableMap} which checks if the current subject is + * permitted to access the values of the decorated map before returning them. + * * @author Jens Pelzetter - * @param - * @param + * + * @param Type of the keys. + * @param Type of the values. */ +@SuppressWarnings("PMD.TooManyMethods") public class SecuredNavigableMap extends SecuredSortedMap implements NavigableMap { + /** + * The decorated navigable map. + */ private final NavigableMap navigableMap; + /** + * Class of the values. + */ private final Class clazz; + /** + * The privilege required to access the values of the decorated map. + */ private final String requiredPrivilege; + /** + * {@link SecuredHelper} used by the decorator. + */ private final SecuredHelper securedHelper; + /** + * Creates a new secured navigable map. + * + * @param navigableMap The navigable map to secure. + * @param clazz The class of the values of the navigable map. + * @param requiredPrivilege The privilege required to access the objects of + * the navigable map. + */ public SecuredNavigableMap(final NavigableMap navigableMap, final Class clazz, final String requiredPrivilege) { diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredNavigableSet.java b/ccm-core/src/main/java/org/libreccm/security/SecuredNavigableSet.java index d9fd05a5d..39a5dbf39 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredNavigableSet.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredNavigableSet.java @@ -23,19 +23,44 @@ import java.util.NavigableSet; import org.libreccm.core.CcmObject; /** + * A decorated for {@link NavigableSet} which checks if the current subject is + * permitted to access the objects from the decorated navigable set before + * returning them. * * @author Jens Pelzetter - * @param + * + * @param Type of the objects in the navigable set. */ +@SuppressWarnings("PMD.TooManyMethods") public class SecuredNavigableSet - extends SecuredSortedSet - implements NavigableSet { + extends SecuredSortedSet + implements NavigableSet { + /** + * The decorated navigable set. + */ private final NavigableSet set; + /** + * Class of the objects in the navigable set. + */ private final Class clazz; + /** + * The privilege required to access the objects of the navigable set. + */ private final String requiredPrivilege; + /** + * {@link SecuredHelper} used by this decorator. + */ private final SecuredHelper securedHelper; + /** + * Creates a new secured navigable set. + * + * @param set The set to secure. + * @param clazz Class of the objects in the set. + * @param requiredPrivilege The privilege required to access the objects in + * the set. + */ public SecuredNavigableSet(final NavigableSet set, final Class clazz, final String requiredPrivilege) { diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredSet.java b/ccm-core/src/main/java/org/libreccm/security/SecuredSet.java index da50405db..15533abad 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredSet.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredSet.java @@ -22,15 +22,26 @@ import java.util.Set; import org.libreccm.core.CcmObject; /** + * A decorator for {@link Set} which checks if the current subject is permitted + * to access the objects from the decorated set before returning them. * * @author Jens Pelzetter - * @param + * + * @param Type of the objects in the set. */ public class SecuredSet - extends SecuredCollection - implements Set { + extends SecuredCollection + implements Set { - public SecuredSet(final Set set, + /** + * Creates a new secured set. + * + * @param set The {@link Set} to secure. + * @param clazz Class of the objects in the set. + * @param requiredPrivilege Privilege required to access the objects in the + * set. + */ + public SecuredSet(final Set set, final Class clazz, final String requiredPrivilege) { super(set, clazz, requiredPrivilege); diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredSortedMap.java b/ccm-core/src/main/java/org/libreccm/security/SecuredSortedMap.java index 7bfd66843..22da6e185 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredSortedMap.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredSortedMap.java @@ -24,19 +24,40 @@ import java.util.Comparator; import java.util.SortedMap; /** + * A decorator for {@link SortedMap} which checks if the current subject is + * permitted to access the values from the decorated sorted map before returning + * them. * * @author Jens Pelzetter - * @param - * @param + * + * @param Type of the keys. + * @param Type of the values. */ public class SecuredSortedMap extends SecuredMap implements SortedMap { + /** + * The decorated sorted map. + */ private final SortedMap sortedMap; + /** + * Class of the values of the decorated sorted map. + */ private final Class clazz; + /** + * Privilege required to access the values of the decorated sorted map. + */ private final String requiredPrivilege; + /** + * Creates new secured sorted map. + * + * @param sortedMap The map to secure. + * @param clazz Class of the values. + * @param requiredPrivilege Privilege required to access the values of the + * secured sorted map. + */ public SecuredSortedMap(final SortedMap sortedMap, final Class clazz, final String requiredPrivilege) { diff --git a/ccm-core/src/main/java/org/libreccm/security/SecuredSortedSet.java b/ccm-core/src/main/java/org/libreccm/security/SecuredSortedSet.java index adc379d3c..43846fe95 100644 --- a/ccm-core/src/main/java/org/libreccm/security/SecuredSortedSet.java +++ b/ccm-core/src/main/java/org/libreccm/security/SecuredSortedSet.java @@ -19,24 +19,48 @@ package org.libreccm.security; import java.util.Comparator; -import java.util.Set; import java.util.SortedSet; + import org.libreccm.core.CcmObject; /** + * Decorator for {@link SortedSet} which checks if the current subject is + * permitted to access the objects from the decorated sorted set before + * returning them. * * @author Jens Pelzetter - * @param + * + * @param Type of the objects in the decorated sorted set. */ public class SecuredSortedSet - extends SecuredSet - implements SortedSet { + extends SecuredSet + implements SortedSet { + /** + * The decorated sorted set. + */ private final SortedSet set; + /** + * The class of the objects in the decorated sorted set. + */ private final Class clazz; + /** + * The privilege required to access the objects in the decorated set. + */ private final String requiredPrivilege; + /** + * {@link SecuredHelper} used by this decorator. + */ private final SecuredHelper securedHelper; + /** + * Creates new secured sorted set. + * + * @param set The sorted set to secure. + * @param clazz The class of the objects in the set. + * @param requiredPrivilege The privilege required to access the objects in + * the set. + */ public SecuredSortedSet(final SortedSet set, final Class clazz, final String requiredPrivilege) { diff --git a/ccm-core/src/main/java/org/libreccm/security/User.java b/ccm-core/src/main/java/org/libreccm/security/User.java index 56fe95984..44d5f1848 100644 --- a/ccm-core/src/main/java/org/libreccm/security/User.java +++ b/ccm-core/src/main/java/org/libreccm/security/User.java @@ -65,7 +65,7 @@ import javax.xml.bind.annotation.XmlTransient; @XmlRootElement(name = "user", namespace = CORE_XML_NS) //Supressing a few warnings from PMD because they misleading here. //User is perfectly fine class name, and the complexity is not to high... -@SuppressWarnings({"PMD.ShortClassName"}) +@SuppressWarnings({"PMD.ShortClassName", "PMD.LongVariable"}) public class User extends Party implements Serializable { private static final long serialVersionUID = 4035223413596611393L; @@ -97,7 +97,6 @@ public class User extends Party implements Serializable { })) @NotNull @XmlElement(name = "primary-email-address", namespace = CORE_XML_NS) - @SuppressWarnings("PMD.LongVariable") private EmailAddress primaryEmailAddress; /** @@ -136,7 +135,6 @@ public class User extends Party implements Serializable { */ @Column(name = "PASSWORD_RESET_REQUIRED") //Can't shorten the name without making the name cryptic. - @SuppressWarnings("PMD.LongVariable") private boolean passwordResetRequired; /** @@ -216,7 +214,6 @@ public class User extends Party implements Serializable { return passwordResetRequired; } - @SuppressWarnings("PMD.LongVariable") protected void setPasswordResetRequired(final boolean passwordResetRequired) { this.passwordResetRequired = passwordResetRequired; } diff --git a/ccm-core/src/main/java/org/libreccm/security/package-info.java b/ccm-core/src/main/java/org/libreccm/security/package-info.java index 874390218..d89cb7003 100644 --- a/ccm-core/src/main/java/org/libreccm/security/package-info.java +++ b/ccm-core/src/main/java/org/libreccm/security/package-info.java @@ -15,27 +15,27 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA -*/ + */ /** - * This package contains all classes dealing with authentication and + * This package contains all classes dealing with authentication and * authorisation in LibreCCM. - * + * * Most of this classes are only relevant for the developers of the core part * of LibreCCM and and core administration UI. For developers of modules the * primary interface is the Apache Shiro Library. Module developers usually have - * the use these classes only in the {@code CcmModule#install(InstallEvent) - * method to create roles and privileges for their module. Therefore most - * methods of these classes can only be invoked by the System user. - * + * the use these classes only in the CcmModule#install(InstallEvent + * method to create roles and privileges for their module. Therefore most + * methods of these classes can only be invoked by the System user. + * * The check if the current user is logged in and/or has a certain permission - * you have to obtain the current {@link Subject} from Shiro. In LibreCCM the - * subject is provided using CDI. In classes eligible for injection you simply - * inject the current subject. In other classes you can use the {@link CdiUtil} - * class. - * - * Another option for method of CDI beans is to use the interceptors provided by + * you have to obtain the current Subject from Shiro. In + * LibreCCM the subject is provided using CDI. In classes eligible for injection + * you simply inject the current subject. In other classes you can use the + * CdiUtil class. + * + * Another option for method of CDI beans is to use the interceptors provided by * this package. - * + * * @see CcmModule */ package org.libreccm.security; diff --git a/src/site/apt/auth.apt b/src/site/apt/auth.apt new file mode 100644 index 000000000..96dca9158 --- /dev/null +++ b/src/site/apt/auth.apt @@ -0,0 +1,97 @@ + -------------------------------------------- + Authentication and Authorisation in LibreCCM + -------------------------------------------- + Jens Pelzetter + -------------------------------------------- + 2015-12-04 + -------------------------------------------- + +Authentication and Authorisation in LibreCCM + + LibreCCM uses {{{http://shiro.apache.org}Apache Shiro}} as the foundation + for its authentication and authorisation system. All classes related to + authentication and authorisation are provided by the + {{{ccm-core/apidocs/index.html?org/libreccm/security/package-summary.html}org.libreccm.security}} + from the CCM Core module. + +* Definitions + + [User] A user is person (or a system) which accesses LibreCCM. + + [Group] A collection of users. A user can be member of multiple groups. + + [Role] A role is basically a collection of permissions and tasks (workflows) + which can be assigned a user or group. + + [Permission] Grants a certain privilege to a role. Permissions are either + systemwide permissions or permissions granting a certain + privilege on a certain CcmObject. + + [Privilege] Describes an action. + +* Managing permissions, roles, users and groups. + + Permissions are granted and revoked using the + {{{ccm-core/apidocs/index.html?org/libreccm/security/PermissionManager.html}PermissionManager}} + class. This class is a CDI bean which can be injected or manually retrieved + using the {{{ccm-core/apidocs/index.html?org/libreccm/cdi/utils/CdiUtil.html}CdiUtil}} + utility. + + Users, Groups and Roles are managed using the repository and manager classes + provided by the {{{ccm-core/apidocs/index.html?org/libreccm/security/package-summary.html}org.libreccm.security}} + package. Most module developers will not have to deal with these classes. + Only if the module wants to create special roles it might be necessary + for a module to use the {{{ccm-core/apidocs/index.html?org/libreccm/security/RoleManager.html}RoleManager}} + or the {{{ccm-core/apidocs/index.html?org/libreccm/security/RoleRepository.html}RoleRepository}}. + +* Validating permissions + + Validating permissions can be done in various ways. For CDI enabled beans + an Interceptor is provided for securing methods. Also a class for checking + permissions is provided which can be injected using CDI or retrieved + using {{{ccm-core/apidocs/index.html?org/libreccm/cdi/utils/CdiUtil.html}CdiUtil}} + utility. + +** Using the CDI interceptor + + A method is secured by + annotating the method with the <<<@AuthorizationRequired>>> annotation. + Additional at least one of the annotations <<<@RequiresRole>>> and + <<<@RequiresPrivilege>>> must be used. + + The <<<@{{{ccm-core/apidocs/index.html?org/libreccm/security/RequiresRole.html}RequiresRole}}>>> + annotation can only be used on method. If the current subject is not + authenticated (not logged in) or does not have the role given by the + annotation the interceptor will throw an <<>>. + + The <<<@{{{ccm-core/apidocs/index.html?org/libreccm/security/RequiresPrivilege.html}RequiresPrivilege}}>>> + annotation can be used at the method or on a parameter of the type + <<>> of the secured method. If the annotation is put on a method + the current subject must be authenticated and have a permission granting + the user the given privilege. + + If the annotation is used on a method parameter the current user must be + authenticated and have a permission which grants the user the given + privilege on the object with which the method is called. + + If the current subject does have appropriate permissions an + <<>> is thrown. + + Under the hood the interceptor uses the <<>> class which + is described in the next section. + +** Using the <<>> + + An instance of the + <<<@{{{ccm-core/apidocs/index.html?org/libreccm/security/PermissionChecker.html}PermisssionChecker}}>>> + can be obtained by injection or by manual lookup. The <<>> + provides various methods for checking permissions. Details can be found in + the JavaDoc. + +** Secured collections + + Methods in the repository classes often return collections. To make securing + these collections easy the {{{ccm-core/apidocs/index.html?org/libreccm/security/package-summary.html}org.libreccm.security}} + package provides decorators for the collections from the Java Standard API + which check if the current subject is permitted to access an object before + returning it. Details can be found in the JavaDoc of the decorators. \ No newline at end of file diff --git a/src/site/site.xml b/src/site/site.xml index c6afd255b..f0833a747 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -24,6 +24,8 @@ href="module-system/anatomy.html"/> +