package org.jboss.seam.security.permission; import static org.jboss.seam.ScopeType.SESSION; import static org.jboss.seam.annotations.Install.BUILT_IN; import java.io.Serializable; import java.security.Principal; import java.security.acl.Group; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Set; import org.drools.FactHandle; import org.drools.RuleBase; import org.drools.StatefulSession; import org.drools.ClassObjectFilter; import org.jboss.seam.Component; import org.jboss.seam.ScopeType; import org.jboss.seam.Seam; import org.jboss.seam.annotations.Create; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.Startup; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.drools.SeamGlobalResolver; import org.jboss.seam.log.LogProvider; import org.jboss.seam.log.Logging; import org.jboss.seam.security.Identity; import org.jboss.seam.security.Role; import org.jboss.seam.security.management.JpaIdentityStore; /** * A permission resolver that uses a Drools rule base to perform permission checks * * @author Shane Bryzak */ @Name("org.jboss.seam.security.ruleBasedPermissionResolver") @Scope(SESSION) @BypassInterceptors @Install(precedence=BUILT_IN, classDependencies="org.drools.WorkingMemory") @Startup public class RuleBasedPermissionResolver implements PermissionResolver, Serializable { public static final String RULES_COMPONENT_NAME = "securityRules"; private static final LogProvider log = Logging.getLogProvider(RuleBasedPermissionResolver.class); private StatefulSession securityContext; private RuleBase securityRules; @Create public boolean create() { initSecurityContext(); return getSecurityContext() != null; } protected void initSecurityContext() { if (getSecurityRules() == null) { setSecurityRules((RuleBase) Component.getInstance(RULES_COMPONENT_NAME, true)); } if (getSecurityRules() != null) { setSecurityContext(getSecurityRules().newStatefulSession(false)); getSecurityContext().setGlobalResolver(new SeamGlobalResolver(getSecurityContext().getGlobalResolver())); } if (getSecurityContext() == null) { log.debug("no security rule base available - please install a RuleBase with the name '" + RULES_COMPONENT_NAME + "' if permission checks are required."); } } /** * Performs a permission check for the specified name and action * * @param target Object The target of the permission check * @param action String The action to be performed on the target * @return boolean True if the user has the specified permission */ public boolean hasPermission(Object target, String action) { StatefulSession securityContext = getSecurityContext(); if (securityContext == null) return false; List<FactHandle> handles = new ArrayList<FactHandle>(); PermissionCheck check; synchronized( securityContext ) { if (!(target instanceof String) && !(target instanceof Class)) { handles.add( securityContext.insert(target) ); } else if (target instanceof Class) { String componentName = Seam.getComponentName((Class) target); target = componentName != null ? componentName : ((Class) target).getName(); } check = new PermissionCheck(target, action); try { synchronizeContext(); handles.add( securityContext.insert(check) ); securityContext.fireAllRules(); } finally { for (FactHandle handle : handles) { securityContext.retract(handle); } } } return check.isGranted(); } public void filterSetByAction(Set<Object> targets, String action) { Iterator iter = targets.iterator(); while (iter.hasNext()) { Object target = iter.next(); if (hasPermission(target, action)) iter.remove(); } } public boolean checkConditionalRole(String roleName, Object target, String action) { StatefulSession securityContext = getSecurityContext(); if (securityContext == null) return false; RoleCheck roleCheck = new RoleCheck(roleName); List<FactHandle> handles = new ArrayList<FactHandle>(); PermissionCheck check = new PermissionCheck(target, action); synchronized( securityContext ) { if (!(target instanceof String) && !(target instanceof Class)) { handles.add( securityContext.insert(target) ); } else if (target instanceof Class) { String componentName = Seam.getComponentName((Class) target); target = componentName != null ? componentName : ((Class) target).getName(); } try { handles.add( securityContext.insert(check)); // Check if there are any additional requirements securityContext.fireAllRules(); if (check.hasRequirements()) { for (String requirement : check.getRequirements()) { Object value = Contexts.lookupInStatefulContexts(requirement); if (value != null) { handles.add (securityContext.insert(value)); } } } synchronizeContext(); handles.add( securityContext.insert(roleCheck)); handles.add( securityContext.insert(check)); securityContext.fireAllRules(); } finally { for (FactHandle handle : handles) { securityContext.retract(handle); } } } return roleCheck.isGranted(); } @SuppressWarnings("unchecked") @Observer(Identity.EVENT_LOGGED_OUT) public void unAuthenticate() { if (getSecurityContext() != null) { getSecurityContext().dispose(); setSecurityContext(null); } initSecurityContext(); } /** * Synchronises the state of the security context with that of the subject */ private void synchronizeContext() { Identity identity = Identity.instance(); if (getSecurityContext() != null) { getSecurityContext().insert(identity.getPrincipal()); for ( Group sg : identity.getSubject().getPrincipals(Group.class) ) { if ( Identity.ROLES_GROUP.equals( sg.getName() ) ) { Enumeration e = sg.members(); while (e.hasMoreElements()) { Principal role = (Principal) e.nextElement(); boolean found = false; Iterator<Role> iter = (Iterator<Role>) getSecurityContext().iterateObjects(new ClassObjectFilter(Role.class)); while (iter.hasNext()) { Role r = iter.next(); if (r.getName().equals(role.getName())) { found = true; break; } } if (!found) { getSecurityContext().insert(new Role(role.getName())); } } } } Iterator<Role> iter = (Iterator<Role>) getSecurityContext().iterateObjects(new ClassObjectFilter(Role.class)); while (iter.hasNext()) { Role r = iter.next(); if (!identity.hasRole(r.getName())) { FactHandle fh = getSecurityContext().getFactHandle(r); getSecurityContext().retract(fh); } } } } public StatefulSession getSecurityContext() { return securityContext; } public void setSecurityContext(StatefulSession securityContext) { this.securityContext = securityContext; } public RuleBase getSecurityRules() { return securityRules; } public void setSecurityRules(RuleBase securityRules) { this.securityRules = securityRules; } public static RuleBasedPermissionResolver instance() { if ( !Contexts.isSessionContextActive() ) { throw new IllegalStateException("No active session context"); } RuleBasedPermissionResolver instance = (RuleBasedPermissionResolver) Component.getInstance( RuleBasedPermissionResolver.class, ScopeType.SESSION); if (instance == null) { throw new IllegalStateException("No RuleBasedPermissionResolver could be created"); } return instance; } /** * Post-authentication event observer */ @Observer(Identity.EVENT_POST_AUTHENTICATE) public void setUserAccountInSecurityContext() { if (getSecurityContext() != null) { getSecurityContext().insert(Identity.instance().getPrincipal()); // If we were authenticated with the JpaIdentityStore, then insert the authenticated // UserAccount into the security context. if (Contexts.isEventContextActive() && Contexts.isSessionContextActive() && Contexts.getEventContext().isSet(JpaIdentityStore.AUTHENTICATED_USER)) { getSecurityContext().insert(Contexts.getEventContext().get(JpaIdentityStore.AUTHENTICATED_USER)); } } } }