keycloak-aplcache

Improvements to required user actions. Including adding support

9/21/2013 8:21:28 AM

Changes

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index db12ace..a15d053 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -13,7 +13,7 @@ public class UserRepresentation {
 
     protected String self; // link
     protected String username;
-    protected String status;
+    protected boolean enabled;
     protected String firstName;
     protected String lastName;
     protected String email;
@@ -61,6 +61,14 @@ public class UserRepresentation {
         this.username = username;
     }
 
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
     public Map<String, String> getAttributes() {
         return attributes;
     }
@@ -92,14 +100,6 @@ public class UserRepresentation {
         return this;
     }
 
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
     public List<String> getRequiredActions() {
         return requiredActions;
     }
diff --git a/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json b/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
index 177b45f..284a4be 100755
--- a/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
+++ b/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
@@ -18,7 +18,7 @@
     "users" : [
         {
             "username" : "bburke@redhat.com",
-            "status": "ENABLED",
+            "enabled": true,
             "attributes" : {
                 "email" : "bburke@redhat.com"
             },
@@ -29,7 +29,7 @@
         },
         {
             "username" : "third-party",
-            "status": "ENABLED",
+            "enabled": true,
             "credentials" : [
                 { "type" : "password",
                     "value" : "password" }
diff --git a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
index b735cd3..ce80497 100755
--- a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
+++ b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
@@ -3,11 +3,13 @@ package org.keycloak.services.managers;
 import org.keycloak.representations.SkeletonKeyToken;
 import org.keycloak.services.models.RoleModel;
 import org.keycloak.services.models.UserModel;
+import org.keycloak.services.models.UserModel.RequiredAction;
 
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 
 /**
@@ -23,6 +25,7 @@ public class AccessCodeEntry {
     protected long expiration;
     protected SkeletonKeyToken token;
     protected UserModel user;
+    protected Set<RequiredAction> requiredActions;
     protected UserModel client;
     protected List<RoleModel> realmRolesRequested = new ArrayList<RoleModel>();
     MultivaluedMap<String, RoleModel> resourceRolesRequested = new MultivaluedHashMap<String, RoleModel>();
@@ -75,6 +78,14 @@ public class AccessCodeEntry {
         this.user = user;
     }
 
+    public Set<RequiredAction> getRequiredActions() {
+        return requiredActions;
+    }
+
+    public void setRequiredActions(Set<RequiredAction> requiredActions) {
+        this.requiredActions = requiredActions;
+    }
+
     public List<RoleModel> getRealmRolesRequested() {
         return realmRolesRequested;
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 2874ef1..a0c10e9 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -12,8 +12,6 @@ import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.models.RealmModel;
 import org.keycloak.services.models.RequiredCredentialModel;
 import org.keycloak.services.models.UserModel;
-import org.keycloak.services.models.UserModel.RequiredAction;
-import org.keycloak.services.models.UserModel.Status;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.SaasService;
 
@@ -205,7 +203,7 @@ public class AuthenticationManager {
             return AuthenticationStatus.INVALID_USER;
         }
 
-        if (!user.isEnabled() && user.getStatus() == Status.DISABLED) {
+        if (!user.isEnabled()) {
             logger.info("Account is disabled, contact admin.");
             return AuthenticationStatus.ACCOUNT_DISABLED;
         }
@@ -249,7 +247,7 @@ public class AuthenticationManager {
                 }
             }
 
-            if (user.getStatus() == Status.ACTIONS_REQUIRED) {
+            if (!user.getRequiredActions().isEmpty()) {
                 return AuthenticationStatus.ACTIONS_REQUIRED;
             } else {
                 return AuthenticationStatus.SUCCESS;
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 438ac68..f584843 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -210,7 +210,7 @@ public class RealmManager {
 
     public UserModel createUser(RealmModel newRealm, UserRepresentation userRep) {
         UserModel user = newRealm.addUser(userRep.getUsername());
-        user.setStatus(UserModel.Status.valueOf(userRep.getStatus()));
+        user.setEnabled(userRep.isEnabled());
         user.setEmail(userRep.getEmail());
         if (userRep.getAttributes() != null) {
             for (Map.Entry<String, String> entry : userRep.getAttributes().entrySet()) {
diff --git a/services/src/main/java/org/keycloak/services/managers/UserManager.java b/services/src/main/java/org/keycloak/services/managers/UserManager.java
index b486cfe..49cd813 100755
--- a/services/src/main/java/org/keycloak/services/managers/UserManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/UserManager.java
@@ -19,7 +19,7 @@ public class UserManager {
         rep.setEmail(user.getEmail());
         rep.setLastName(user.getLastName());
         rep.setFirstName(user.getFirstName());
-        rep.setStatus(user.getStatus().name());
+        rep.setEnabled(user.isEnabled());
         rep.setUsername(user.getLoginName());
         for (Map.Entry<String, String> entry : user.getAttributes().entrySet()) {
             rep.attribute(entry.getKey(), entry.getValue());
@@ -29,7 +29,7 @@ public class UserManager {
 
     public UserModel createUser(RealmModel newRealm, UserRepresentation userRep) {
         UserModel user = newRealm.addUser(userRep.getUsername());
-        user.setStatus(UserModel.Status.valueOf(userRep.getStatus()));
+        user.setEnabled(userRep.isEnabled());
         user.setEmail(userRep.getEmail());
         user.setFirstName(userRep.getFirstName());
         user.setLastName(userRep.getLastName());
@@ -56,7 +56,7 @@ public class UserManager {
      * @param userRep
      */
     public void updateUserAsAdmin(UserModel user, UserRepresentation userRep) {
-        user.setStatus(UserModel.Status.valueOf(userRep.getStatus()));
+        user.setEnabled(userRep.isEnabled());
         user.setEmail(userRep.getEmail());
         user.setFirstName(userRep.getFirstName());
         user.setLastName(userRep.getLastName());
diff --git a/services/src/main/java/org/keycloak/services/models/picketlink/UserAdapter.java b/services/src/main/java/org/keycloak/services/models/picketlink/UserAdapter.java
index 6ff4a96..169a4e2 100755
--- a/services/src/main/java/org/keycloak/services/models/picketlink/UserAdapter.java
+++ b/services/src/main/java/org/keycloak/services/models/picketlink/UserAdapter.java
@@ -3,8 +3,9 @@ package org.keycloak.services.models.picketlink;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.List;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.keycloak.services.models.UserModel;
 import org.keycloak.services.models.utils.ArrayUtils;
@@ -20,7 +21,6 @@ public class UserAdapter implements UserModel {
     private static final String EMAIL_VERIFIED_ATTR = "emailVerified";
     private static final String KEYCLOAK_TOTP_ATTR = "totpEnabled";
     private static final String REQUIRED_ACTIONS_ATTR = "requiredActions";
-    private static final String STATUS_ATTR = "status";
 
     protected User user;
     protected IdentityManager idm;
@@ -44,23 +44,9 @@ public class UserAdapter implements UserModel {
         return user.isEnabled();
     }
 
-    public UserModel.Status getStatus() {
-        Attribute<UserModel.Status> a = user.getAttribute(STATUS_ATTR);
-        if (a != null) {
-            return a.getValue();
-        } else {
-            return user.isEnabled() ? UserModel.Status.ENABLED : UserModel.Status.DISABLED;
-        }
-    }
-
     @Override
-    public void setStatus(UserModel.Status status) {
-        user.setAttribute(new Attribute<UserModel.Status>(STATUS_ATTR, status));
-        if (status == UserModel.Status.DISABLED) {
-            user.setEnabled(false);
-        } else {
-            user.setEnabled(true);
-        }
+    public void setEnabled(boolean enabled) {
+        user.setEnabled(enabled);
         idm.update(user);
     }
 
@@ -131,7 +117,7 @@ public class UserAdapter implements UserModel {
     @Override
     public Map<String, String> getAttributes() {
         Map<String, String> attributes = new HashMap<String, String>();
-        for (Attribute attribute : user.getAttributes()) {
+        for (Attribute<?> attribute : user.getAttributes()) {
            if (attribute.getValue() != null) attributes.put(attribute.getName(), attribute.getValue().toString());
         }
         return attributes;
@@ -152,12 +138,16 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public List<RequiredAction> getRequiredActions() {
+    public Set<RequiredAction> getRequiredActions() {
         RequiredAction[] actions = getRequiredActionsArray();
         if (actions == null) {
-            return null;
+            return Collections.emptySet();
         } else {
-            return Collections.unmodifiableList(Arrays.asList(actions));
+            Set<RequiredAction> s = new HashSet<RequiredAction>();
+            for (RequiredAction a : actions) {
+                s.add(a);
+            }
+            return Collections.unmodifiableSet(s);
         }
     }
 
@@ -167,7 +157,9 @@ public class UserAdapter implements UserModel {
         if (actions == null) {
             actions = new RequiredAction[] { action };
         } else {
-            actions = ArrayUtils.add(actions, action);
+            if (Arrays.binarySearch(actions, action) < 0) {
+                actions = ArrayUtils.add(actions, action);
+            }
         }
 
         Attribute<RequiredAction[]> a = new Attribute<RequiredAction[]>(REQUIRED_ACTIONS_ATTR, actions);
diff --git a/services/src/main/java/org/keycloak/services/models/UserModel.java b/services/src/main/java/org/keycloak/services/models/UserModel.java
index bab26e0..9bd370a 100755
--- a/services/src/main/java/org/keycloak/services/models/UserModel.java
+++ b/services/src/main/java/org/keycloak/services/models/UserModel.java
@@ -1,7 +1,7 @@
 package org.keycloak.services.models;
 
-import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -19,9 +19,7 @@ public interface UserModel {
 
     boolean isTotp();
 
-    Status getStatus();
-
-    void setStatus(Status status);
+    void setEnabled(boolean enabled);
 
     void setAttribute(String name, String value);
 
@@ -31,7 +29,7 @@ public interface UserModel {
 
     Map<String, String> getAttributes();
 
-    List<RequiredAction> getRequiredActions();
+    Set<RequiredAction> getRequiredActions();
     
     void addRequiredAction(RequiredAction action);
 
@@ -55,10 +53,6 @@ public interface UserModel {
 
     void setTotp(boolean totp);
 
-    public static enum Status {
-        ENABLED, DISABLED, ACTIONS_REQUIRED
-    }
-
     public static enum RequiredAction {
         VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, RESET_PASSWORD
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 3213fe3..da8aac6 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -21,6 +21,9 @@
  */
 package org.keycloak.services.resources;
 
+import java.util.HashSet;
+import java.util.Set;
+
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
@@ -98,49 +101,44 @@ public class AccountService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
-        UserModel user = getUser(RequiredAction.UPDATE_PROFILE);
-        if (user != null) {
-            user.setFirstName(formData.getFirst("firstName"));
-            user.setLastName(formData.getFirst("lastName"));
-            user.setEmail(formData.getFirst("email"));
-
-            Response response = redirectOauth();
-            if (response != null) {
-                return response;
-            } else {
-                return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccount();
-            }
-        } else {
+        AccessCodeEntry accessCodeEntry = getAccessCodeEntry(RequiredAction.UPDATE_PROFILE);
+        UserModel user = accessCodeEntry != null ? getUserFromAccessCode(accessCodeEntry) : getUserFromAuthManager();
+        if (user == null) {
             return Response.status(Status.FORBIDDEN).build();
         }
-    }
 
-    private UserModel getUser(RequiredAction action) {
-        if (uriInfo.getQueryParameters().containsKey(FormFlows.CODE)) {
-            AccessCodeEntry accessCodeEntry = getAccessCodeEntry(uriInfo.getQueryParameters().getFirst(FormFlows.CODE));
-            if (accessCodeEntry == null) {
-                return null;
-            }
+        user.setFirstName(formData.getFirst("firstName"));
+        user.setLastName(formData.getFirst("lastName"));
+        user.setEmail(formData.getFirst("email"));
 
-            String loginName = accessCodeEntry.getUser().getLoginName();
-            UserModel user = realm.getUser(loginName);
-            if (!user.getRequiredActions().contains(action)) {
-                return null;
-            }
-            if (!accessCodeEntry.getUser().getRequiredActions().contains(action)) {
-                return null;
-            }
-            return user;
+        user.removeRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
+        if (accessCodeEntry != null) {
+            accessCodeEntry.getRequiredActions().remove(UserModel.RequiredAction.UPDATE_PROFILE);
+        }
+
+        Response response = redirectOauth(accessCodeEntry);
+        if (response != null) {
+            return response;
         } else {
-            return getUserFromAuthManager();
+            return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccount();
         }
     }
 
+    private UserModel getUserFromAccessCode(AccessCodeEntry accessCodeEntry) {
+        String loginName = accessCodeEntry.getUser().getLoginName();
+        return realm.getUser(loginName);
+    }
+
     private UserModel getUserFromAuthManager() {
         return authManager.authenticateIdentityCookie(realm, uriInfo, headers);
     }
 
-    private AccessCodeEntry getAccessCodeEntry(String code) {
+    private AccessCodeEntry getAccessCodeEntry(RequiredAction requiredAction) {
+        String code = uriInfo.getQueryParameters().getFirst(FormFlows.CODE);
+        if (code == null) {
+            return null;
+        }
+
         JWSInput input = new JWSInput(code, providers);
         boolean verifiedCode = false;
         try {
@@ -163,6 +161,11 @@ public class AccountService {
             return null;
         }
 
+        if (accessCodeEntry.getRequiredActions() == null
+                || !accessCodeEntry.getRequiredActions().contains(requiredAction)) {
+            return null;
+        }
+
         return accessCodeEntry;
     }
 
@@ -170,72 +173,80 @@ public class AccountService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
-        UserModel user = getUser(RequiredAction.CONFIGURE_TOTP);
-        if (user != null) {
-            FormFlows forms = Flows.forms(realm, request, uriInfo);
+        AccessCodeEntry accessCodeEntry = getAccessCodeEntry(RequiredAction.CONFIGURE_TOTP);
+        UserModel user = accessCodeEntry != null ? getUserFromAccessCode(accessCodeEntry) : getUserFromAuthManager();
+        if (user == null) {
+            return Response.status(Status.FORBIDDEN).build();
+        }
 
-            String totp = formData.getFirst("totp");
-            String totpSecret = formData.getFirst("totpSecret");
+        FormFlows forms = Flows.forms(realm, request, uriInfo);
 
-            String error = null;
+        String totp = formData.getFirst("totp");
+        String totpSecret = formData.getFirst("totpSecret");
 
-            if (Validation.isEmpty(totp)) {
-                error = Messages.MISSING_TOTP;
-            } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
-                error = Messages.INVALID_TOTP;
-            }
+        String error = null;
 
-            if (error != null) {
-                return forms.setError(error).setUser(user).forwardToTotp();
-            }
+        if (Validation.isEmpty(totp)) {
+            error = Messages.MISSING_TOTP;
+        } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
+            error = Messages.INVALID_TOTP;
+        }
 
-            UserCredentialModel credentials = new UserCredentialModel();
-            credentials.setType(CredentialRepresentation.TOTP);
-            credentials.setValue(formData.getFirst("totpSecret"));
-            realm.updateCredential(user, credentials);
+        if (error != null) {
+            return forms.setError(error).setUser(user).forwardToTotp();
+        }
 
-            user.removeRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
+        UserCredentialModel credentials = new UserCredentialModel();
+        credentials.setType(CredentialRepresentation.TOTP);
+        credentials.setValue(formData.getFirst("totpSecret"));
+        realm.updateCredential(user, credentials);
 
-            user.setTotp(true);
+        user.removeRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
+        if (accessCodeEntry != null) {
+            accessCodeEntry.getRequiredActions().remove(UserModel.RequiredAction.CONFIGURE_TOTP);
+        }
 
-            Response response = redirectOauth();
-            if (response != null) {
-                return response;
-            } else {
-                return Flows.forms(realm, request, uriInfo).setUser(user).forwardToTotp();
-            }
+        user.setTotp(true);
+
+        Response response = redirectOauth(accessCodeEntry);
+        if (response != null) {
+            return response;
         } else {
-            return Response.status(Status.FORBIDDEN).build();
+            return Flows.forms(realm, request, uriInfo).setUser(user).forwardToTotp();
         }
     }
 
     @Path("email-verify")
     @GET
-    public Response processEmailVerification(@QueryParam("code") String code) {
-        AccessCodeEntry accessCodeEntry = getAccessCodeEntry(code);
-        String loginName = accessCodeEntry.getUser().getLoginName();
-        UserModel user = realm.getUser(loginName);
-        if (user != null) {
-            user.setEmailVerified(true);
-            user.removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
-
-            Response response = redirectOauth();
-            if (response != null) {
-                return response;
-            } else {
-                return Flows.forms(realm, request, uriInfo).setUser(user).forwardToVerifyEmail();
-            }
-        } else {
+    public Response processEmailVerification() {
+        AccessCodeEntry accessCodeEntry = getAccessCodeEntry(RequiredAction.VERIFY_EMAIL);
+        UserModel user = accessCodeEntry != null ? getUserFromAccessCode(accessCodeEntry) : null;
+        if (user == null) {
             return Response.status(Status.FORBIDDEN).build();
         }
+
+        user.setEmailVerified(true);
+        user.removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
+        if (accessCodeEntry != null) {
+            accessCodeEntry.getRequiredActions().remove(UserModel.RequiredAction.VERIFY_EMAIL);
+        }
+
+        Response response = redirectOauth(accessCodeEntry);
+        if (response != null) {
+            return response;
+        } else {
+            return Flows.forms(realm, request, uriInfo).setUser(user).forwardToVerifyEmail();
+        }
     }
 
-    private Response redirectOauth() {
+    private Response redirectOauth(AccessCodeEntry accessCodeEntry) {
+        if (accessCodeEntry == null) {
+            return null;
+        }
         String redirect = uriInfo.getQueryParameters().getFirst("redirect_uri");
         if (redirect != null) {
-            AccessCodeEntry accessCode = getAccessCodeEntry(uriInfo.getQueryParameters().getFirst(FormFlows.CODE));
             String state = uriInfo.getQueryParameters().getFirst("state");
-            return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode, state,
+            return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCodeEntry, state,
                     redirect);
         } else {
             return null;
@@ -246,50 +257,53 @@ public class AccountService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
-        UserModel user = getUser(RequiredAction.RESET_PASSWORD);
-        if (user != null) {
-            FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
+        AccessCodeEntry accessCodeEntry = getAccessCodeEntry(RequiredAction.RESET_PASSWORD);
+        UserModel user = accessCodeEntry != null ? getUserFromAccessCode(accessCodeEntry) : getUserFromAuthManager();
+        if (user == null) {
+            return Response.status(Status.FORBIDDEN).build();
+        }
 
-            String password = formData.getFirst("password");
-            String passwordNew = formData.getFirst("password-new");
-            String passwordConfirm = formData.getFirst("password-confirm");
+        FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
 
-            String error = null;
+        String password = formData.getFirst("password");
+        String passwordNew = formData.getFirst("password-new");
+        String passwordConfirm = formData.getFirst("password-confirm");
 
-            if (Validation.isEmpty(passwordNew)) {
-                error = Messages.MISSING_PASSWORD;
-            } else if (!passwordNew.equals(passwordConfirm)) {
-                error = Messages.INVALID_PASSWORD_CONFIRM;
-            }
+        String error = null;
 
-            if (user.getRequiredActions() == null || !user.getRequiredActions().contains(RequiredAction.RESET_PASSWORD)) {
-                if (Validation.isEmpty(password)) {
-                    error = Messages.MISSING_PASSWORD;
-                } else if (!realm.validatePassword(user, password)) {
-                    error = Messages.INVALID_PASSWORD_EXISTING;
-                }
-            }
+        if (Validation.isEmpty(passwordNew)) {
+            error = Messages.MISSING_PASSWORD;
+        } else if (!passwordNew.equals(passwordConfirm)) {
+            error = Messages.INVALID_PASSWORD_CONFIRM;
+        }
 
-            if (error != null) {
-                return forms.setError(error).forwardToPassword();
+        if (accessCodeEntry == null) {
+            if (Validation.isEmpty(password)) {
+                error = Messages.MISSING_PASSWORD;
+            } else if (!realm.validatePassword(user, password)) {
+                error = Messages.INVALID_PASSWORD_EXISTING;
             }
+        }
 
-            UserCredentialModel credentials = new UserCredentialModel();
-            credentials.setType(CredentialRepresentation.PASSWORD);
-            credentials.setValue(passwordNew);
+        if (error != null) {
+            return forms.setError(error).forwardToPassword();
+        }
 
-            realm.updateCredential(user, credentials);
+        UserCredentialModel credentials = new UserCredentialModel();
+        credentials.setType(CredentialRepresentation.PASSWORD);
+        credentials.setValue(passwordNew);
 
-            user.removeRequiredAction(RequiredAction.RESET_PASSWORD);
-            user.setStatus(UserModel.Status.ENABLED);
+        realm.updateCredential(user, credentials);
 
-            authManager.expireIdentityCookie(realm, uriInfo);
-            new ResourceAdminManager().singleLogOut(realm, user.getLoginName());
-            
-            return Flows.forms(realm, request, uriInfo).forwardToLogin();
-        } else {
-            return Response.status(Status.FORBIDDEN).build();
+        user.removeRequiredAction(RequiredAction.RESET_PASSWORD);
+        if (accessCodeEntry != null) {
+            accessCodeEntry.getRequiredActions().remove(UserModel.RequiredAction.RESET_PASSWORD);
         }
+
+        authManager.expireIdentityCookie(realm, uriInfo);
+        new ResourceAdminManager().singleLogOut(realm, user.getLoginName());
+
+        return Flows.forms(realm, request, uriInfo).forwardToLogin();
     }
 
     @Path("")
@@ -328,7 +342,14 @@ public class AccountService {
     @Path("password")
     @GET
     public Response passwordPage() {
-        UserModel user = getUser(RequiredAction.RESET_PASSWORD);
+        UserModel user = getUserFromAuthManager();
+
+        // TODO Remove when we have a separate login-reset-password page
+        if (user == null) {
+            AccessCodeEntry accessCodeEntry = getAccessCodeEntry(RequiredAction.RESET_PASSWORD);
+            user = accessCodeEntry != null ? getUserFromAccessCode(accessCodeEntry) : null;
+        }
+
         if (user != null) {
             return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
         } else {
@@ -361,10 +382,12 @@ public class AccountService {
 
         // String username = formData.getFirst("username");
         UserModel user = realm.getUser(username);
-        user.addRequiredAction(RequiredAction.RESET_PASSWORD);
-        user.setStatus(UserModel.Status.ACTIONS_REQUIRED);
+
+        Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
+        requiredActions.add(RequiredAction.RESET_PASSWORD);
 
         AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
+        accessCode.setRequiredActions(requiredActions);
         accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
 
         if (user.getEmail() == null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index a71e8ac..acd86cb 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -21,6 +21,10 @@
  */
 package org.keycloak.services.resources.flows;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.services.managers.AccessCodeEntry;
@@ -30,6 +34,7 @@ import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.models.RealmModel;
 import org.keycloak.services.models.RoleModel;
 import org.keycloak.services.models.UserModel;
+import org.keycloak.services.models.UserModel.RequiredAction;
 import org.keycloak.services.resources.TokenService;
 
 import javax.ws.rs.core.Response;
@@ -88,16 +93,19 @@ public class OAuthFlows {
         log.info("processAccessCode: go to oauth page?: "
                 + (!isResource && (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested()
                         .size() > 0)));
-        if (!isResource
-                && (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested().size() > 0)) {
+
+        Set<RequiredAction> requiredActions = user.getRequiredActions();
+        if (!requiredActions.isEmpty()) {
+            accessCode.setRequiredActions(new HashSet<UserModel.RequiredAction>(requiredActions));
             accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
-            return oauthGrantPage(accessCode, client);
+            return Flows.forms(realm, request, uriInfo).setCode(accessCode.getCode()).setUser(user)
+                    .forwardToAction(user.getRequiredActions().iterator().next());
         }
 
-        if (user.getRequiredActions() != null) {
+        if (!isResource
+                && (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested().size() > 0)) {
             accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
-            return Flows.forms(realm, request, uriInfo).setCode(accessCode.getCode()).setUser(user)
-                    .forwardToAction(user.getRequiredActions().get(0));
+            return oauthGrantPage(accessCode, client);
         }
 
         if (redirect != null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/SaasService.java b/services/src/main/java/org/keycloak/services/resources/SaasService.java
index 12156c3..4a64674 100755
--- a/services/src/main/java/org/keycloak/services/resources/SaasService.java
+++ b/services/src/main/java/org/keycloak/services/resources/SaasService.java
@@ -229,7 +229,7 @@ public class SaasService {
                 return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData)
                         .forwardToLogin();
             case ACTIONS_REQUIRED:
-                return Flows.forms(realm, request, uriInfo).forwardToAction(user.getRequiredActions().get(0));
+                return Flows.forms(realm, request, uriInfo).forwardToAction(user.getRequiredActions().iterator().next());
             default:
                 return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData)
                         .forwardToLogin();
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 2df6a7c..44b1d92 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -20,7 +20,6 @@ import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.models.*;
 import org.keycloak.services.models.UserModel.RequiredAction;
-import org.keycloak.services.models.UserModel.Status;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.OAuthFlows;
 import org.keycloak.services.validation.Validation;
@@ -218,7 +217,6 @@ public class TokenService {
         for (RequiredCredentialModel c : realm.getRequiredCredentials()) {
             if (c.getType().equals(CredentialRepresentation.TOTP) && !user.isTotp()) {
                 user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
-                user.setStatus(Status.ACTIONS_REQUIRED);
                 logger.info("User is required to configure totp");
             }
         }
@@ -227,7 +225,6 @@ public class TokenService {
     private void isEmailVerificationRequired(UserModel user) {
         if (realm.isVerifyEmail() && !user.isEmailVerified()) {
             user.addRequiredAction(RequiredAction.VERIFY_EMAIL);
-            user.setStatus(Status.ACTIONS_REQUIRED);
             logger.info("User is required to verify email");
         }
     }
@@ -417,6 +414,12 @@ public class TokenService {
             return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
                     .build();
         }
+        if (accessCode.getRequiredActions() != null && !accessCode.getRequiredActions().isEmpty()) {
+            Map<String, String> res = new HashMap<String, String>();
+            res.put("error", "invalid_grant");
+            res.put("error_description", "Actions required");
+            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res).build();
+        }
         if (!client.getLoginName().equals(accessCode.getClient().getLoginName())) {
             Map<String, String> res = new HashMap<String, String>();
             res.put("error", "invalid_grant");
diff --git a/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java b/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java
index ad3149c..76870e0 100644
--- a/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java
+++ b/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java
@@ -17,7 +17,6 @@ import org.keycloak.services.models.RealmModel;
 import org.keycloak.services.models.UserCredentialModel;
 import org.keycloak.services.models.UserModel;
 import org.keycloak.services.models.UserModel.RequiredAction;
-import org.keycloak.services.models.UserModel.Status;
 import org.keycloak.services.resources.KeycloakApplication;
 import org.picketlink.idm.credential.util.TimeBasedOTP;
 
@@ -66,7 +65,6 @@ public class AuthenticationManagerTest {
     public void authFormRequiredAction() {
         realm.addRequiredCredential(CredentialRepresentation.TOTP);
         user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
-        user.setStatus(Status.ACTIONS_REQUIRED);
         
         AuthenticationStatus status = am.authenticateForm(realm, user, formData);
         Assert.assertEquals(AuthenticationStatus.ACTIONS_REQUIRED, status);
@@ -74,21 +72,13 @@ public class AuthenticationManagerTest {
 
     @Test
     public void authFormUserDisabled() {
-        user.setStatus(Status.DISABLED);
+        user.setEnabled(false);
 
         AuthenticationStatus status = am.authenticateForm(realm, user, formData);
         Assert.assertEquals(AuthenticationStatus.ACCOUNT_DISABLED, status);
     }
 
     @Test
-    public void authFormUserRequiredActions() {
-        user.setStatus(Status.ACTIONS_REQUIRED);
-
-        AuthenticationStatus status = am.authenticateForm(realm, user, formData);
-        Assert.assertEquals(AuthenticationStatus.ACTIONS_REQUIRED, status);
-    }
-
-    @Test
     public void authFormWithTotp() {
         realm.addRequiredCredential(CredentialRepresentation.TOTP);
         
diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java
index 015caac..581c1e8 100755
--- a/services/src/test/java/org/keycloak/test/AdapterTest.java
+++ b/services/src/test/java/org/keycloak/test/AdapterTest.java
@@ -15,11 +15,13 @@ import org.keycloak.services.models.RequiredCredentialModel;
 import org.keycloak.services.models.RoleModel;
 import org.keycloak.services.models.UserModel;
 import org.keycloak.services.models.UserCredentialModel;
+import org.keycloak.services.models.UserModel.RequiredAction;
 import org.keycloak.services.resources.KeycloakApplication;
 
 
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.StringTokenizer;
@@ -265,53 +267,41 @@ public class AdapterTest {
     }
 
     @Test
-    public void testUserStatus() throws Exception {
+    public void testUserRequiredActions() throws Exception {
         test1CreateRealm();
 
         UserModel user = realmModel.addUser("bburke");
-        Assert.assertTrue(user.isEnabled());
-        Assert.assertEquals(UserModel.Status.ENABLED, user.getStatus());
-
-        user.setStatus(UserModel.Status.DISABLED);
-        user = realmModel.getUser("bburke");
 
-        Assert.assertFalse(user.isEnabled());
-        Assert.assertEquals(UserModel.Status.DISABLED, user.getStatus());
+        Assert.assertTrue(user.getRequiredActions().isEmpty());
 
-        user.setStatus(UserModel.Status.ACTIONS_REQUIRED);
+        user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
         user = realmModel.getUser("bburke");
 
-        Assert.assertTrue(user.isEnabled());
-        Assert.assertEquals(UserModel.Status.ACTIONS_REQUIRED, user.getStatus());
-    }
-
-    @Test
-    public void testUserRequiredActions() throws Exception {
-        test1CreateRealm();
-
-        UserModel user = realmModel.addUser("bburke");
-
-        Assert.assertNull(user.getRequiredActions());
+        Assert.assertEquals(1, user.getRequiredActions().size());
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
 
         user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
         user = realmModel.getUser("bburke");
 
-        Assert.assertEquals(Arrays.asList(UserModel.RequiredAction.CONFIGURE_TOTP), user.getRequiredActions());
+        Assert.assertEquals(1, user.getRequiredActions().size());
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
 
         user.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
         user = realmModel.getUser("bburke");
 
-        Assert.assertEquals(Arrays.asList(UserModel.RequiredAction.CONFIGURE_TOTP, UserModel.RequiredAction.VERIFY_EMAIL),
-                user.getRequiredActions());
+        Assert.assertEquals(2, user.getRequiredActions().size());
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
 
         user.removeRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
         user = realmModel.getUser("bburke");
 
-        Assert.assertEquals(Arrays.asList(UserModel.RequiredAction.VERIFY_EMAIL), user.getRequiredActions());
+        Assert.assertEquals(1, user.getRequiredActions().size());
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
 
         user.removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
         user = realmModel.getUser("bburke");
 
-        Assert.assertNull(user.getRequiredActions());
+        Assert.assertTrue(user.getRequiredActions().isEmpty());
     }
 }
diff --git a/services/src/test/resources/testrealm.json b/services/src/test/resources/testrealm.json
index 2549914..cfe5215 100755
--- a/services/src/test/resources/testrealm.json
+++ b/services/src/test/resources/testrealm.json
@@ -12,7 +12,7 @@
     "users": [
         {
             "username": "wburke",
-            "status": "ENABLED",
+            "enabled": true,
             "attributes": {
                 "email": "bburke@redhat.com"
             },
@@ -25,7 +25,7 @@
         },
         {
             "username": "loginclient",
-            "status": "ENABLED",
+            "enabled": true,
             "credentials": [
                 {
                     "type": "password",
@@ -35,7 +35,7 @@
         },
         {
             "username": "admin",
-            "status": "ENABLED",
+            "enabled": true,
             "credentials": [
                 {
                     "type": "password",
@@ -45,7 +45,7 @@
         },
         {
             "username": "oauthclient",
-            "status": "ENABLED",
+            "enabled": true,
             "credentials": [
                 {
                     "type": "password",
@@ -55,7 +55,7 @@
         },
         {
             "username": "mySocialUser",
-            "status": "ENABLED"
+            "enabled": true
         }
     ],
     "roleMappings": [
diff --git a/services/src/test/resources/testrealm-demo.json b/services/src/test/resources/testrealm-demo.json
index 8ef37fe..92d23ea 100755
--- a/services/src/test/resources/testrealm-demo.json
+++ b/services/src/test/resources/testrealm-demo.json
@@ -15,7 +15,7 @@
     "users" : [
         {
             "username" : "bburke@redhat.com",
-            "status": "ENABLED",
+            "enabled": true,
             "attributes" : {
                 "email" : "bburke@redhat.com"
             },
@@ -26,7 +26,7 @@
         },
         {
             "username" : "third-party",
-            "status": "ENABLED",
+            "enabled": true,
             "credentials" : [
                 { "type" : "Password",
                     "value" : "password" }
diff --git a/testsuite/src/test/java/org/keycloak/testsuite/RegisterTest.java b/testsuite/src/test/java/org/keycloak/testsuite/RegisterTest.java
index 26683e1..1417262 100644
--- a/testsuite/src/test/java/org/keycloak/testsuite/RegisterTest.java
+++ b/testsuite/src/test/java/org/keycloak/testsuite/RegisterTest.java
@@ -39,7 +39,7 @@ public class RegisterTest extends AbstractDroneTest {
 
         Assert.assertTrue(registerPage.isCurrent());
 
-        registerPage.register("name", "email", "username", null, null);
+        registerPage.register("name", "email", "registerExistingUser", null, null);
 
         Assert.assertTrue(registerPage.isCurrent());
         Assert.assertEquals("Please specify password", registerPage.getError());
@@ -52,7 +52,7 @@ public class RegisterTest extends AbstractDroneTest {
 
         Assert.assertTrue(registerPage.isCurrent());
 
-        registerPage.register("name", "email", "bburke@redhat.com", "password", "invalid");
+        registerPage.register("name", "email", "registerUserInvalidPasswordConfirm", "password", "invalid");
 
         Assert.assertTrue(registerPage.isCurrent());
         Assert.assertEquals("Password confirmation doesn't match", registerPage.getError());
@@ -65,7 +65,7 @@ public class RegisterTest extends AbstractDroneTest {
 
         Assert.assertTrue(registerPage.isCurrent());
 
-        registerPage.register("name", "email", "username", null, null);
+        registerPage.register("name", "email", "registerUserMissingPassword", null, null);
 
         Assert.assertTrue(registerPage.isCurrent());
         Assert.assertEquals("Please specify password", registerPage.getError());
@@ -91,7 +91,7 @@ public class RegisterTest extends AbstractDroneTest {
 
         Assert.assertTrue(registerPage.isCurrent());
 
-        registerPage.register("name", "email", "username", "password", "password");
+        registerPage.register("name", "email", "registerUserSuccess", "password", "password");
         Assert.assertTrue(appPage.isCurrent());
     }
 
diff --git a/testsuite/src/test/java/org/keycloak/testsuite/ResetPasswordTest.java b/testsuite/src/test/java/org/keycloak/testsuite/ResetPasswordTest.java
index e90a1d9..22b0ab7 100644
--- a/testsuite/src/test/java/org/keycloak/testsuite/ResetPasswordTest.java
+++ b/testsuite/src/test/java/org/keycloak/testsuite/ResetPasswordTest.java
@@ -91,4 +91,24 @@ public class ResetPasswordTest extends AbstractDroneTest {
         Assert.assertEquals("bburke@redhat.com", appPage.getUser());
     }
 
+    @Test
+    public void tempPassword() {
+        appPage.open();
+
+        Assert.assertTrue(loginPage.isCurrent());
+
+        loginPage.login("reset@pass.com", "temp-password");
+
+        Assert.assertTrue(changePasswordPage.isCurrent());
+
+        changePasswordPage.changePassword("new-password", "new-password");
+
+        Assert.assertTrue(loginPage.isCurrent());
+
+        loginPage.login("reset@pass.com", "new-password");
+
+        Assert.assertTrue(appPage.isCurrent());
+        Assert.assertEquals("reset@pass.com", appPage.getUser());
+    }
+
 }
diff --git a/testsuite/src/test/resources/testrealm.json b/testsuite/src/test/resources/testrealm.json
index 3161c1f..0ea2810 100755
--- a/testsuite/src/test/resources/testrealm.json
+++ b/testsuite/src/test/resources/testrealm.json
@@ -17,7 +17,7 @@
     "users" : [
         {
             "username" : "bburke@redhat.com",
-            "status": "ENABLED",
+            "enabled": true,
             "email" : "bburke@redhat.com",
             "credentials" : [
                 { "type" : "password",
@@ -25,8 +25,18 @@
             ]
         },
         {
+            "username" : "reset@pass.com",
+            "enabled": true,
+            "requiredActions" : [ "RESET_PASSWORD" ],
+            "email" : "reset@pass.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "temp-password" }
+            ]
+        },
+        {
             "username" : "third-party",
-            "status": "ENABLED",
+            "enabled": true,
             "credentials" : [
                 { "type" : "password",
                     "value" : "password" }
@@ -49,6 +59,10 @@
             "roles": ["user"]
         },
         {
+            "username": "reset@pass.com",
+            "roles": ["user"]
+        },
+        {
             "username": "third-party",
             "roles": ["KEYCLOAK_IDENTITY_REQUESTER"]
         }
diff --git a/testsuite/src/test/resources/testrealm-email.json b/testsuite/src/test/resources/testrealm-email.json
index 49340fd..79e1a94 100755
--- a/testsuite/src/test/resources/testrealm-email.json
+++ b/testsuite/src/test/resources/testrealm-email.json
@@ -17,7 +17,7 @@
     "users" : [
         {
             "username" : "bburke@redhat.com",
-            "status": "ENABLED",
+            "enabled": true,
             "email" : "bburke@redhat.com",
             "credentials" : [
                 { "type" : "password",
@@ -26,7 +26,7 @@
         },
         {
             "username" : "third-party",
-            "status": "ENABLED",
+            "enabled": true,
             "credentials" : [
                 { "type" : "password",
                     "value" : "password" }
diff --git a/testsuite/src/test/resources/testrealm-totp.json b/testsuite/src/test/resources/testrealm-totp.json
index 014339f..d73f805 100755
--- a/testsuite/src/test/resources/testrealm-totp.json
+++ b/testsuite/src/test/resources/testrealm-totp.json
@@ -16,7 +16,7 @@
     "users" : [
         {
             "username" : "bburke@redhat.com",
-            "status": "ENABLED",
+            "enabled": true,
             "email" : "bburke@redhat.com",
             "credentials" : [
                 { "type" : "password",
@@ -25,7 +25,7 @@
         },
         {
             "username" : "third-party",
-            "status": "ENABLED",
+            "enabled": true,
             "credentials" : [
                 { "type" : "password",
                     "value" : "password" }