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
pull/2/head
jensp 2015-12-04 14:53:43 +00:00
parent bf5ddceda7
commit d043fbea56
24 changed files with 607 additions and 242 deletions

View File

@ -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;

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@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<Bean<?>> beans = beanManager.
getBeans(RoleRepository.class);
getBeans(RoleRepository.class);
final Iterator<Bean<?>> iterator = beans.iterator();
if (iterator.hasNext()) {
@SuppressWarnings("unchecked")
final Bean<RoleRepository> bean = (Bean<RoleRepository>) iterator.
next();
final Bean<RoleRepository> bean
= (Bean<RoleRepository>) iterator.
next();
final CreationalContext<RoleRepository> 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<Role> 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<Bean<?>> beans = beanManager.getBeans(
UserRepository.class);
UserRepository.class);
final Iterator<Bean<?>> iterator = beans.iterator();
if (iterator.hasNext()) {
@SuppressWarnings("unchecked")
final Bean<UserRepository> bean = (Bean<UserRepository>) iterator
.next();
.next();
final CreationalContext<UserRepository> 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) {

View File

@ -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",

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public interface InheritsPermissions {

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/

View File

@ -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";

View File

@ -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
* <em>Access denied</em> object.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*
* @param <E>
* @param <E> Type of the objects in the collection. Must extend
* {@link CcmObject}.
*/
@SuppressWarnings("PMD.TooManyMethods")
public class SecuredCollection<E extends CcmObject> implements Collection<E> {
private static final Logger LOGGER = LogManager.getLogger(
SecuredCollection.class);
/**
* The decorated collection.
*/
private final Collection<E> collection;
/**
* The class of the objects in the collection. Required for creating the
* virtual <em>Access denied</em> object
*/
private final Class<E> clazz;
/**
* The privilege required for accessing the objects in the collection.
*/
private final String requiredPrivilege;
/**
* Helper class for creating the <em>Access denied</em> object.
*/
private final SecuredHelper<E> 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<E> collection,
final Class<E> clazz,
final String requiredPrivilege) {
@ -74,7 +107,8 @@ public class SecuredCollection<E extends CcmObject> implements Collection<E> {
@Override
public Iterator<E> iterator() {
return new SecuredIterator<>(collection.iterator(), clazz, requiredPrivilege);
return new SecuredIterator<>(collection.iterator(), clazz,
requiredPrivilege);
}
@Override
@ -91,16 +125,17 @@ public class SecuredCollection<E extends CcmObject> implements Collection<E> {
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> T[] toArray(final T[] array) {
final PermissionChecker permissionChecker;
final CdiUtil cdiUtil = new CdiUtil();
@ -110,10 +145,11 @@ public class SecuredCollection<E extends CcmObject> implements Collection<E> {
} 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<E extends CcmObject> implements Collection<E> {
collection.clear();
}
// private E generateAccessDeniedObject(final Class<E> 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;
// }
// }
}

View File

@ -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
* <em>Access Denied</em> 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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <K>
* @param <V>
* @param <K> The type of the key of the entry.
* @param <V> The type of the value of the entry.
*/
class SecuredEntry<K, V extends CcmObject> implements Map.Entry<K, V> {
/**
* The decorated entry.
*/
private final Map.Entry<K, V> entry;
/**
* {@link SecuredHelper} for creating the virtual <em>Access denied</em>
* object. Provided by the {@link SecuredMap} which creates the
* {@code SecuredEntry}.
*/
private final SecuredHelper<V> securedHelper;
/**
* Creates a new secured entry.
*
* @param entry The entry to secure.
* @param securedHelper The {@link SecuredHelper} for creating the
* virtual <em>Access denied</em> object.
*/
public SecuredEntry(final Map.Entry<K, V> entry,
final SecuredHelper<V> securedHelper) {
this.entry = entry;

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
class SecuredEntryIterator<E extends Map.Entry<K, V>, K, V extends CcmObject>
implements Iterator<E> {
/**
* The decorated iterator.
*/
private final Iterator<E> iterator;
/**
* The {@link SecuredHelper} for creating the virtual <em>Access denied</em>
* object. Provided by the {@link SecuredMap} creating the iterator.
*/
private final SecuredHelper<V> securedHelper;
/**
* Creates a new secured iterator for entries.
*
* @param iterator The iterator to secure.
* @param securedHelper The {@link SecuredHelper} for creating the virtual
* <em>Access denied</em> object.
*/
public SecuredEntryIterator(final Iterator<E> iterator,
final SecuredHelper<V> securedHelper) {
this.iterator = iterator;

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
class SecuredEntrySet<E extends Map.Entry<K, V>, K, V extends CcmObject>
@SuppressWarnings("PMD.TooManyMethods")
class SecuredEntrySet<E extends Map.Entry<K, V>, K, V extends CcmObject>
implements Set<E> {
private final Set<E> set;
private final String requiredPrivilege;
private final SecuredHelper<V> securedHelper;
public SecuredEntrySet(final Set<E> set,
final String requiredPrivilege,
final SecuredHelper<V> 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<E> 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> 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<? extends E> 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<E> set;
/**
* The privilege required to access the values of the entries in the set.
*/
private final String requiredPrivilege;
/**
* {@link SecuredHelper} for creating the virtual <em>Access denied</em>
* object.
*/
private final SecuredHelper<V> 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 <em>Access denied</em> objects.
*/
public SecuredEntrySet(final Set<E> set,
final String requiredPrivilege,
final SecuredHelper<V> 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<E> 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> 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<? extends E> 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();
}
}

View File

@ -35,7 +35,13 @@ class SecuredHelper<E extends CcmObject> {
private final static Logger LOGGER = LogManager.getLogger(
SecuredHelper.class);
/**
* Class of the objects in the collection.
*/
private final Class<E> clazz;
/**
* Privilege required to access the objects in the collection.
*/
private final String requiredPrivilege;
protected SecuredHelper(final Class<E> clazz,
@ -51,8 +57,8 @@ class SecuredHelper<E extends CcmObject> {
* @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<E extends CcmObject> {
}
/**
* Helper method for creating an "Access denied" placeholder object.
* Helper method for creating an <em>Access denied</em> placeholder object.
*
* @return An object of the provided {@link #clazz} with it's
* {@link CcmObject#displayName} property set to {@code Access denied}.

View File

@ -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;

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <E>
*
* @param <E> The type of the objects in the list.
*/
public class SecuredList<E extends CcmObject>
extends SecuredCollection<E>
implements List<E> {
extends SecuredCollection<E>
implements List<E> {
/**
* The decorated list.
*/
private final List<E> list;
/**
* The class of the objects in the list.
*/
private final Class<E> clazz;
/**
* The privilege required to access the objects in the list.
*/
private final String requiredPrivilege;
/**
* {@link SecuredHelper} used by the list.
*/
private final SecuredHelper<E> 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<E> list,
final Class<E> clazz,
final String requiredPrivilege) {

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <E>
*/
public class SecuredListIterator<E extends CcmObject>
extends SecuredIterator<E>
implements ListIterator<E> {
private final static Logger LOGGER = LogManager.getLogger(
SecuredListIterator.class);
extends SecuredIterator<E>
implements ListIterator<E> {
/**
* The decorated {@link ListIterator}.
*/
private final ListIterator<E> iterator;
/**
* {@link SecuredHelper} used by this iterator.
*/
private final SecuredHelper<E> 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<E> iterator,
final Class<E> clazz,
final String requiredPrivilege) {

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <K>
* @param <V>
*
* @param <K> Type of the keys
* @param <V> Type of the values.
*/
public class SecuredMap<K, V extends CcmObject> implements Map<K, V> {
/**
* The decorated map.
*/
private final Map<K, V> map;
/**
* Class of the values in map.
*/
private final Class<V> clazz;
/**
* The privilege required to access the values of the map.
*/
private final String requiredPrivilege;
/**
* {@link SecuredHelper} used by the map.
*/
private final SecuredHelper<V> 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<K, V> map,
final Class<V> clazz,
final String requiredPrivilege) {

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <K>
* @param <V>
*
* @param <K> Type of the keys.
* @param <V> Type of the values.
*/
@SuppressWarnings("PMD.TooManyMethods")
public class SecuredNavigableMap<K, V extends CcmObject>
extends SecuredSortedMap<K, V>
implements NavigableMap<K, V> {
/**
* The decorated navigable map.
*/
private final NavigableMap<K, V> navigableMap;
/**
* Class of the values.
*/
private final Class<V> 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<V> 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<K, V> navigableMap,
final Class<V> clazz,
final String requiredPrivilege) {

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <E>
*
* @param <E> Type of the objects in the navigable set.
*/
@SuppressWarnings("PMD.TooManyMethods")
public class SecuredNavigableSet<E extends CcmObject>
extends SecuredSortedSet<E>
implements NavigableSet<E> {
extends SecuredSortedSet<E>
implements NavigableSet<E> {
/**
* The decorated navigable set.
*/
private final NavigableSet<E> set;
/**
* Class of the objects in the navigable set.
*/
private final Class<E> 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<E> 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<E> set,
final Class<E> clazz,
final String requiredPrivilege) {

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <E>
*
* @param <E> Type of the objects in the set.
*/
public class SecuredSet<E extends CcmObject>
extends SecuredCollection<E>
implements Set<E> {
extends SecuredCollection<E>
implements Set<E> {
public SecuredSet(final Set<E> 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<E> set,
final Class<E> clazz,
final String requiredPrivilege) {
super(set, clazz, requiredPrivilege);

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <K>
* @param <V>
*
* @param <K> Type of the keys.
* @param <V> Type of the values.
*/
public class SecuredSortedMap<K, V extends CcmObject>
extends SecuredMap<K, V>
implements SortedMap<K, V> {
/**
* The decorated sorted map.
*/
private final SortedMap<K, V> sortedMap;
/**
* Class of the values of the decorated sorted map.
*/
private final Class<V> 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<K, V> sortedMap,
final Class<V> clazz,
final String requiredPrivilege) {

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <E>
*
* @param <E> Type of the objects in the decorated sorted set.
*/
public class SecuredSortedSet<E extends CcmObject>
extends SecuredSet<E>
implements SortedSet<E> {
extends SecuredSet<E>
implements SortedSet<E> {
/**
* The decorated sorted set.
*/
private final SortedSet<E> set;
/**
* The class of the objects in the decorated sorted set.
*/
private final Class<E> 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<E> 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<E> set,
final Class<E> clazz,
final String requiredPrivilege) {

View File

@ -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;
}

View File

@ -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 <code>CcmModule#install(InstallEvent</code>
* 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 <code>Subject</code> 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
* <a href="../cdi/utils/CdiUtil.html">CdiUtil</a> class.
*
* Another option for method of CDI beans is to use the interceptors provided by
* this package.
*
*
* @see CcmModule
*/
package org.libreccm.security;

View File

@ -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 <<<AuthorizationException>>>.
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
<<<CcmObject>>> 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
<<<AuthorizationException>>> is thrown.
Under the hood the interceptor uses the <<<PermissionChecker>>> class which
is described in the next section.
** Using the <<<PermissionChecker>>>
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 <<<PermissionChecker>>>
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.

View File

@ -24,6 +24,8 @@
href="module-system/anatomy.html"/>
</item>
<item name="How LibreCCM uses JPA" href="entities.html"/>
<item name="Authentication and Authorization"
href="auth.html"/>
</menu>
<menu ref="modules"/>