package org.jboss.seam.security; import java.io.Serializable; import java.rmi.server.UID; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.enterprise.context.SessionScoped; import javax.enterprise.event.Observes; import javax.enterprise.inject.spi.BeanManager; import javax.inject.Inject; import javax.inject.Named; import org.jboss.seam.security.events.QuietLoginEvent; import org.jboss.seam.security.util.Base64; import org.picketlink.idm.api.Role; /** * Remember-me functionality is provided by this class, in two different flavours. The first mode * provides username-only persistence, and is considered to be secure as the user (or their browser) * is still required to provide a password. The second mode provides an auto-login feature, however * is NOT considered to be secure and is vulnerable to XSS attacks compromising the user's account. * <p/> * Use the auto-login mode with caution! * * @author Shane Bryzak */ @Named @SessionScoped public class RememberMe implements Serializable { private static final long serialVersionUID = 2242379431576068199L; public enum Mode {disabled, usernameOnly, autoLogin} @Inject BeanManager manager; @Inject Identity identity; @Inject IdentityImpl identityImpl; @Inject CredentialsImpl credentials; // Heaps of stuff commented out here because we need to add generic cookie support //private ManagedCookie usernameSelector; //private ManagedCookie tokenSelector; private TokenStore tokenStore; private boolean enabled; //private int cookieMaxAge = ManagedCookie.DEFAULT_MAX_AGE; private boolean autoLoggedIn; private Random random = new Random(System.currentTimeMillis()); private Mode mode = Mode.usernameOnly; public RememberMe() { } /* public @Inject void create() { if (mode.equals(Mode.usernameOnly)) { usernameSelector = (ManagedCookie) BeanManagerHelper.getInstanceByType(manager, ManagedCookie.class); usernameSelector.setCookieName("org.jboss.seam.security.username"); usernameSelector.setCookieEnabled(enabled); } else if (mode.equals(Mode.autoLogin)) { tokenSelector = (ManagedCookie) BeanManagerHelper.getInstanceByType(manager, ManagedCookie.class); tokenSelector.setCookieName("org.jboss.seam.security.authtoken"); tokenSelector.setCookieEnabled(enabled); // Default to JpaTokenStore if (tokenStore == null) { tokenStore = BeanManagerHelper.getInstanceByType(manager,JpaTokenStore.class); } } } public void initCredentials(@Observes CredentialsInitializedEvent event) { // FIXME use the context path as the cookie path // String cookiePath = getCookiePath(); String cookiePath = "/"; if (mode.equals(Mode.usernameOnly)) { if (cookiePath != null) { usernameSelector.setCookiePath(cookiePath); } String username = usernameSelector.getCookieValue(); if (username!=null) { setEnabled(true); event.getCredentials().setUsername(username); } } else if (mode.equals(Mode.autoLogin)) { if (cookiePath != null) { tokenSelector.setCookiePath(cookiePath); } String token = tokenSelector.getCookieValue(); if (token != null) { setEnabled(true); DecodedToken decoded = new DecodedToken(token); if (tokenStore.validateToken(decoded.getUsername(), decoded.getValue())) { event.getCredentials().setUsername(decoded.getUsername()); event.getCredentials().setPassword(decoded.getValue()); } else { // Have we been compromised? Just in case, invalidate all authentication tokens tokenStore.invalidateAll(decoded.getUsername()); } } } }*/ public void quietLogin(@Observes QuietLoginEvent event) { if (mode.equals(Mode.autoLogin) && isEnabled()) { final String username = credentials.getUsername(); final BoolWrapper userEnabled = new BoolWrapper(); final List<Role> roles = new ArrayList<Role>(); // Double check our credentials again if (tokenStore.validateToken(username, credentials.getPassword())) { identityImpl.runAs(new RunAsOperation(true) { @Override public void execute() { /*if (identityManager.isUserEnabled(username)) { userEnabled.value = true; for (Role role : identityManager.getUserRoles(username)) { roles.add(role); } }*/ } }); if (userEnabled.value) { identityImpl.unAuthenticate(); identityImpl.preAuthenticate(); // populate the roles for (Role role : roles) { identity.addRole(role.getRoleType().getName(), role.getGroup().getName(), role.getGroup().getGroupType()); } // Set the principal // identity.getSubject().getPrincipals().add(new SimplePrincipal(username)); identityImpl.postAuthenticate(); autoLoggedIn = true; } } } } /* public void postAuthenticate(@Observes PostAuthenticateEvent event) { if (mode.equals(Mode.usernameOnly)) { if ( !enabled ) { usernameSelector.clearCookieValue(); } else { usernameSelector.setCookieMaxAge(cookieMaxAge); usernameSelector.setCookieValueIfEnabled( credentials.getUsername() ); } } else if (mode.equals(Mode.autoLogin)) { DecodedToken decoded = new DecodedToken(tokenSelector.getCookieValue()); // Invalidate the current token (if it exists) whether enabled or not if (decoded.getUsername() != null) { tokenStore.invalidateToken(decoded.getUsername(), decoded.getValue()); } if ( !enabled ) { tokenSelector.clearCookieValue(); } else { String value = generateTokenValue(); tokenStore.createToken(identity.getPrincipal().getName(), value); tokenSelector.setCookieEnabled(enabled); tokenSelector.setCookieMaxAge(cookieMaxAge); tokenSelector.setCookieValueIfEnabled(encodeToken(identity.getPrincipal().getName(), value)); } } } */ /* public void loggedOut(@Observes LoggedOutEvent event) { if (mode.equals(Mode.autoLogin)) { tokenSelector.clearCookieValue(); } }*/ public Mode getMode() { return mode; } public void setMode(Mode mode) { this.mode = mode; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } /* public void setEnabled(boolean enabled) { if (this.enabled != enabled) { this.enabled = enabled; // selector is null during component initialization (setup handled in @Create method) if (usernameSelector != null && mode.equals(Mode.usernameOnly)) { usernameSelector.setCookieEnabled(enabled); } // selector is null during component initialization (setup handled in @Create method) else if (tokenSelector != null && mode.equals(Mode.autoLogin)) { tokenSelector.setCookieEnabled(enabled); } } }*/ /* public int getCookieMaxAge() { return cookieMaxAge; } public void setCookieMaxAge(int cookieMaxAge) { this.cookieMaxAge = cookieMaxAge; }*/ public TokenStore getTokenStore() { return tokenStore; } public void setTokenStore(TokenStore tokenStore) { this.tokenStore = tokenStore; } /** * A flag that an application can use to protect sensitive operations if the user has been * auto-authenticated. */ public boolean isAutoLoggedIn() { return autoLoggedIn; } protected String generateTokenValue() { StringBuilder sb = new StringBuilder(); sb.append(new UID().toString()); sb.append(":"); sb.append(random.nextLong()); return sb.toString(); } protected String encodeToken(String username, String value) { StringBuilder sb = new StringBuilder(); sb.append(username); sb.append(":"); sb.append(value); return Base64.encodeBytes(sb.toString().getBytes()); } /** * I hate these hacks... */ private class BoolWrapper { boolean value; } /* private class DecodedToken { private String username; private String value; public DecodedToken(String cookieValue) { if (cookieValue != null) { try { String decoded = new String(Base64.decode(cookieValue)); username = decoded.substring(0, decoded.indexOf(':')); value = decoded.substring(decoded.indexOf(':') + 1); } catch (Exception ex) { // intentionally swallow } } } public String getUsername() { return username; } public String getValue() { return value; } }*/ }