/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.services.access;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import nl.strohalm.cyclos.access.AdminPermission;
import nl.strohalm.cyclos.access.BasicPermission;
import nl.strohalm.cyclos.access.BrokerPermission;
import nl.strohalm.cyclos.access.MemberPermission;
import nl.strohalm.cyclos.access.ModuleType;
import nl.strohalm.cyclos.access.OperatorPermission;
import nl.strohalm.cyclos.access.Permission;
import nl.strohalm.cyclos.access.PermissionCheck;
import nl.strohalm.cyclos.entities.Entity;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Abstract implementation for {@link PermissionCheck}
*
* @author luis
*/
public abstract class AbstractPermissionCheck implements PermissionCheck {
public static class RequiredValuesBean {
private final Entity[] entities;
private final Permission permission;
public RequiredValuesBean(final Permission permission, final Entity[] entities) {
this.permission = permission;
this.entities = entities;
}
public Entity[] getEntities() {
return entities;
}
public Permission getPermission() {
return permission;
}
}
private static final Log LOG = LogFactory.getLog(AbstractPermissionCheck.class);
private static boolean trace;
static {
trace = Boolean.getBoolean("cyclos.tracePermissionChecks");
}
/* Map from multivalued permissions to entities. All entities mapped to a permission must be contained in the allowed permission's values */
protected Map<Permission, RequiredValuesBean> requiredValuesMap;
protected AdminPermission[] adminPermissions;
protected BrokerPermission[] brokerPermissions;
protected MemberPermission[] memberPermissions;
protected OperatorPermission[] operatorPermissions;
protected MemberPermission[] operatorMemberPermissions;
protected BasicPermission[] basicPermissions;
private boolean permissionChecked;
private StackTraceElement[] stackTrace;
public AbstractPermissionCheck() {
if (trace) {
stackTrace = Thread.currentThread().getStackTrace();
}
}
@Override
public PermissionCheck admin(final AdminPermission... permissions) {
adminPermissions = (AdminPermission[]) (adminPermissions == null ? permissions : ArrayUtils.addAll(adminPermissions, permissions));
return this;
}
@Override
public PermissionCheck adminFor(final AdminPermission permission, final Entity... entities) {
checkMultivaluedPermission(permission, null);
admin(new AdminPermission[] { permission });
addRequiredValues(permission, permission, entities);
return this;
}
@Override
public PermissionCheck basic(final BasicPermission... permissions) {
basicPermissions = (BasicPermission[]) (basicPermissions == null ? permissions : ArrayUtils.addAll(basicPermissions, permissions));
return this;
}
@Override
public PermissionCheck broker(final BrokerPermission... permissions) {
brokerPermissions = (BrokerPermission[]) (brokerPermissions == null ? permissions : ArrayUtils.addAll(brokerPermissions, permissions));
return this;
}
@Override
public PermissionCheck brokerFor(final BrokerPermission permission, final Entity... entities) {
checkMultivaluedPermission(permission, null);
broker(new BrokerPermission[] { permission });
addRequiredValues(permission, permission, entities);
return this;
}
@Override
public void check() throws PermissionDeniedException {
if (!hasPermission()) {
throw new PermissionDeniedException();
}
}
@Override
public final boolean hasPermission() {
permissionChecked = true;
return doHasPermission();
}
@Override
public PermissionCheck member(final MemberPermission... permissions) {
memberPermissions = (MemberPermission[]) (memberPermissions == null ? permissions : ArrayUtils.addAll(memberPermissions, permissions));
return this;
}
@Override
public PermissionCheck memberFor(final MemberPermission permission, final Entity... entities) {
checkMultivaluedPermission(permission, null);
member(new MemberPermission[] { permission });
addRequiredValues(permission, permission, entities);
return this;
}
@Override
public PermissionCheck operator() {
if (operatorPermissions == null) {
// Just ensure there is an array set in operatorPermissions
operatorPermissions = new OperatorPermission[0];
}
return this;
}
@Override
public PermissionCheck operator(final MemberPermission... permissions) {
operatorMemberPermissions = (MemberPermission[]) (operatorMemberPermissions == null ? permissions : ArrayUtils.addAll(operatorMemberPermissions, permissions));
return this;
}
@Override
public PermissionCheck operator(final OperatorPermission... permissions) {
operatorPermissions = (OperatorPermission[]) (operatorPermissions == null ? permissions : ArrayUtils.addAll(operatorPermissions, permissions));
return this;
}
@Override
public PermissionCheck operatorFor(final MemberPermission permission, final Entity... entities) {
checkMultivaluedPermission(permission, null);
operator(new MemberPermission[] { permission });
addRequiredValues(permission, permission, entities);
return this;
}
@Override
public PermissionCheck operatorFor(final OperatorPermission permission, final Entity... entities) {
checkMultivaluedPermission(permission, null);
operator(new OperatorPermission[] { permission });
addRequiredValues(permission, permission, entities);
return this;
}
@Override
public PermissionCheck operatorFor(final OperatorPermission permission, final MemberPermission parentPermission, final Entity... entities) {
checkMultivaluedPermission(permission, parentPermission);
operator(new OperatorPermission[] { permission });
addRequiredValues(permission, parentPermission, entities);
return this;
}
protected abstract boolean doHasPermission();
@Override
protected void finalize() throws Throwable {
if (!permissionChecked) {
if (stackTrace == null) {
LOG.warn("PermissionCheck object created without actually checking permission. Did you forget a call to check() or hasPermission()? Set the -Dcyclos.tracePermissionChecks=true system argument to view where this permission object was created");
} else {
Exception ex = new Exception();
ex.setStackTrace(stackTrace);
LOG.warn("PermissionCheck object created without actually checking permission. Did you forget a call to check() or hasPermission()?", ex);
}
}
}
protected List<Permission> getPermissions(final Group.Nature groupNature, final ModuleType onlyOfType) {
// Get the raw list of permissions
List<Permission> permissions = null;
if (groupNature != null) {
switch (groupNature) {
case ADMIN:
permissions = join(basicPermissions, adminPermissions);
break;
case BROKER:
if (onlyOfType == ModuleType.MEMBER) {
permissions = join(basicPermissions, memberPermissions);
} else {
permissions = join(basicPermissions, memberPermissions, brokerPermissions);
}
break;
case MEMBER:
permissions = join(basicPermissions, memberPermissions);
break;
case OPERATOR:
permissions = join(basicPermissions, operatorPermissions, operatorMemberPermissions);
break;
}
}
boolean initiallyEmpty = permissions != null && permissions.isEmpty();
// Apply the filter
if (permissions != null && onlyOfType != null) {
for (Iterator<Permission> iterator = permissions.iterator(); iterator.hasNext();) {
Permission permission = iterator.next();
if (permission.getModule().getType() != onlyOfType) {
iterator.remove();
}
}
// None of the permissions matched the expected type. Return null, which will make the permission check fail, as initially,
// permissions were not empty (which happens when we just want someone to have a role, not an specific permission)
if (permissions.isEmpty() && !initiallyEmpty) {
permissions = null;
}
}
return permissions;
}
private void addRequiredValues(final Permission key, final Permission multivaluedPermission, final Entity... entities) {
if (requiredValuesMap == null) {
requiredValuesMap = new HashMap<Permission, RequiredValuesBean>();
} else if (requiredValuesMap.containsKey(key)) {
throw new IllegalArgumentException(String.format("Permission (%1$s) already added to the allowed values map", key));
}
requiredValuesMap.put(key, new RequiredValuesBean(multivaluedPermission, entities));
}
/**
* Checks the multivalued permission<br>
* @param permission the multivalued permission or a boolean operator permission with a multivalued parent permission.
* @param parentPermission not null only if permission is an operator permission.
* @throws IllegalArgumentException if the specified permission doesn't allow get the related relationship from which retrieve the allowed values
* or if it was already added.
*/
private void checkMultivaluedPermission(final Permission permission, final MemberPermission parentPermission) {
// we allow operator permissions without a relationship only if its parent has
if (permission.relationship() == null && (!(permission instanceof OperatorPermission) || parentPermission == null || parentPermission.relationship() == null)) {
throw new IllegalArgumentException(String.format("Invalid permission: %1$s.%2$s. The permission (or its parent if any) must has a relationship to allow ensuring entity membership", permission.getClass().getSimpleName(), permission));
}
}
private List<Permission> join(final Permission[]... permissions) {
List<Permission> result = new ArrayList<Permission>();
boolean hasNonNull = false;
for (Permission[] current : permissions) {
if (current != null) {
hasNonNull = true;
CollectionUtils.addAll(result, current);
}
}
if (!hasNonNull) {
return null;
}
return result;
}
}