package org.jboss.seam.security.permission; import static org.jboss.seam.ScopeType.APPLICATION; import static org.jboss.seam.annotations.Install.BUILT_IN; import java.io.Serializable; import java.security.Principal; 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.persistence.EntityManager; import javax.persistence.Query; import org.jboss.seam.Component; import org.jboss.seam.annotations.Create; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.annotations.security.permission.PermissionAction; import org.jboss.seam.annotations.security.permission.PermissionDiscriminator; import org.jboss.seam.annotations.security.permission.PermissionRole; import org.jboss.seam.annotations.security.permission.PermissionTarget; import org.jboss.seam.annotations.security.permission.PermissionUser; import org.jboss.seam.core.Expressions; import org.jboss.seam.core.Expressions.ValueExpression; import org.jboss.seam.log.LogProvider; import org.jboss.seam.log.Logging; import org.jboss.seam.security.Role; import org.jboss.seam.security.SimplePrincipal; import org.jboss.seam.security.management.IdentityManager; import org.jboss.seam.security.management.IdentityStore; import org.jboss.seam.security.management.JpaIdentityStore; import org.jboss.seam.security.permission.PermissionMetadata.ActionSet; import org.jboss.seam.util.AnnotatedBeanProperty; /** * A permission store implementation that uses JPA as its persistence mechanism. * * @author Shane Bryzak */ @Name("org.jboss.seam.security.jpaPermissionStore") @Install(precedence = BUILT_IN, value=false) @Scope(APPLICATION) @BypassInterceptors public class JpaPermissionStore implements PermissionStore, Serializable { private static final LogProvider log = Logging.getLogProvider(JpaPermissionStore.class); private enum Discrimination { user, role, either } private ValueExpression<EntityManager> entityManager; private Class userPermissionClass; private Class rolePermissionClass; private AnnotatedBeanProperty<PermissionUser> userProperty; private AnnotatedBeanProperty<PermissionRole> roleProperty; private AnnotatedBeanProperty<PermissionTarget> targetProperty; private AnnotatedBeanProperty<PermissionAction> actionProperty; private AnnotatedBeanProperty<PermissionDiscriminator> discriminatorProperty; private AnnotatedBeanProperty<PermissionTarget> roleTargetProperty; private AnnotatedBeanProperty<PermissionAction> roleActionProperty; private Map<Integer,String> queryCache = new HashMap<Integer,String>(); private IdentifierPolicy identifierPolicy; private PermissionMetadata metadata; @Create public void init() { metadata = new PermissionMetadata(); // TODO see if we can scan for this automatically if (userPermissionClass == null) { log.debug("No permissionClass set, JpaPermissionStore will be unavailable."); return; } if (entityManager == null) { entityManager = Expressions.instance().createValueExpression("#{entityManager}", EntityManager.class); } initProperties(); identifierPolicy = (IdentifierPolicy) Component.getInstance(IdentifierPolicy.class, true); } protected void initProperties() { userProperty = new AnnotatedBeanProperty<PermissionUser>(userPermissionClass, PermissionUser.class); targetProperty = new AnnotatedBeanProperty<PermissionTarget>(userPermissionClass, PermissionTarget.class); actionProperty = new AnnotatedBeanProperty<PermissionAction>(userPermissionClass, PermissionAction.class); if (rolePermissionClass != null) { roleProperty = new AnnotatedBeanProperty<PermissionRole>(rolePermissionClass, PermissionRole.class); if (roleProperty.isSet()) { roleTargetProperty = new AnnotatedBeanProperty<PermissionTarget>(rolePermissionClass, PermissionTarget.class); roleActionProperty = new AnnotatedBeanProperty<PermissionAction>(rolePermissionClass, PermissionAction.class); } } else { roleProperty = new AnnotatedBeanProperty<PermissionRole>(userPermissionClass, PermissionRole.class); if (roleProperty.isSet()) { discriminatorProperty = new AnnotatedBeanProperty<PermissionDiscriminator>(userPermissionClass, PermissionDiscriminator.class); } } if (!userProperty.isSet()) { throw new RuntimeException("Invalid userPermissionClass " + userPermissionClass.getName() + " - required annotation @PermissionUser not found on any Field or Method."); } if (rolePermissionClass != null) { if (!roleProperty.isSet()) { throw new RuntimeException("Invalid rolePermissionClass " + rolePermissionClass.getName() + " - required annotation @PermissionRole not found on any Field or Method."); } if (!roleTargetProperty.isSet()) { throw new RuntimeException("Invalid rolePermissionClass " + rolePermissionClass.getName() + " - required annotation @PermissionTarget not found on any Field or Method."); } if (!roleActionProperty.isSet()) { throw new RuntimeException("Invalid rolePermissionClass " + rolePermissionClass.getName() + " - required annotation @PermissionAction not found on any Field or Method."); } } else if (!discriminatorProperty.isSet()) { throw new RuntimeException("Invalid userPermissionClass " + userPermissionClass.getName() + " - no rolePermissionClass set and @PermissionDiscriminator annotation not found on " + "any Field or Method"); } } /** * 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, Principal recipient, Discrimination discrimination) { 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 |= (recipient != null) ? 4 : 0; queryKey |= (discrimination.equals(Discrimination.user) ? 8 : 0); queryKey |= (discrimination.equals(Discrimination.role) ? 16 : 0); queryKey |= (discrimination.equals(Discrimination.either) ? 32 : 0); boolean isRole = discrimination.equals(Discrimination.role); boolean useRoleTable = isRole && rolePermissionClass != null; if (!queryCache.containsKey(queryKey)) { boolean conditionsAdded = false; StringBuilder q = new StringBuilder(); q.append("select p from "); q.append(useRoleTable ? rolePermissionClass.getName() : userPermissionClass.getName()); q.append(" p"); if (target != null) { q.append(" where p."); q.append(useRoleTable ? roleTargetProperty.getName() : targetProperty.getName()); q.append(" = :target"); conditionsAdded = true; } if (targets != null) { q.append(" where p."); q.append(useRoleTable ? roleTargetProperty.getName() : targetProperty.getName()); q.append(" in (:targets)"); conditionsAdded = true; } if (recipient != null) { q.append(conditionsAdded ? " and p." : " where p."); q.append(isRole ? roleProperty.getName() : userProperty.getName()); q.append(" = :recipient"); conditionsAdded = true; } // If there is no discrimination, then don't add such a condition to the query if (!discrimination.equals(Discrimination.either) && discriminatorProperty != null) { q.append(conditionsAdded ? " and p." : " where p."); q.append(discriminatorProperty.getName()); q.append(" = :discriminator"); 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 (recipient != null) query.setParameter("recipient", resolvePrincipalEntity(recipient)); if (!discrimination.equals(Discrimination.either) && discriminatorProperty != null) { query.setParameter("discriminator", getDiscriminatorValue( discrimination.equals(Discrimination.role))); } return query; } public boolean grantPermission(Permission permission) { return updatePermissionActions(permission.getTarget(), permission.getRecipient(), new String[] {permission.getAction()}, true); } public boolean revokePermission(Permission permission) { return updatePermissionActions(permission.getTarget(), permission.getRecipient(), new String[] { permission.getAction() }, 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 target, Principal recipient, String[] actions, boolean set) { boolean recipientIsRole = recipient instanceof Role; try { if (recipientIsRole) { if (rolePermissionClass != null) { List permissions = createPermissionQuery(target, null, recipient, Discrimination.role).getResultList(); if (permissions.isEmpty()) { if (!set) return true; ActionSet actionSet = metadata.createActionSet(target.getClass(), null); for (String action : actions) { actionSet.add(action); } Object instance = rolePermissionClass.newInstance(); roleTargetProperty.setValue(instance, identifierPolicy.getIdentifier(target)); roleActionProperty.setValue(instance, actionSet.toString()); roleProperty.setValue(instance, resolvePrincipalEntity(recipient)); lookupEntityManager().persist(instance); return true; } Object instance = permissions.get(0); ActionSet actionSet = metadata.createActionSet(target.getClass(), roleActionProperty.getValue(instance).toString()); for (String action : actions) { if (set) { actionSet.add(action); } else { actionSet.remove(action); } } if (permissions.size() > 1) { // This is where it gets a little messy.. if there is more than one permission // record, then we need to consolidate them all into just the first one for (Object p : permissions) { actionSet.addMembers(roleActionProperty.getValue(p).toString()); if (!p.equals(instance)) { lookupEntityManager().remove(p); } } } if (!actionSet.isEmpty()) { roleActionProperty.setValue(instance, actionSet.toString()); lookupEntityManager().merge(instance); } else { // No actions remaining in set, so just remove the record lookupEntityManager().remove(instance); } return true; } if (!discriminatorProperty.isSet()) { throw new RuntimeException("Could not grant permission, rolePermissionClass not set"); } } if (userPermissionClass == null) { throw new RuntimeException("Could not grant permission, userPermissionClass not set"); } List permissions = createPermissionQuery(target, null, recipient, recipientIsRole ? Discrimination.role : Discrimination.user).getResultList(); if (permissions.isEmpty()) { if (!set) return true; ActionSet actionSet = metadata.createActionSet(target.getClass(), null); for (String action : actions) { actionSet.add(action); } Object instance = userPermissionClass.newInstance(); targetProperty.setValue(instance, identifierPolicy.getIdentifier(target)); actionProperty.setValue(instance, actionSet.toString()); if (recipientIsRole) { roleProperty.setValue(instance, resolvePrincipalEntity(recipient)); } else { userProperty.setValue(instance, resolvePrincipalEntity(recipient)); } if (discriminatorProperty.isSet()) { PermissionDiscriminator discriminator = discriminatorProperty.getAnnotation(); discriminatorProperty.setValue(instance, recipientIsRole ? discriminator.roleValue() : discriminator.userValue()); } lookupEntityManager().persist(instance); return true; } Object instance = permissions.get(0); ActionSet actionSet = metadata.createActionSet(target.getClass(), actionProperty.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(actionProperty.getValue(p).toString()); if (!p.equals(instance)) { lookupEntityManager().remove(p); } } } if (!actionSet.isEmpty()) { actionProperty.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<Principal,List<Permission>>> groupedPermissions = groupPermissions(permissions); for (Object target : groupedPermissions.keySet()) { Map<Principal,List<Permission>> recipientPermissions = groupedPermissions.get(target); for (Principal 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).getAction(); updatePermissionActions(target, recipient, actions, true); } } return true; } public boolean revokePermissions(List<Permission> permissions) { // Target/Recipient/Action map Map<Object,Map<Principal,List<Permission>>> groupedPermissions = groupPermissions(permissions); for (Object target : groupedPermissions.keySet()) { Map<Principal,List<Permission>> recipientPermissions = groupedPermissions.get(target); for (Principal 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).getAction(); updatePermissionActions(target, recipient, 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<Principal,List<Permission>>> groupPermissions(List<Permission> permissions) { // Target/Recipient/Action map Map<Object,Map<Principal,List<Permission>>> groupedPermissions = new HashMap<Object,Map<Principal,List<Permission>>>(); for (Permission permission : permissions) { if (!groupedPermissions.containsKey(permission.getTarget())) { groupedPermissions.put(permission.getTarget(), new HashMap<Principal,List<Permission>>()); } Map<Principal,List<Permission>> recipientPermissions = groupedPermissions.get(permission.getTarget()); if (!recipientPermissions.containsKey(permission.getRecipient())) { List<Permission> perms = new ArrayList<Permission>(); perms.add(permission); recipientPermissions.put(permission.getRecipient(), perms); } else { recipientPermissions.get(permission.getRecipient()).add(permission); } } return groupedPermissions; } private String getDiscriminatorValue(boolean isRole) { PermissionDiscriminator discriminator = discriminatorProperty.getAnnotation(); return isRole ? discriminator.roleValue() : discriminator.userValue(); } /** * If the user or role properties in the entity class refer to other entities, then this method * uses the JpaIdentityStore (if available) to lookup that user or role entity. Otherwise it * simply returns the name of the recipient. * * @param recipient * @return The entity or name representing the permission recipient */ protected Object resolvePrincipalEntity(Principal recipient) { boolean recipientIsRole = recipient instanceof Role; JpaIdentityStore identityStore = (JpaIdentityStore) Component.getInstance(JpaIdentityStore.class, true); if (identityStore != null) { if (recipientIsRole && roleProperty.isSet() && roleProperty.getPropertyType().equals(identityStore.getRoleClass())) { return identityStore.lookupRole(recipient.getName()); } else if (userProperty.getPropertyType().equals(identityStore.getUserClass())) { return identityStore.lookupUser(recipient.getName()); } } return recipient.getName(); } protected Principal resolvePrincipal(Object principal, boolean isUser) { IdentityStore ids = IdentityManager.instance().getRoleIdentityStore(); JpaIdentityStore identityStore = null; if (ids instanceof JpaIdentityStore) identityStore = (JpaIdentityStore) ids; if (principal instanceof String) { return isUser ? new SimplePrincipal((String) principal) : new Role((String) principal, identityStore == null ? false : identityStore.isRoleConditional((String) principal)); } if (identityStore != null) { if (isUser && identityStore.getUserClass().isAssignableFrom(principal.getClass())) { return new SimplePrincipal(identityStore.getUserName(principal)); } if (!isUser && identityStore.getRoleClass().isAssignableFrom(principal.getClass())) { String name = identityStore.getRoleName(principal); return new Role(name, identityStore.isRoleConditional(name)); } } throw new IllegalArgumentException("Cannot resolve principal name for principal " + principal); } /** * 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 target, Set<Object> targets, String action) { if (target != 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, Discrimination.either) : createPermissionQuery(target, null, null, Discrimination.either); List userPermissions = permissionQuery.getResultList(); Map<String,Principal> principalCache = new HashMap<String,Principal>(); boolean useDiscriminator = rolePermissionClass == null && discriminatorProperty.isSet(); 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 (target != null) { actionSet = metadata.createActionSet(target.getClass(), actionProperty.getValue(permission).toString()); } } else { actionSet = metadata.createActionSet(target.getClass(), actionProperty.getValue(permission).toString()); } if (target != null && (action == null || (actionSet != null && actionSet.contains(action)))) { boolean isUser = true; if (useDiscriminator && discriminatorProperty.getAnnotation().roleValue().equals( discriminatorProperty.getValue(permission))) { isUser = false; } Principal principal = lookupPrincipal(principalCache, permission, isUser); if (action != null) { permissions.add(new Permission(target, action, principal)); } else { for (String a : actionSet.members()) { permissions.add(new Permission(target, a, principal)); } } } } // If we have a separate class for role permissions, then query them now if (rolePermissionClass != null) { permissionQuery = targets != null ? createPermissionQuery(null, targets, null, Discrimination.role) : createPermissionQuery(target, null, null, Discrimination.role); List rolePermissions = permissionQuery.getResultList(); for (Object permission : rolePermissions) { ActionSet actionSet = null; if (targets != null) { target = identifierCache.get(roleTargetProperty.getValue(permission)); if (target != null) { actionSet = metadata.createActionSet(target.getClass(), roleActionProperty.getValue(permission).toString()); } } else { actionSet = metadata.createActionSet(target.getClass(), roleActionProperty.getValue(permission).toString()); } if (target != null && (action == null || (actionSet != null && actionSet.contains(action)))) { Principal principal = lookupPrincipal(principalCache, permission, false); if (action != null) { permissions.add(new Permission(target, action, principal)); } else { for (String a : actionSet.members()) { permissions.add(new Permission(target, a, principal)); } } } } } return permissions; } private Principal lookupPrincipal(Map<String,Principal> cache, Object permission, boolean isUser) { Principal principal = resolvePrincipal(isUser ? userProperty.getValue(permission) : roleProperty.getValue(permission), isUser); String key = (isUser ? "u:" : "r:") + principal.getName(); if (!cache.containsKey(key)) { cache.put(key, principal); } else { principal = cache.get(key); } return principal; } 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 entityManager.getValue(); } public ValueExpression getEntityManager() { return entityManager; } public void setEntityManager(ValueExpression expression) { this.entityManager = expression; } public Class getUserPermissionClass() { return userPermissionClass; } public void setUserPermissionClass(Class userPermissionClass) { this.userPermissionClass = userPermissionClass; } public Class getRolePermissionClass() { return rolePermissionClass; } public void setRolePermissionClass(Class rolePermissionClass) { this.rolePermissionClass = rolePermissionClass; } public void clearPermissions(Object target) { EntityManager em = lookupEntityManager(); String identifier = identifierPolicy.getIdentifier(target); em.createQuery( "delete from " + userPermissionClass.getName() + " p where p." + targetProperty.getName() + " = :target") .setParameter("target", identifier) .executeUpdate(); if (rolePermissionClass != null) { em.createQuery( "delete from " + rolePermissionClass.getName() + " p where p." + roleTargetProperty.getName() + " = :target") .setParameter("target", identifier) .executeUpdate(); } } }