Improvements to required user actions. Including adding support for required actions to AccessCodeEntry. Added test for temporary password.
This commit is contained in:
parent
4422d9609b
commit
72315bb9d7
21 changed files with 290 additions and 247 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,8 +157,10 @@ public class UserAdapter implements UserModel {
|
|||
if (actions == null) {
|
||||
actions = new RequiredAction[] { action };
|
||||
} else {
|
||||
if (Arrays.binarySearch(actions, action) < 0) {
|
||||
actions = ArrayUtils.add(actions, action);
|
||||
}
|
||||
}
|
||||
|
||||
Attribute<RequiredAction[]> a = new Attribute<RequiredAction[]>(REQUIRED_ACTIONS_ATTR, actions);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
AccessCodeEntry accessCodeEntry = getAccessCodeEntry(RequiredAction.UPDATE_PROFILE);
|
||||
UserModel user = accessCodeEntry != null ? getUserFromAccessCode(accessCodeEntry) : getUserFromAuthManager();
|
||||
if (user == null) {
|
||||
return Response.status(Status.FORBIDDEN).build();
|
||||
}
|
||||
|
||||
user.setFirstName(formData.getFirst("firstName"));
|
||||
user.setLastName(formData.getFirst("lastName"));
|
||||
user.setEmail(formData.getFirst("email"));
|
||||
|
||||
Response response = redirectOauth();
|
||||
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 Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccount();
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
private UserModel getUserFromAccessCode(AccessCodeEntry accessCodeEntry) {
|
||||
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;
|
||||
} else {
|
||||
return getUserFromAuthManager();
|
||||
}
|
||||
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,8 +173,12 @@ 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) {
|
||||
AccessCodeEntry accessCodeEntry = getAccessCodeEntry(RequiredAction.CONFIGURE_TOTP);
|
||||
UserModel user = accessCodeEntry != null ? getUserFromAccessCode(accessCodeEntry) : getUserFromAuthManager();
|
||||
if (user == null) {
|
||||
return Response.status(Status.FORBIDDEN).build();
|
||||
}
|
||||
|
||||
FormFlows forms = Flows.forms(realm, request, uriInfo);
|
||||
|
||||
String totp = formData.getFirst("totp");
|
||||
|
@ -195,47 +202,51 @@ public class AccountService {
|
|||
realm.updateCredential(user, credentials);
|
||||
|
||||
user.removeRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
|
||||
if (accessCodeEntry != null) {
|
||||
accessCodeEntry.getRequiredActions().remove(UserModel.RequiredAction.CONFIGURE_TOTP);
|
||||
}
|
||||
|
||||
user.setTotp(true);
|
||||
|
||||
Response response = redirectOauth();
|
||||
Response response = redirectOauth(accessCodeEntry);
|
||||
if (response != null) {
|
||||
return response;
|
||||
} else {
|
||||
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToTotp();
|
||||
}
|
||||
} else {
|
||||
return Response.status(Status.FORBIDDEN).build();
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
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();
|
||||
Response response = redirectOauth(accessCodeEntry);
|
||||
if (response != null) {
|
||||
return response;
|
||||
} else {
|
||||
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToVerifyEmail();
|
||||
}
|
||||
} else {
|
||||
return Response.status(Status.FORBIDDEN).build();
|
||||
}
|
||||
}
|
||||
|
||||
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,8 +257,12 @@ 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) {
|
||||
AccessCodeEntry accessCodeEntry = getAccessCodeEntry(RequiredAction.RESET_PASSWORD);
|
||||
UserModel user = accessCodeEntry != null ? getUserFromAccessCode(accessCodeEntry) : getUserFromAuthManager();
|
||||
if (user == null) {
|
||||
return Response.status(Status.FORBIDDEN).build();
|
||||
}
|
||||
|
||||
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
|
||||
|
||||
String password = formData.getFirst("password");
|
||||
|
@ -262,7 +277,7 @@ public class AccountService {
|
|||
error = Messages.INVALID_PASSWORD_CONFIRM;
|
||||
}
|
||||
|
||||
if (user.getRequiredActions() == null || !user.getRequiredActions().contains(RequiredAction.RESET_PASSWORD)) {
|
||||
if (accessCodeEntry == null) {
|
||||
if (Validation.isEmpty(password)) {
|
||||
error = Messages.MISSING_PASSWORD;
|
||||
} else if (!realm.validatePassword(user, password)) {
|
||||
|
@ -281,15 +296,14 @@ public class AccountService {
|
|||
realm.updateCredential(user, credentials);
|
||||
|
||||
user.removeRequiredAction(RequiredAction.RESET_PASSWORD);
|
||||
user.setStatus(UserModel.Status.ENABLED);
|
||||
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();
|
||||
} else {
|
||||
return Response.status(Status.FORBIDDEN).build();
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,18 +93,21 @@ public class OAuthFlows {
|
|||
log.info("processAccessCode: go to oauth page?: "
|
||||
+ (!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 Flows.forms(realm, request, uriInfo).setCode(accessCode.getCode()).setUser(user)
|
||||
.forwardToAction(user.getRequiredActions().iterator().next());
|
||||
}
|
||||
|
||||
if (!isResource
|
||||
&& (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested().size() > 0)) {
|
||||
accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
|
||||
return oauthGrantPage(accessCode, client);
|
||||
}
|
||||
|
||||
if (user.getRequiredActions() != null) {
|
||||
accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
|
||||
return Flows.forms(realm, request, uriInfo).setCode(accessCode.getCode()).setUser(user)
|
||||
.forwardToAction(user.getRequiredActions().get(0));
|
||||
}
|
||||
|
||||
if (redirect != null) {
|
||||
return redirectAccessCode(accessCode, state, redirect);
|
||||
} else {
|
||||
|
|
|
@ -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,20 +72,12 @@ 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);
|
||||
|
|
|
@ -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;
|
||||
|
@ -264,54 +266,42 @@ public class AdapterTest {
|
|||
Assert.assertEquals("user", role.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserStatus() 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());
|
||||
|
||||
user.setStatus(UserModel.Status.ACTIONS_REQUIRED);
|
||||
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.assertTrue(user.getRequiredActions().isEmpty());
|
||||
|
||||
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.CONFIGURE_TOTP);
|
||||
user = realmModel.getUser("bburke");
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -17,16 +17,26 @@
|
|||
"users" : [
|
||||
{
|
||||
"username" : "bburke@redhat.com",
|
||||
"status": "ENABLED",
|
||||
"enabled": true,
|
||||
"email" : "bburke@redhat.com",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"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" }
|
||||
|
@ -48,6 +58,10 @@
|
|||
"username": "bburke@redhat.com",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"username": "reset@pass.com",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"username": "third-party",
|
||||
"roles": ["KEYCLOAK_IDENTITY_REQUESTER"]
|
||||
|
|
Loading…
Reference in a new issue