Details
diff --git a/forms/src/main/java/org/keycloak/forms/UrlBean.java b/forms/src/main/java/org/keycloak/forms/UrlBean.java
index cb82aaa..0fb83c0 100644
--- a/forms/src/main/java/org/keycloak/forms/UrlBean.java
+++ b/forms/src/main/java/org/keycloak/forms/UrlBean.java
@@ -108,8 +108,16 @@ public class UrlBean {
}
}
- public String getPasswordResetUrl() {
- return Urls.accountPasswordReset(baseURI, realm.getId()).toString();
+ public String getLoginUpdatePasswordUrl() {
+ return Urls.loginActionUpdatePassword(baseURI, realm.getId()).toString();
+ }
+
+ public String getLoginUpdateTotpUrl() {
+ return Urls.loginActionUpdateTotp(baseURI, realm.getId()).toString();
+ }
+
+ public String getLoginUpdateProfileUrl() {
+ return Urls.loginActionUpdateProfile(baseURI, realm.getId()).toString();
}
public String getSocialUrl() {
@@ -124,8 +132,12 @@ public class UrlBean {
return Urls.accountTotpRemove(baseURI, realm.getId()).toString();
}
- public String getEmailVerificationUrl() {
- return Urls.accountEmailVerification(baseURI, realm.getId()).toString();
+ public String getLoginPasswordResetUrl() {
+ return Urls.loginPasswordReset(baseURI, realm.getId()).toString();
+ }
+
+ public String getLoginEmailVerificationUrl() {
+ return Urls.loginActionEmailVerification(baseURI, realm.getId()).toString();
}
}
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl
index 863118e..7e63046 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl
@@ -24,7 +24,7 @@
</#list>
<div class="aside-btn">
- <p>Forgot <a href="${url.passwordResetUrl}">Password</a>?</p>
+ <p>Forgot <a href="${url.loginPasswordResetUrl}">Password</a>?</p>
</div>
<input class="btn-primary" type="submit" value="Log In"/>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl
index 9b3cca5..2b1c1c5 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl
@@ -27,7 +27,7 @@
</li>
<li class="clearfix">
<p><strong>3</strong>Enter the one-time-password provided by Google Authenticator below and click Submit to finish the setup.</p>
- <form action="${url.totpUrl}" method="post">
+ <form action="${url.loginUpdateTotpUrl}" method="post">
<div>
<label for="otp" class="two-lines">One-time-password</label><input type="text" id="totp" name="totp" />
<input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" />
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl
index 60ae59a..ad80199 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl
@@ -21,7 +21,7 @@
</#if>
<p class="instruction">${rb.getString('emailInstruction')}</p>
- <form action="${url.passwordResetUrl}" method="post">
+ <form action="${url.loginPasswordResetUrl}" method="post">
<div>
<label for="username">${rb.getString('username')}</label><input id="username" name="username" type="text" />
</div>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-update-password.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-update-password.ftl
index a9b1ed2..4db1911 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-update-password.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-update-password.ftl
@@ -11,7 +11,7 @@
<#elseif section = "form">
<div id="form">
- <form action="${url.passwordUrl}" method="post">
+ <form action="${url.loginUpdatePasswordUrl}" method="post">
<div>
<label for="password-new">${rb.getString('passwordNew')}</label><input type="password" id="password-new" name="password-new" />
</div>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-update-profile.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-update-profile.ftl
index fb9a8e4..2683d2f 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-update-profile.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-update-profile.ftl
@@ -15,7 +15,7 @@
<#elseif section = "form">
<div id="form">
- <form action="${url.accountUrl}" method="post">
+ <form action="${url.loginUpdateProfileUrl}" method="post">
<div class="feedback error bottom-left">
<p><strong>Some required fields are empty or incorrect.</strong><br>Please correct the fields in red.</p>
</div>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl
index d92c349..cff75f1 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl
@@ -20,7 +20,7 @@
Your account is not enabled. An email with instructions to verify your email address has been sent to you.
</p>
<p class="instruction">Haven't received a verification code in your email?
- <a href="${url.emailVerificationUrl}">Click here</a> to re-send the email.
+ <a href="${url.loginEmailVerificationUrl}">Click here</a> to re-send the email.
</p>
</div>
diff --git a/services/src/main/java/org/keycloak/services/email/EmailSender.java b/services/src/main/java/org/keycloak/services/email/EmailSender.java
index 93f59f9..6fd0907 100755
--- a/services/src/main/java/org/keycloak/services/email/EmailSender.java
+++ b/services/src/main/java/org/keycloak/services/email/EmailSender.java
@@ -79,7 +79,7 @@ public class EmailSender {
}
public void sendEmailVerification(UserModel user, RealmModel realm, AccessCodeEntry accessCode, UriInfo uriInfo) {
- UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountService.class, "emailVerification");
+ UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
builder.queryParam("key", accessCode.getId());
URI uri = builder.build(realm.getId());
@@ -103,7 +103,7 @@ public class EmailSender {
}
public void sendPasswordReset(UserModel user, RealmModel realm, AccessCodeEntry accessCode, UriInfo uriInfo) {
- UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountService.class, "passwordPage");
+ UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
builder.queryParam("key", accessCode.getId());
URI uri = builder.build(realm.getId());
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 2874971..7f54e52 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -98,8 +98,7 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
- AccessCodeEntry accessCodeEntry = getAccessCodeEntry(RequiredAction.UPDATE_PROFILE);
- UserModel user = accessCodeEntry != null ? getUserFromAccessCode(accessCodeEntry) : getUserFromAuthManager();
+ UserModel user = getUserFromAuthManager();
if (user == null) {
return Response.status(Status.FORBIDDEN).build();
}
@@ -108,60 +107,7 @@ public class AccountService {
user.setLastName(formData.getFirst("lastName"));
user.setEmail(formData.getFirst("email"));
- user.removeRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
- if (accessCodeEntry != null) {
- accessCodeEntry.getRequiredActions().remove(UserModel.RequiredAction.UPDATE_PROFILE);
- }
-
- if (accessCodeEntry != null) {
- return redirectOauth(user, accessCodeEntry);
- } else {
- 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(RequiredAction requiredAction) {
- String code = uriInfo.getQueryParameters().getFirst(FormFlows.CODE);
- if (code == null) {
- return null;
- }
-
- JWSInput input = new JWSInput(code, providers);
- boolean verifiedCode = false;
- try {
- verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
- } catch (Exception ignored) {
- return null;
- }
-
- if (!verifiedCode) {
- return null;
- }
-
- String key = input.readContent(String.class);
- AccessCodeEntry accessCodeEntry = tokenManager.getAccessCode(key);
- if (accessCodeEntry == null) {
- return null;
- }
-
- if (accessCodeEntry.isExpired()) {
- return null;
- }
-
- if (accessCodeEntry.getRequiredActions() == null || !accessCodeEntry.getRequiredActions().contains(requiredAction)) {
- return null;
- }
-
- return accessCodeEntry;
+ return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccount();
}
@Path("totp-remove")
@@ -177,8 +123,7 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
- AccessCodeEntry accessCodeEntry = getAccessCodeEntry(RequiredAction.CONFIGURE_TOTP);
- UserModel user = accessCodeEntry != null ? getUserFromAccessCode(accessCodeEntry) : getUserFromAuthManager();
+ UserModel user = getUserFromAuthManager();
if (user == null) {
return Response.status(Status.FORBIDDEN).build();
}
@@ -205,150 +150,39 @@ public class AccountService {
credentials.setValue(formData.getFirst("totpSecret"));
realm.updateCredential(user, credentials);
- user.removeRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
- if (accessCodeEntry != null) {
- accessCodeEntry.getRequiredActions().remove(UserModel.RequiredAction.CONFIGURE_TOTP);
- }
-
user.setTotp(true);
- if (accessCodeEntry != null) {
- return redirectOauth(user, accessCodeEntry);
- } else {
- return Flows.forms(realm, request, uriInfo).setError("successTotp").setErrorType(FormFlows.ErrorType.SUCCESS)
- .setUser(user).forwardToTotp();
- }
- }
-
- @Path("password-reset")
- @GET
- public Response passwordReset() {
- return Flows.forms(realm, request, uriInfo).forwardToPasswordReset();
- }
-
- @Path("password-reset")
- @POST
- @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
- public Response sendPasswordReset(final MultivaluedMap<String, String> formData) {
- String username = formData.getFirst("username");
- String email = formData.getFirst("email");
-
- String scopeParam = uriInfo.getQueryParameters().getFirst("scope");
- String state = uriInfo.getQueryParameters().getFirst("state");
- String redirect = uriInfo.getQueryParameters().getFirst("redirect_uri");
- String clientId = uriInfo.getQueryParameters().getFirst("client_id");
-
- UserModel client = realm.getUser(clientId);
- if (client == null) {
- return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).forwardToSecurityFailure(
- "Unknown login requester.");
- }
- if (!client.isEnabled()) {
- return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).forwardToSecurityFailure(
- "Login requester not enabled.");
- }
-
- UserModel user = realm.getUser(username);
- if (user == null || !email.equals(user.getEmail())) {
- return Flows.forms(realm, request, uriInfo).setError("emailError").forwardToPasswordReset();
- }
-
- Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
- requiredActions.add(RequiredAction.UPDATE_PASSWORD);
-
- AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
- accessCode.setRequiredActions(requiredActions);
- accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
-
- new EmailSender().sendPasswordReset(user, realm, accessCode, uriInfo);
-
- return Flows.forms(realm, request, uriInfo).setError("emailSent").setErrorType(FormFlows.ErrorType.SUCCESS)
- .forwardToPasswordReset();
- }
-
- @Path("email-verification")
- @GET
- public Response emailVerification() {
- if (uriInfo.getQueryParameters().containsKey("key")) {
- AccessCodeEntry accessCode = tokenManager.getAccessCode(uriInfo.getQueryParameters().getFirst("key"));
- if (accessCode == null || accessCode.isExpired()
- || !accessCode.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL)) {
- return Response.status(Status.FORBIDDEN).build();
- }
-
- String loginName = accessCode.getUser().getLoginName();
- UserModel user = realm.getUser(loginName);
- user.setEmailVerified(true);
- user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
-
- accessCode.getRequiredActions().remove(RequiredAction.VERIFY_EMAIL);
-
- return redirectOauth(user, accessCode);
- } else {
- AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.VERIFY_EMAIL);
- UserModel user = accessCode != null ? getUserFromAccessCode(accessCode) : null;
- if (user == null) {
- return Response.status(Status.FORBIDDEN).build();
- }
-
- return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user)
- .forwardToAction(RequiredAction.VERIFY_EMAIL);
- }
- }
-
- private Response redirectOauth(UserModel user, AccessCodeEntry accessCode) {
- if (accessCode == null) {
- return null;
- }
-
- Set<RequiredAction> requiredActions = user.getRequiredActions();
- if (!requiredActions.isEmpty()) {
- return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user)
- .forwardToAction(requiredActions.iterator().next());
- } else {
- accessCode.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
- return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
- accessCode.getState(), accessCode.getRedirectUri());
- }
+ return Flows.forms(realm, request, uriInfo).setError("successTotp").setErrorType(FormFlows.ErrorType.SUCCESS)
+ .setUser(user).forwardToTotp();
}
@Path("password")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
- AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PASSWORD);
- UserModel user = accessCode != null ? getUserFromAccessCode(accessCode) : getUserFromAuthManager();
+ UserModel user = getUserFromAuthManager();
if (user == null) {
return Response.status(Status.FORBIDDEN).build();
}
- boolean loginAction = accessCode != null;
-
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
String password = formData.getFirst("password");
String passwordNew = formData.getFirst("password-new");
String passwordConfirm = formData.getFirst("password-confirm");
- String error = null;
-
if (Validation.isEmpty(passwordNew)) {
- error = Messages.MISSING_PASSWORD;
+ forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
} else if (!passwordNew.equals(passwordConfirm)) {
- error = Messages.INVALID_PASSWORD_CONFIRM;
+ forms.setError(Messages.INVALID_PASSWORD_CONFIRM).forwardToPassword();
}
- if (!loginAction) {
- if (Validation.isEmpty(password)) {
- error = Messages.MISSING_PASSWORD;
- } else if (!realm.validatePassword(user, password)) {
- error = Messages.INVALID_PASSWORD_EXISTING;
- }
+ if (Validation.isEmpty(password)) {
+ forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
+ } else if (!realm.validatePassword(user, password)) {
+ forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword();
}
- if (error != null) {
- return forms.setError(error).forwardToPassword();
- }
UserCredentialModel credentials = new UserCredentialModel();
credentials.setType(CredentialRepresentation.PASSWORD);
@@ -356,16 +190,7 @@ public class AccountService {
realm.updateCredential(user, credentials);
- user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
- if (accessCode != null) {
- accessCode.getRequiredActions().remove(UserModel.RequiredAction.UPDATE_PASSWORD);
- }
-
- if (accessCode != null) {
- return redirectOauth(user, accessCode);
- } else {
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
- }
+ return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
}
@Path("")
@@ -404,21 +229,14 @@ public class AccountService {
@Path("password")
@GET
public Response passwordPage() {
- if (uriInfo.getQueryParameters().containsKey("key")) {
- AccessCodeEntry accessCode = tokenManager.getAccessCode(uriInfo.getQueryParameters().getFirst("key"));
- if (accessCode == null || accessCode.isExpired()
- || !accessCode.getRequiredActions().contains(RequiredAction.UPDATE_PASSWORD)) {
- return Response.status(Status.FORBIDDEN).build();
- }
-
- return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode)
- .forwardToAction(RequiredAction.UPDATE_PASSWORD);
- } else {
- UserModel user = getUserFromAuthManager();
- if (user == null) {
- return Response.status(Status.FORBIDDEN).build();
- }
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
+ UserModel user = getUserFromAuthManager();
+ if (user == null) {
+ return Response.status(Status.FORBIDDEN).build();
}
+ return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
+ }
+
+ private UserModel getUserFromAuthManager() {
+ return authManager.authenticateIdentityCookie(realm, uriInfo, headers);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
index b6a86a2..200b746 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
@@ -35,7 +35,7 @@ public class Urls {
return accountBase(baseUri).path(AccountService.class, "accessPage").build(realmId);
}
- public static UriBuilder accountBase(URI baseUri) {
+ private static UriBuilder accountBase(URI baseUri) {
return realmBase(baseUri).path(RealmsResource.class, "getAccountService");
}
@@ -59,12 +59,32 @@ public class Urls {
return accountBase(baseUri).path(AccountService.class, "processTotpRemove").build(realmId);
}
- public static URI accountEmailVerification(URI baseUri, String realmId) {
- return accountBase(baseUri).path(AccountService.class, "emailVerification").build(realmId);
+ public static URI loginActionUpdatePassword(URI baseUri, String realmId) {
+ return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updatePassword").build(realmId);
}
- public static URI accountPasswordReset(URI baseUri, String realmId) {
- return accountBase(baseUri).path(AccountService.class, "passwordReset").build(realmId);
+ public static URI loginActionUpdateTotp(URI baseUri, String realmId) {
+ return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updateTotp").build(realmId);
+ }
+
+ public static URI loginActionUpdateProfile(URI baseUri, String realmId) {
+ return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updateProfile").build(realmId);
+ }
+
+ public static URI loginActionEmailVerification(URI baseUri, String realmId) {
+ return loginActionEmailVerificationBuilder(baseUri).build(realmId);
+ }
+
+ public static UriBuilder loginActionEmailVerificationBuilder(URI baseUri) {
+ return requiredActionsBase(baseUri).path(RequiredActionsService.class, "emailVerification");
+ }
+
+ public static URI loginPasswordReset(URI baseUri, String realmId) {
+ return loginPasswordResetBuilder(baseUri).build(realmId);
+ }
+
+ public static UriBuilder loginPasswordResetBuilder(URI baseUri) {
+ return requiredActionsBase(baseUri).path(RequiredActionsService.class, "passwordReset");
}
private static UriBuilder realmBase(URI baseUri) {
@@ -120,11 +140,15 @@ public class Urls {
.build(realmId);
}
- private static UriBuilder tokenBase(URI baseUri) {
- return realmBase(baseUri).path(RealmsResource.class, "getTokenService");
- }
-
public static URI socialRegisterAction(URI baseUri, String realmId) {
return socialBase(baseUri).path(SocialResource.class, "socialRegistration").build(realmId);
}
+
+ private static UriBuilder requiredActionsBase(URI baseUri) {
+ return tokenBase(baseUri).path(TokenService.class, "getRequiredActionsService");
+ }
+
+ private static UriBuilder tokenBase(URI baseUri) {
+ return realmBase(baseUri).path(RealmsResource.class, "getTokenService");
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index ee344b8..214a814 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -29,6 +29,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.email.EmailSender;
import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.TokenManager;
@@ -43,8 +44,8 @@ import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.*;
-import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.Providers;
+import java.util.HashSet;
import java.util.Set;
/**
@@ -75,7 +76,7 @@ public class RequiredActionsService {
this.tokenManager = tokenManager;
}
- @Path("")
+ @Path("profile")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response updateProfile(final MultivaluedMap<String, String> formData) {
@@ -98,7 +99,7 @@ public class RequiredActionsService {
@Path("totp")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
- public Response configureTotp(final MultivaluedMap<String, String> formData) {
+ public Response updateTotp(final MultivaluedMap<String, String> formData) {
AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.CONFIGURE_TOTP);
if (accessCode == null) {
return forwardToErrorPage();
@@ -129,6 +130,46 @@ public class RequiredActionsService {
return redirectOauth(user, accessCode);
}
+ @Path("password")
+ @POST
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Response updatePassword(final MultivaluedMap<String, String> formData) {
+ AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PASSWORD);
+ if (accessCode == null) {
+ return forwardToErrorPage();
+ }
+
+ UserModel user = getUser(accessCode);
+
+ 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);
+ if (Validation.isEmpty(passwordNew)) {
+ forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+ } else if (!passwordNew.equals(passwordConfirm)) {
+ forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+ }
+
+ UserCredentialModel credentials = new UserCredentialModel();
+ credentials.setType(CredentialRepresentation.PASSWORD);
+ credentials.setValue(passwordNew);
+
+ realm.updateCredential(user, credentials);
+
+ user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
+ if (accessCode != null) {
+ accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PASSWORD);
+ }
+
+ if (accessCode != null) {
+ return redirectOauth(user, accessCode);
+ } else {
+ return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
+ }
+ }
+
@Path("email-verification")
@GET
@@ -158,44 +199,59 @@ public class RequiredActionsService {
}
}
- @Path("password")
+ @Path("password-reset")
+ @GET
+ public Response passwordReset() {
+ if (uriInfo.getQueryParameters().containsKey("key")) {
+ AccessCodeEntry accessCode = tokenManager.getAccessCode(uriInfo.getQueryParameters().getFirst("key"));
+ if (accessCode == null || accessCode.isExpired()
+ || !accessCode.getRequiredActions().contains(RequiredAction.UPDATE_PASSWORD)) {
+ return forwardToErrorPage();
+ }
+ return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+ } else {
+ return Flows.forms(realm, request, uriInfo).forwardToPasswordReset();
+ }
+ }
+
+ @Path("password-reset")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
- public Response resetPassword(final MultivaluedMap<String, String> formData) {
- AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PASSWORD);
- if (accessCode == null) {
- return forwardToErrorPage();
+ public Response sendPasswordReset(final MultivaluedMap<String, String> formData) {
+ String username = formData.getFirst("username");
+ String email = formData.getFirst("email");
+
+ String scopeParam = uriInfo.getQueryParameters().getFirst("scope");
+ String state = uriInfo.getQueryParameters().getFirst("state");
+ String redirect = uriInfo.getQueryParameters().getFirst("redirect_uri");
+ String clientId = uriInfo.getQueryParameters().getFirst("client_id");
+
+ UserModel client = realm.getUser(clientId);
+ if (client == null) {
+ return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).forwardToSecurityFailure(
+ "Unknown login requester.");
+ }
+ if (!client.isEnabled()) {
+ return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).forwardToSecurityFailure(
+ "Login requester not enabled.");
}
- UserModel user = getUser(accessCode);
-
- 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);
- if (Validation.isEmpty(passwordNew)) {
- forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
- } else if (!passwordNew.equals(passwordConfirm)) {
- forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+ UserModel user = realm.getUser(username);
+ if (user == null || !email.equals(user.getEmail())) {
+ return Flows.forms(realm, request, uriInfo).setError("emailError").forwardToPasswordReset();
}
- UserCredentialModel credentials = new UserCredentialModel();
- credentials.setType(CredentialRepresentation.PASSWORD);
- credentials.setValue(passwordNew);
+ Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
+ requiredActions.add(RequiredAction.UPDATE_PASSWORD);
- realm.updateCredential(user, credentials);
+ AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
+ accessCode.setRequiredActions(requiredActions);
+ accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
- user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
- if (accessCode != null) {
- accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PASSWORD);
- }
+ new EmailSender().sendPasswordReset(user, realm, accessCode, uriInfo);
- if (accessCode != null) {
- return redirectOauth(user, accessCode);
- } else {
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
- }
+ return Flows.forms(realm, request, uriInfo).setError("emailSent").setErrorType(FormFlows.ErrorType.SUCCESS)
+ .forwardToPasswordReset();
}
private AccessCodeEntry getAccessCodeEntry(RequiredAction requiredAction) {
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 c8bfd0e..d72e80d 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -30,6 +30,7 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
@@ -75,6 +76,8 @@ public class TokenService {
@Context
protected KeycloakTransaction transaction;
+ @Context
+ protected ResourceContext resourceContext;
private ResourceAdminManager resourceAdminManager = new ResourceAdminManager();
@@ -219,7 +222,9 @@ public class TokenService {
@Path("auth/request/login-actions")
public RequiredActionsService getRequiredActionsService() {
- return new RequiredActionsService(realm, tokenManager);
+ RequiredActionsService service = new RequiredActionsService(realm, tokenManager);
+ resourceContext.initResource(service);
+ return service;
}
private void isTotpConfigurationRequired(UserModel user) {