package org.jboss.seam.security.permission;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.jboss.solder.logging.Logger;
import org.jboss.seam.security.annotations.permission.PermissionProperty;
import org.jboss.seam.security.annotations.permission.PermissionPropertyType;
import org.jboss.seam.security.permission.PermissionMetadata.ActionSet;
import org.jboss.solder.properties.Property;
import org.jboss.solder.properties.query.PropertyCriteria;
import org.jboss.solder.properties.query.PropertyQueries;
import org.picketlink.idm.spi.model.IdentityObject;
/**
* A permission store implementation that uses JPA as its persistence mechanism.
*
* @author Shane Bryzak
*/
@ApplicationScoped
public class JpaPermissionStore implements PermissionStore, Serializable {
private static final long serialVersionUID = 4764590939669047915L;
private static final Logger log = Logger.getLogger(JpaPermissionStore.class);
private class PropertyTypeCriteria implements PropertyCriteria {
private PermissionPropertyType pt;
public PropertyTypeCriteria(PermissionPropertyType pt) {
this.pt = pt;
}
public boolean fieldMatches(Field f) {
return f.isAnnotationPresent(PermissionProperty.class) &&
f.getAnnotation(PermissionProperty.class).value().equals(pt);
}
public boolean methodMatches(Method m) {
return m.isAnnotationPresent(PermissionProperty.class) &&
m.getAnnotation(PermissionProperty.class).value().equals(pt);
}
}
private boolean enabled;
private Class<?> identityPermissionClass;
private Property<Object> identityProperty;
private Property<?> relationshipTypeProperty;
private Property<String> relationshipNameProperty;
private Property<String> resourceProperty;
private Property<Object> permissionProperty;
private Map<Integer, String> queryCache = new HashMap<Integer, String>();
private PermissionMetadata metadata;
@Inject
IdentifierPolicy identifierPolicy;
@Inject
BeanManager manager;
@Inject
Instance<EntityManager> entityManagerInstance;
@Inject
public void init() {
metadata = new PermissionMetadata();
// TODO see if we can scan for this automatically
if (identityPermissionClass == null) {
log.debug("No identityPermissionClass set, JpaPermissionStore will be unavailable.");
enabled = false;
return;
}
initProperties();
}
protected void initProperties() {
identityProperty = PropertyQueries.createQuery(identityPermissionClass)
.addCriteria(new PropertyTypeCriteria(PermissionPropertyType.IDENTITY))
.getFirstResult();
if (identityProperty == null) {
throw new RuntimeException("Invalid identityPermissionClass " +
identityPermissionClass.getName() +
" - required annotation @PermissionProperty(IDENTITY) not found on any field or method.");
}
relationshipTypeProperty = PropertyQueries.createQuery(identityPermissionClass)
.addCriteria(new PropertyTypeCriteria(PermissionPropertyType.RELATIONSHIP_TYPE))
.getFirstResult();
if (relationshipTypeProperty == null) {
throw new RuntimeException("Invalid identityPermissionClass " +
identityPermissionClass.getName() +
" - required annotation @PermissionProperty(RELATIONSHIP_TYPE) not found on any field or method.");
}
relationshipNameProperty = PropertyQueries.<String>createQuery(identityPermissionClass)
.addCriteria(new PropertyTypeCriteria(PermissionPropertyType.RELATIONSHIP_NAME))
.getFirstResult();
if (relationshipNameProperty == null) {
throw new RuntimeException("Invalid identityPermissionClass " +
identityPermissionClass.getName() +
" - required annotation @PermissionProperty(RELATIONSHIP_NAME) not found on any field or method.");
}
resourceProperty = PropertyQueries.<String>createQuery(identityPermissionClass)
.addCriteria(new PropertyTypeCriteria(PermissionPropertyType.RESOURCE))
.getFirstResult();
if (resourceProperty == null) {
throw new RuntimeException("Invalid identityPermissionClass " +
identityPermissionClass.getName() +
" - required annotation @PermissionProperty(RESOURCE) not found on any field or method.");
}
permissionProperty = PropertyQueries.createQuery(identityPermissionClass)
.addCriteria(new PropertyTypeCriteria(PermissionPropertyType.PERMISSION))
.getFirstResult();
if (permissionProperty == null) {
throw new RuntimeException("Invalid identityPermissionClass " +
identityPermissionClass.getName() +
" - required annotation @PermissionProperty(PERMISSION) not found on any field or method.");
}
enabled = true;
}
/**
* Creates a Query that returns a list of permission records for the specified parameters.
*
* @param target The target of the permission, may be null
* @param targets A set of permission targets, may be null
* @param recipient The permission recipient, may be null
* @param discrimination A discrimination (either user, role or both), required
* @return Query The query generated for the provided parameters
*/
protected Query createPermissionQuery(Object target, Set<?> targets,
IdentityObject identity) {
if (target != null && targets != null) {
throw new IllegalArgumentException("Cannot specify both target and targets");
}
int queryKey = (target != null) ? 1 : 0;
queryKey |= (targets != null) ? 2 : 0;
queryKey |= (identity != null) ? 4 : 0;
if (!queryCache.containsKey(queryKey)) {
boolean conditionsAdded = false;
StringBuilder q = new StringBuilder();
q.append("select p from ");
q.append(identityPermissionClass.getName());
q.append(" p");
if (target != null) {
q.append(" where p.");
q.append(resourceProperty.getName());
q.append(" = :target");
conditionsAdded = true;
}
if (targets != null) {
q.append(" where p.");
q.append(resourceProperty.getName());
q.append(" in (:targets)");
conditionsAdded = true;
}
if (identity != null) {
q.append(conditionsAdded ? " and p." : " where p.");
q.append(identityProperty.getName());
q.append(" = :identity");
conditionsAdded = true;
}
queryCache.put(queryKey, q.toString());
}
Query query = lookupEntityManager().createQuery(queryCache.get(queryKey));
if (target != null) query.setParameter("target", identifierPolicy.getIdentifier(target));
if (targets != null) {
Set<String> identifiers = new HashSet<String>();
for (Object t : targets) {
identifiers.add(identifierPolicy.getIdentifier(t));
}
query.setParameter("targets", identifiers);
}
if (identity != null) query.setParameter("identity", resolveIdentityEntity(identity));
return query;
}
public boolean grantPermission(Permission permission) {
return updatePermissionActions(permission.getResource(), permission.getIdentity(),
new String[]{permission.getPermission()}, true);
}
public boolean revokePermission(Permission permission) {
return updatePermissionActions(permission.getResource(), permission.getIdentity(),
new String[]{permission.getPermission()}, false);
}
/**
* This is where the bulk of the actual work happens.
*
* @param target The target object to update permissions for
* @param recipient The recipient to update permissions for
* @param actions The actions that will be updated
* @param set true if the specified actions are to be granted, false if they are to be revoked
* @return true if the operation is successful
*/
protected boolean updatePermissionActions(Object resource, IdentityObject identity, String[] actions,
boolean set) {
try {
List<?> permissions = createPermissionQuery(resource, null, identity).getResultList();
if (permissions.isEmpty()) {
if (!set) return true;
ActionSet actionSet = metadata.createActionSet(resource.getClass(), null);
for (String action : actions) {
actionSet.add(action);
}
Object instance = identityPermissionClass.newInstance();
resourceProperty.setValue(instance, identifierPolicy.getIdentifier(resource));
permissionProperty.setValue(instance, actionSet.toString());
identityProperty.setValue(instance, resolveIdentityEntity(identity));
lookupEntityManager().persist(instance);
return true;
}
Object instance = permissions.get(0);
ActionSet actionSet = metadata.createActionSet(resource.getClass(),
permissionProperty.getValue(instance).toString());
for (String action : actions) {
if (set) {
actionSet.add(action);
} else {
actionSet.remove(action);
}
}
if (permissions.size() > 1) {
// Same as with roles, consolidate the records if there is more than one
for (Object p : permissions) {
actionSet.addMembers(permissionProperty.getValue(p).toString());
if (!p.equals(instance)) {
lookupEntityManager().remove(p);
}
}
}
if (!actionSet.isEmpty()) {
permissionProperty.setValue(instance, actionSet.toString());
lookupEntityManager().merge(instance);
} else {
// No actions remaining in set, so just remove the record
lookupEntityManager().remove(instance);
}
return true;
} catch (Exception ex) {
throw new RuntimeException("Could not grant permission", ex);
}
}
public boolean grantPermissions(List<Permission> permissions) {
// Target/Recipient/Action map
Map<Object, Map<IdentityObject, List<Permission>>> groupedPermissions = groupPermissions(permissions);
for (Object resource : groupedPermissions.keySet()) {
Map<IdentityObject, List<Permission>> recipientPermissions = groupedPermissions.get(resource);
for (IdentityObject recipient : recipientPermissions.keySet()) {
List<Permission> ps = recipientPermissions.get(recipient);
String[] actions = new String[ps.size()];
for (int i = 0; i < ps.size(); i++) actions[i] = ps.get(i).getPermission();
updatePermissionActions(resource, recipient, actions, true);
}
}
return true;
}
public boolean revokePermissions(List<Permission> permissions) {
// Target/Recipient/Action map
Map<Object, Map<IdentityObject, List<Permission>>> groupedPermissions = groupPermissions(permissions);
for (Object target : groupedPermissions.keySet()) {
Map<IdentityObject, List<Permission>> recipientPermissions = groupedPermissions.get(target);
for (IdentityObject identity : recipientPermissions.keySet()) {
List<Permission> ps = recipientPermissions.get(identity);
String[] actions = new String[ps.size()];
for (int i = 0; i < ps.size(); i++) actions[i] = ps.get(i).getPermission();
updatePermissionActions(target, identity, actions, false);
}
}
return true;
}
/**
* Groups a list of arbitrary permissions into a more easily-consumed structure
*
* @param permissions The list of permissions to group
* @return
*/
private Map<Object, Map<IdentityObject, List<Permission>>> groupPermissions(List<Permission> permissions) {
// Target/Recipient/Action map
Map<Object, Map<IdentityObject, List<Permission>>> groupedPermissions = new HashMap<Object, Map<IdentityObject, List<Permission>>>();
for (Permission permission : permissions) {
if (!groupedPermissions.containsKey(permission.getResource())) {
groupedPermissions.put(permission.getResource(), new HashMap<IdentityObject, List<Permission>>());
}
Map<IdentityObject, List<Permission>> recipientPermissions = groupedPermissions.get(permission.getResource());
if (!recipientPermissions.containsKey(permission.getIdentity())) {
List<Permission> perms = new ArrayList<Permission>();
perms.add(permission);
recipientPermissions.put(permission.getIdentity(), perms);
} else {
recipientPermissions.get(permission.getIdentity()).add(permission);
}
}
return groupedPermissions;
}
/**
* @param recipient
* @return The entity or name representing the permission recipient
*/
protected Object resolveIdentityEntity(IdentityObject identity) {
// TODO implement this method (we already know the identity's entity class)
return identity.getName();
}
/**
* Returns a list of all user and role permissions for the specified action for all specified target objects
*/
public List<Permission> listPermissions(Set<Object> targets, String action) {
// TODO limit the number of targets passed at a single time to 25
return listPermissions(null, targets, action);
}
/**
* Returns a list of all user and role permissions for a specific permission target and action.
*/
public List<Permission> listPermissions(Object target, String action) {
return listPermissions(target, null, action);
}
protected List<Permission> listPermissions(Object resource, Set<Object> targets, String action) {
if (identityPermissionClass == null) return null;
if (resource != null && targets != null) {
throw new IllegalArgumentException("Cannot specify both target and targets");
}
List<Permission> permissions = new ArrayList<Permission>();
if (targets != null && targets.isEmpty()) return permissions;
// First query for user permissions
Query permissionQuery = targets != null ?
createPermissionQuery(null, targets, null) :
createPermissionQuery(resource, null, null);
List<?> userPermissions = permissionQuery.getResultList();
Map<String, Object> identifierCache = null;
if (targets != null) {
identifierCache = new HashMap<String, Object>();
for (Object t : targets) {
identifierCache.put(identifierPolicy.getIdentifier(t), t);
}
}
for (Object permission : userPermissions) {
ActionSet actionSet = null;
if (targets != null) {
//target = identifierCache.get(targetProperty.getValue(permission));
if (resource != null) {
//actionSet = metadata.createActionSet(target.getClass(),
// actionProperty.getValue(permission).toString());
}
} else {
//actionSet = metadata.createActionSet(target.getClass(),
// actionProperty.getValue(permission).toString());
}
if (resource != null && (action == null || (actionSet != null && actionSet.contains(action)))) {
// FIXME
IdentityObject identity = null; //lookupPrincipal(principalCache, permission);
if (action != null) {
permissions.add(new Permission(resource, action, identity));
} else {
for (String a : actionSet.members()) {
permissions.add(new Permission(resource, a, identity));
}
}
}
}
return permissions;
}
public List<Permission> listPermissions(Object target) {
return listPermissions(target, null);
}
public List<String> listAvailableActions(Object target) {
return metadata.listAllowableActions(target.getClass());
}
private EntityManager lookupEntityManager() {
return entityManagerInstance.get();
}
public Class<?> getIdentityPermissionClass() {
return identityPermissionClass;
}
public void setIdentityPermissionClass(Class<?> identityPermissionClass) {
this.identityPermissionClass = identityPermissionClass;
}
public void clearPermissions(Object resource) {
EntityManager em = lookupEntityManager();
String identifier = identifierPolicy.getIdentifier(resource);
em.createQuery(
"delete from " + identityPermissionClass.getName() + " p where p." +
resourceProperty.getName() + " = :resource")
.setParameter("resource", identifier)
.executeUpdate();
}
public boolean isEnabled() {
return enabled;
}
}