package org.jboss.seam.security; import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.enterprise.context.SessionScoped; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.event.Observes; import javax.enterprise.inject.Any; import javax.enterprise.inject.Instance; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.inject.Inject; import javax.inject.Named; import javax.servlet.http.HttpSession; import org.jboss.seam.security.Authenticator.AuthenticationStatus; import org.jboss.seam.security.events.AlreadyLoggedInEvent; import org.jboss.seam.security.events.DeferredAuthenticationEvent; import org.jboss.seam.security.events.LoggedInEvent; import org.jboss.seam.security.events.LoginFailedEvent; import org.jboss.seam.security.events.NotAuthorizedEvent; import org.jboss.seam.security.events.NotLoggedInEvent; import org.jboss.seam.security.events.PostAuthenticateEvent; import org.jboss.seam.security.events.PostLoggedOutEvent; import org.jboss.seam.security.events.PreAuthenticateEvent; import org.jboss.seam.security.events.PreLoggedOutEvent; import org.jboss.seam.security.events.QuietLoginEvent; import org.jboss.seam.security.jaas.JaasAuthenticator; import org.jboss.seam.security.management.IdmAuthenticator; import org.jboss.seam.security.permission.PermissionMapper; import org.jboss.seam.security.util.Strings; import org.jboss.solder.beanManager.BeanManagerLocator; import org.jboss.solder.literal.NamedLiteral; import org.jboss.solder.logging.Logger; import org.picketlink.idm.api.Group; import org.picketlink.idm.api.Role; import org.picketlink.idm.api.User; import org.picketlink.idm.impl.api.model.SimpleGroup; import org.picketlink.idm.impl.api.model.SimpleRole; import org.picketlink.idm.impl.api.model.SimpleRoleType; /** * Identity implementation for authentication and authorization * * @author Shane Bryzak */ public @Named("identity") @SessionScoped class IdentityImpl implements Identity, Serializable { private static final long serialVersionUID = 3751659008033189259L; protected static boolean securityEnabled = true; private static final Logger log = Logger.getLogger(IdentityImpl.class); @Inject BeanManager beanManager; @Inject private Credentials credentials; @Inject private PermissionMapper permissionMapper; @Inject Instance<RequestSecurityState> requestSecurityState; @Inject @Any Instance<Authenticator> authenticators; @Inject HttpSession session; private Authenticator activeAuthenticator; private User user; private Class<? extends Authenticator> authenticatorClass; private String authenticatorName; /** * Contains a group name to group type:role list mapping of roles assigned * during the authentication process */ private Map<String, Map<String, List<String>>> preAuthenticationRoles = new HashMap<String, Map<String, List<String>>>(); private Set<Role> activeRoles = new HashSet<Role>(); /** * Map of group name:group type group memberships assigned during the * authentication process */ private Map<String, List<String>> preAuthenticationGroups = new HashMap<String, List<String>>(); private Set<Group> activeGroups = new HashSet<Group>(); private transient ThreadLocal<Boolean> systemOp; /** * Flag that indicates we are in the process of authenticating */ private boolean authenticating = false; public static boolean isSecurityEnabled() { return securityEnabled; } public static void setSecurityEnabled(boolean enabled) { securityEnabled = enabled; } public boolean isLoggedIn() { // If there is a user set, then the user is logged in. return user != null; } public Class<? extends Authenticator> getAuthenticatorClass() { return authenticatorClass; } public void setAuthenticatorClass(Class<? extends Authenticator> authenticatorClass) { this.authenticatorClass = authenticatorClass; } public String getAuthenticatorName() { return authenticatorName; } public void setAuthenticatorName(String authenticatorName) { this.authenticatorName = authenticatorName; } public boolean tryLogin() { if (!authenticating && getUser() == null && credentials.isSet() && !requestSecurityState.get().isLoginTried()) { requestSecurityState.get().setLoginTried(true); quietLogin(); } return isLoggedIn(); } public String login() { try { if (isLoggedIn()) { // If authentication has already occurred during this request via a silent login, // and login() is explicitly called then we still want to raise the LOGIN_SUCCESSFUL event, // and then return. if (requestSecurityState.get().isSilentLogin()) { beanManager.fireEvent(new LoggedInEvent(user)); return RESPONSE_LOGIN_SUCCESS; } beanManager.fireEvent(new AlreadyLoggedInEvent()); return RESPONSE_LOGIN_SUCCESS; } boolean success = authenticate(); if (success) { if (log.isDebugEnabled()) { log.debug("Login successful"); } beanManager.fireEvent(new LoggedInEvent(user)); return RESPONSE_LOGIN_SUCCESS; } beanManager.fireEvent(new LoginFailedEvent(null)); return RESPONSE_LOGIN_FAILED; } catch (Exception ex) { log.error("Login failed", ex); beanManager.fireEvent(new LoginFailedEvent(ex)); return RESPONSE_LOGIN_EXCEPTION; } } public void quietLogin() { try { beanManager.fireEvent(new QuietLoginEvent()); // Ensure that we haven't been authenticated as a result of the EVENT_QUIET_LOGIN event if (!isLoggedIn()) { if (credentials.isSet()) { authenticate(); if (isLoggedIn()) { requestSecurityState.get().setSilentLogin(true); } } } } catch (Exception ex) { log.error("Error authenticating", ex); credentials.invalidate(); } } protected boolean authenticate() throws AuthenticationException { if (authenticating) { authenticating = false; throw new IllegalStateException("Authentication already in progress."); } try { authenticating = true; user = null; preAuthenticate(); activeAuthenticator = lookupAuthenticator(); if (activeAuthenticator == null) { authenticating = false; throw new AuthenticationException("An Authenticator could not be located"); } activeAuthenticator.authenticate(); if (activeAuthenticator.getStatus() == null) { throw new AuthenticationException("Authenticator must return a valid authentication status"); } switch (activeAuthenticator.getStatus()) { case SUCCESS: postAuthenticate(); return true; case FAILURE: authenticating = false; return false; } return false; } catch (Exception ex) { authenticating = false; if (ex instanceof AuthenticationException) { throw (AuthenticationException) ex; } else { throw new AuthenticationException("Authentication failed.", ex); } } } /** * Clears any roles added by calling addRole() while not authenticated. * This method may be overridden by a subclass if different * pre-authentication logic should occur. */ protected void preAuthenticate() { preAuthenticationRoles.clear(); beanManager.fireEvent(new PreAuthenticateEvent()); } protected void deferredAuthenticationObserver(@Observes DeferredAuthenticationEvent event) { if (event.isSuccess()) { postAuthenticate(); } else { authenticating = false; activeAuthenticator = null; } } protected void postAuthenticate() { if (activeAuthenticator == null) { throw new IllegalStateException("activeAuthenticator is null"); } try { activeAuthenticator.postAuthenticate(); if (!activeAuthenticator.getStatus().equals(AuthenticationStatus.SUCCESS)) return; user = activeAuthenticator.getUser(); if (user == null) { throw new AuthenticationException("Authenticator must provide a non-null User after successful authentication"); } if (isLoggedIn()) { if (!preAuthenticationRoles.isEmpty()) { for (String group : preAuthenticationRoles.keySet()) { Map<String, List<String>> groupTypeRoles = preAuthenticationRoles.get(group); for (String groupType : groupTypeRoles.keySet()) { for (String roleType : groupTypeRoles.get(groupType)) { addRole(roleType, group, groupType); } } } preAuthenticationRoles.clear(); } if (!preAuthenticationGroups.isEmpty()) { for (String group : preAuthenticationGroups.keySet()) { for (String groupType : preAuthenticationGroups.get(group)) { activeGroups.add(new SimpleGroup(group, groupType)); } } preAuthenticationGroups.clear(); } } beanManager.fireEvent(new PostAuthenticateEvent()); } finally { // Set credential to null whether authentication is successful or not activeAuthenticator = null; credentials.setCredential(null); authenticating = false; } } /** * Returns an Authenticator instance to be used for authentication. The default * implementation obeys the following business logic: * <p/> * 1. If the user has specified an authenticatorClass property, use it to * locate the Authenticator with that exact type * 2. If the user has specified an authenticatorName property, use it to * locate and return the Authenticator with that name * 3. If the authenticatorClass and authenticatorName haven't been specified, * and the user has provided their own custom Authenticator, return that one * 4. If the user hasn't provided a custom Authenticator, return IdmAuthenticator * and attempt to use the identity management API to authenticate * * @return */ protected Authenticator lookupAuthenticator() throws AuthenticationException { if (authenticatorClass != null) { return authenticators.select(authenticatorClass).get(); } if (!Strings.isEmpty(authenticatorName)) { Instance<Authenticator> selected = authenticators.select(new NamedLiteral(authenticatorName)); if (selected.isAmbiguous()) { log.error("Multiple Authenticators found with configured name [" + authenticatorName + "]"); return null; } if (selected.isUnsatisfied()) { log.error("No authenticator with name [" + authenticatorName + "] was found"); return null; } return selected.get(); } Authenticator selectedAuth = null; // Hack to workaround glassfish visibility issue BeanManager bm = new BeanManagerLocator().getBeanManager(); // for (Authenticator auth : authenticators) for (Authenticator auth : getReferences(bm, Authenticator.class)) { // If the user has provided their own custom authenticator then use it - // a custom authenticator is one that isn't one of the known authenticators; // JaasAuthenticator, IdmAuthenticator, or any external authenticator, etc if (!JaasAuthenticator.class.isAssignableFrom(auth.getClass()) && !IdmAuthenticator.class.isAssignableFrom(auth.getClass()) && !isExternalAuthenticator(auth.getClass())) { selectedAuth = auth; break; } if (IdmAuthenticator.class.isAssignableFrom(auth.getClass())) { selectedAuth = auth; } } return selectedAuth; } private boolean isExternalAuthenticator(Class<? extends Authenticator> authClass) { Class<?> cls = authClass; while (cls != Object.class) { if (cls.getName().startsWith("org.jboss.seam.security.external.")) { return true; } cls = cls.getSuperclass(); } return false; } @SuppressWarnings("unchecked") private <T> Set<T> getReferences(final BeanManager manager, final Class<T> type, Annotation... qualifiers) { Set<Bean<?>> resolverBeans = manager.getBeans(type, qualifiers); if (resolverBeans.size() == 0) { return Collections.emptySet(); } Set<T> refs = new LinkedHashSet<T>(); for (Bean<?> bean : resolverBeans) { // FIXME when should the dependent context be cleaned up? CreationalContext<T> context = (CreationalContext<T>) manager.createCreationalContext(bean); if (context != null) { refs.add((T) manager.getReference(bean, type, context)); } } return refs; } /** * Resets all security state and credentials */ public void unAuthenticate() { user = null; credentials.clear(); preAuthenticationRoles.clear(); activeRoles.clear(); preAuthenticationGroups.clear(); activeGroups.clear(); } public void logout() { if (isLoggedIn()) { PostLoggedOutEvent loggedOutEvent = new PostLoggedOutEvent(user); beanManager.fireEvent(new PreLoggedOutEvent()); unAuthenticate(); session.invalidate(); beanManager.fireEvent(loggedOutEvent); } } public boolean hasRole(String roleType, String group, String groupType) { if (!securityEnabled) return true; if (systemOp != null && Boolean.TRUE.equals(systemOp.get())) return true; tryLogin(); for (Role role : activeRoles) { if (role.getRoleType().getName().equals(roleType) && role.getGroup().getName().equals(group) && role.getGroup().getGroupType().equals(groupType)) { return true; } } return false; } public boolean addRole(String roleType, String group, String groupType) { if (roleType == null || "".equals(roleType) || group == null || "".equals(group) || groupType == null || "".equals(groupType)) return false; if (isLoggedIn()) { return activeRoles.add(new SimpleRole(new SimpleRoleType(roleType), user, new SimpleGroup(group, groupType))); } else { List<String> roleTypes = null; Map<String, List<String>> groupTypes = preAuthenticationRoles.get(group); if (groupTypes != null) { roleTypes = groupTypes.get(groupType); } else { groupTypes = new HashMap<String, List<String>>(); preAuthenticationRoles.put(group, groupTypes); } if (roleTypes == null) { roleTypes = new ArrayList<String>(); groupTypes.put(groupType, roleTypes); } return roleTypes.add(roleType); } } public boolean inGroup(String name, String groupType) { for (Group group : activeGroups) { if (group.getName().equals(name) && group.getGroupType().equals(groupType)) return true; } return false; } public boolean addGroup(String name, String groupType) { if (name == null || "".equals(name) || groupType == null || "".equals(groupType)) { return false; } if (isLoggedIn()) { return activeGroups.add(new SimpleGroup(name, groupType)); } else { List<String> groupTypes = null; if (preAuthenticationGroups.containsKey(name)) { groupTypes = preAuthenticationGroups.get(name); } else { groupTypes = new ArrayList<String>(); preAuthenticationGroups.put(name, groupTypes); } return groupTypes.add(groupType); } } public void removeGroup(String name, String groupType) { for (Group group : activeGroups) { if (group.getName().equals(name) && group.getGroupType().equals(groupType)) { activeGroups.remove(group); return; } } } /** * Removes a role from the authenticated user * * @param role The name of the role to remove */ public void removeRole(String roleType, String group, String groupType) { for (Role role : activeRoles) { if (role.getRoleType().getName().equals(roleType) && role.getGroup().getName().equals(group) && role.getGroup().getGroupType().equals(groupType)) { activeRoles.remove(role); return; } } } public void checkRole(String roleType, String group, String groupType) { tryLogin(); if (!hasRole(roleType, group, groupType)) { if (!isLoggedIn()) { beanManager.fireEvent(new NotLoggedInEvent()); throw new NotLoggedInException(); } else { beanManager.fireEvent(new NotAuthorizedEvent()); throw new AuthorizationException(String.format( "Authorization check failed for role [%s:%s:%s]", roleType, group, groupType)); } } } public void checkGroup(String group, String groupType) { tryLogin(); if (!inGroup(group, groupType)) { if (!isLoggedIn()) { beanManager.fireEvent(new NotLoggedInEvent()); throw new NotLoggedInException(); } else { beanManager.fireEvent(new NotAuthorizedEvent()); throw new AuthorizationException(String.format( "Authorization check failed for group [%s:%s]", group, groupType)); } } } public void checkPermission(Object target, String action) { if (systemOp != null && Boolean.TRUE.equals(systemOp.get())) return; tryLogin(); if (!hasPermission(target, action)) { if (!isLoggedIn()) { beanManager.fireEvent(new NotLoggedInEvent()); throw new NotLoggedInException(); } else { beanManager.fireEvent(new NotAuthorizedEvent()); throw new AuthorizationException(String.format( "Authorization check failed for permission[%s,%s]", target, action)); } } } public void filterByPermission(Collection<?> collection, String action) { permissionMapper.filterByPermission(collection, action); } public boolean hasPermission(Object target, String action) { if (!securityEnabled) return true; if (systemOp != null && Boolean.TRUE.equals(systemOp.get())) return true; if (permissionMapper == null) return false; if (target == null) return false; return permissionMapper.resolvePermission(target, action); } public synchronized void runAs(RunAsOperation operation) { User savedUser = getUser(); if (systemOp == null) { systemOp = new ThreadLocal<Boolean>(); } boolean savedSystemOp = systemOp.get(); try { user = operation.getUser(); systemOp.set(operation.isSystemOperation()); operation.execute(); } finally { systemOp.set(savedSystemOp); user = savedUser; } } public void checkRestriction(String expr) { // TODO Do we still need this method? } public User getUser() { return user; } public Set<Role> getRoles() { return Collections.unmodifiableSet(activeRoles); } public Set<Group> getGroups() { return Collections.unmodifiableSet(activeGroups); } public boolean isVerified() { // TODO Auto-generated method stub return false; } }