diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java index 250ac9fa04..eacce62a87 100755 --- a/events/api/src/main/java/org/keycloak/events/EventType.java +++ b/events/api/src/main/java/org/keycloak/events/EventType.java @@ -68,7 +68,8 @@ public enum EventType { IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR(false), IMPERSONATE(true), CUSTOM_REQUIRED_ACTION(true), - CUSTOM_REQUIRED_ACTION_ERROR(true); + CUSTOM_REQUIRED_ACTION_ERROR(true), + EXECUTE_ACTIONS(true); private boolean saveByDefault; diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index b693827a21..12c0eb46d6 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -304,7 +304,7 @@ module.controller('UserTabCtrl', function($scope, $location, Dialog, Notificatio }; }); -module.controller('UserDetailCtrl', function($scope, realm, user, BruteForceUser, User, UserFederationInstances, UserImpersonation, RequiredActions, $location, Dialog, Notifications) { +module.controller('UserDetailCtrl', function($scope, realm, user, BruteForceUser, User, UserExecuteActionsEmail, UserFederationInstances, UserImpersonation, RequiredActions, $location, Dialog, Notifications) { $scope.realm = realm; $scope.create = !user.id; $scope.editUsername = $scope.create || $scope.realm.editUsernameAllowed; @@ -389,6 +389,21 @@ module.controller('UserDetailCtrl', function($scope, realm, user, BruteForceUser } }, true); + $scope.sendExecuteActionsEmail = function() { + if ($scope.changed) { + Dialog.message("Cannot send email", "You must save your current changes before you can send an email"); + return; + } + Dialog.confirm('Send Email', 'Are you sure you want to send email to user?', function() { + UserExecuteActionsEmail.update({ realm: realm.realm, userId: user.id }, { }, function() { + Notifications.success("Email sent to user"); + }, function() { + Notifications.error("Failed to send email to user"); + }); + }); + }; + + $scope.save = function() { convertAttributeValuesToLists(); @@ -513,15 +528,6 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, User, Use }); }; - $scope.resetPasswordEmail = function() { - Dialog.confirm('Reset password email', 'Are you sure you want to send password reset email to user?', function() { - UserCredentials.resetPasswordEmail({ realm: realm.realm, userId: user.id }, { }, function() { - Notifications.success("Password reset email sent to user"); - }, function() { - Notifications.error("Failed to send password reset mail to user"); - }); - }); - }; $scope.$watch('user', function() { if (!angular.equals($scope.user, user)) { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js index c8ba5f5d1e..845ac2f4e2 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -5,7 +5,7 @@ var module = angular.module('keycloak.services', [ 'ngResource', 'ngRoute' ]); module.service('Dialog', function($modal) { var dialog = {}; - var openDialog = function(title, message, btns) { + var openDialog = function(title, message, btns, template) { var controller = function($scope, $modalInstance, title, message, btns) { $scope.title = title; $scope.message = message; @@ -20,7 +20,7 @@ module.service('Dialog', function($modal) { }; return $modal.open({ - templateUrl: resourceUrl + '/templates/kc-modal.html', + templateUrl: resourceUrl + template, controller: controller, resolve: { title: function() { @@ -56,7 +56,7 @@ module.service('Dialog', function($modal) { } } - openDialog(title, msg, btns).then(success); + openDialog(title, msg, btns, '/templates/kc-modal.html').then(success); } dialog.confirmGenerateKeys = function(name, type, success) { @@ -73,7 +73,7 @@ module.service('Dialog', function($modal) { } } - openDialog(title, msg, btns).then(success); + openDialog(title, msg, btns, '/templates/kc-modal.html').then(success); } dialog.confirm = function(title, message, success, cancel) { @@ -88,10 +88,21 @@ module.service('Dialog', function($modal) { } } - openDialog(title, message, btns).then(success, cancel); + openDialog(title, message, btns, '/templates/kc-modal.html').then(success, cancel); } - return dialog + dialog.message = function(title, message, success, cancel) { + var btns = { + ok: { + label: "Ok", + cssClass: 'btn btn-default' + } + } + + openDialog(title, message, btns, '/templates/kc-modal-message.html').then(success, cancel); + } + + return dialog }); module.service('CopyDialog', function($modal) { @@ -427,16 +438,18 @@ module.factory('UserCredentials', function($resource) { } }).update; - credentials.resetPasswordEmail = $resource(authUrl + '/admin/realms/:realm/users/:userId/reset-password-email', { + return credentials; +}); + +module.factory('UserExecuteActionsEmail', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/execute-actions-email', { realm : '@realm', userId : '@userId' }, { update : { method : 'PUT' } - }).update; - - return credentials; + }); }); module.factory('RealmRoleMapping', function($resource) { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html index ffdfd6e648..b07371a045 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-credentials.html @@ -37,14 +37,7 @@ -
-
- -
- -
-
- +
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html index feedea5a4c..e00d928c59 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html @@ -99,6 +99,15 @@
Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator.
+
+ + +
+ +
+ Sends an email to user with an embedded link. Clicking on link will allow the user to execute all their required actions. They will not have to login prior to this. For example, set the required action to update password, click this button, and the user will be able to change their password without logging in. +
+
@@ -113,10 +122,10 @@
- +
- +
Login as this user. If user is in same realm as you, your current login session will be logged out before you are logged in as this user.
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-modal-message.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-modal-message.html new file mode 100755 index 0000000000..fc3270828c --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-modal-message.html @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/html/changePassword.ftl b/forms/common-themes/src/main/resources/theme/keycloak/email/html/changePassword.ftl deleted file mode 100755 index 62037e01f8..0000000000 --- a/forms/common-themes/src/main/resources/theme/keycloak/email/html/changePassword.ftl +++ /dev/null @@ -1,5 +0,0 @@ - - -${msg("changePasswordBodyHtml",link, linkExpiration, realmName)} - - diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/html/executeActions.ftl b/forms/common-themes/src/main/resources/theme/keycloak/email/html/executeActions.ftl new file mode 100755 index 0000000000..f75e10fa2e --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/keycloak/email/html/executeActions.ftl @@ -0,0 +1,5 @@ + + +${msg("executeActionsBodyHtml",link, linkExpiration, realmName)} + + diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_de.properties index 1777078711..ca5c776aa3 100755 --- a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_de.properties +++ b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_de.properties @@ -1,10 +1,10 @@ emailVerificationSubject=E-Mail verifizieren passwordResetSubject=Passwort zur\u00FCckzusetzen -passwordResetBody=Someone just requested to change your {2} account''s password. If this was you, click on the link below to set a new password.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your password, just ignore this message and nothing will be changed. -passwordResetBodyHtml=

Someone just requested to change your {2} account''s password. If this was you, click on the link below to set a new password.

{0}

This link will expire within {1} minutes.

If you don''t want to reset your password, just ignore this message and nothing will be changed.

-changePasswordSubject=Change password -changePasswordBody=Your adminstrator has just requested that you change your {2} account''s password. Click on the link below to set a new password\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you don''t want to reset your password, just ignore this message and nothing will be changed. -changePasswordBodyHtml=

Your adminstrator has just requested that you change your {2} account''s password. Click on the link below to set a new password

{0}

This link will expire within {1} minutes.

If you don''t want to reset your password, just ignore this message and nothing will be changed.

+passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed. +passwordResetBodyHtml=

Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.

{0}

This link will expire within {1} minutes.

If you don''t want to reset your credentials, just ignore this message and nothing will be changed.

+executeActionsSubject=Update Your Account +executeActionsBody=Your adminstrator has just requested that you update your {2} account. Click on the link below to start this process.\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you are unaware that your admin has requested this, just ignore this message and nothing will be changed. +executeActionsBodyHtml=

Your adminstrator has just requested that you update your {2} account. Click on the link below to start this process.

{0}

This link will expire within {1} minutes.

If you are unaware that your admin has requested this, just ignore this message and nothing will be changed.

emailVerificationBody=Jemand hat ein {2} Konto mit dieser E-Mail Adresse erstellt. Fall das Sie waren, dann klicken Sie auf den Link um die E-Mail Adresse zu verifizieren.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie dieses Konto nicht erstellt haben, dann k\u00F6nnen sie diese Nachricht ignorieren. emailVerificationBodyHtml=

Jemand hat ein {2} Konto mit dieser E-Mail Adresse erstellt. Fall das Sie waren, dann klicken Sie auf den Link um die E-Mail Adresse zu verifizieren.

{0}

Dieser Link wird in {1} Minuten ablaufen.

Falls Sie dieses Konto nicht erstellt haben, dann k\u00F6nnen sie diese Nachricht ignorieren.

eventLoginErrorSubject=Fehlgeschlagene Anmeldung diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties index dc353e9bf8..fd9d909251 100755 --- a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties @@ -2,11 +2,11 @@ emailVerificationSubject=Verify email emailVerificationBody=Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you didn''t create this account, just ignore this message. emailVerificationBodyHtml=

Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address

{0}

This link will expire within {1} minutes.

If you didn''t create this account, just ignore this message.

passwordResetSubject=Reset password -passwordResetBody=Someone just requested to change your {2} account''s password. If this was you, click on the link below to set a new password.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your password, just ignore this message and nothing will be changed. -passwordResetBodyHtml=

Someone just requested to change your {2} account''s password. If this was you, click on the link below to set a new password.

{0}

This link will expire within {1} minutes.

If you don''t want to reset your password, just ignore this message and nothing will be changed.

-changePasswordSubject=Change password -changePasswordBody=Your adminstrator has just requested that you change your {2} account''s password. Click on the link below to set a new password\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you don''t want to reset your password, just ignore this message and nothing will be changed. -changePasswordBodyHtml=

Your adminstrator has just requested that you change your {2} account''s password. Click on the link below to set a new password

{0}

This link will expire within {1} minutes.

If you don''t want to reset your password, just ignore this message and nothing will be changed.

+passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed. +passwordResetBodyHtml=

Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.

{0}

This link will expire within {1} minutes.

If you don''t want to reset your credentials, just ignore this message and nothing will be changed.

+executeActionsSubject=Update Your Account +executeActionsBody=Your adminstrator has just requested that you update your {2} account. Click on the link below to start this process.\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you are unaware that your admin has requested this, just ignore this message and nothing will be changed. +executeActionsBodyHtml=

Your adminstrator has just requested that you update your {2} account. Click on the link below to start this process.

{0}

This link will expire within {1} minutes.

If you are unaware that your admin has requested this, just ignore this message and nothing will be changed.

eventLoginErrorSubject=Login error eventLoginErrorBody=A failed login attempt was detected to your account on {0} from {1}. If this was not you, please contact an admin. eventLoginErrorBodyHtml=

A failed login attempt was detected to your account on {0} from {1}. If this was not you, please contact an admin.

diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_pt_BR.properties index 91758c3a3d..06b4647a4c 100755 --- a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_pt_BR.properties +++ b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_pt_BR.properties @@ -2,11 +2,11 @@ emailVerificationSubject=Verifica\u00E7\u00E3o de e-mail emailVerificationBody=Algu\u00E9m criou uma conta {2} com este endere\u00E7o de e-mail. Se foi voc\u00EA, clique no link abaixo para verificar o seu endere\u00E7o de email\n\n{0}\n\nEste link ir\u00E1 expirar dentro de {1} minutos.\n\nSe n\u00E3o foi voc\u00EA que criou esta conta, basta ignorar esta mensagem. emailVerificationBodyHtml=

Algu\u00E9m criou uma conta {2} com este endere\u00E7o de e-mail. Se foi voc\u00EA, clique no link abaixo para verificar o seu endere\u00E7o de email

{0}

Este link ir\u00E1 expirar dentro de {1} minutos.

Se n\u00E3o foi voc\u00EA que criou esta conta, basta ignorar esta mensagem.

passwordResetSubject=Redefini\u00E7\u00E3o de senha -passwordResetBody=Someone just requested to change your {2} account''s password. If this was you, click on the link below to set a new password.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your password, just ignore this message and nothing will be changed. -passwordResetBodyHtml=

Someone just requested to change your {2} account''s password. If this was you, click on the link below to set a new password.

{0}

This link will expire within {1} minutes.

If you don''t want to reset your password, just ignore this message and nothing will be changed.

-changePasswordSubject=Change password -changePasswordBody=Your adminstrator has just requested that you change your {2} account''s password. Click on the link below to set a new password\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you don''t want to reset your password, just ignore this message and nothing will be changed. -changePasswordBodyHtml=

Your adminstrator has just requested that you change your {2} account''s password. Click on the link below to set a new password

{0}

This link will expire within {1} minutes.

If you don''t want to reset your password, just ignore this message and nothing will be changed.

+passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed. +passwordResetBodyHtml=

Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.

{0}

This link will expire within {1} minutes.

If you don''t want to reset your credentials, just ignore this message and nothing will be changed.

+executeActionsSubject=Update Your Account +executeActionsBody=Your adminstrator has just requested that you update your {2} account. Click on the link below to start this process.\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you are unaware that your admin has requested this, just ignore this message and nothing will be changed. +executeActionsBodyHtml=

Your adminstrator has just requested that you update your {2} account. Click on the link below to start this process.

{0}

This link will expire within {1} minutes.

If you are unaware that your admin has requested this, just ignore this message and nothing will be changed.

eventLoginErrorSubject=Erro de login eventLoginErrorBody=Uma tentativa de login mal sucedida para a sua conta foi detectada em {0} de {1}. Se n\u00E3o foi voc\u00EA, por favor, entre em contato com um administrador. eventLoginErrorBodyHtml=

Uma tentativa de login mal sucedida para a sua conta foi detectada em {0} de {1}. Se n\u00E3o foi voc\u00EA, por favor, entre em contato com um administrador.

diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/text/changePassword.ftl b/forms/common-themes/src/main/resources/theme/keycloak/email/text/changePassword.ftl deleted file mode 100755 index 6cad660005..0000000000 --- a/forms/common-themes/src/main/resources/theme/keycloak/email/text/changePassword.ftl +++ /dev/null @@ -1 +0,0 @@ -${msg("changePasswordBody",link, linkExpiration, realmName)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/text/executeActions.ftl b/forms/common-themes/src/main/resources/theme/keycloak/email/text/executeActions.ftl new file mode 100755 index 0000000000..a33758f152 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/keycloak/email/text/executeActions.ftl @@ -0,0 +1 @@ +${msg("executeActionsBody",link, linkExpiration, realmName)} \ No newline at end of file diff --git a/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java b/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java index e860bde887..2304ce56f5 100755 --- a/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java +++ b/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java @@ -19,7 +19,6 @@ public interface EmailProvider extends Provider { /** * Reset password sent from forgot password link on login * - * @param code * @param link * @param expirationInMinutes * @throws EmailException @@ -33,7 +32,7 @@ public interface EmailProvider extends Provider { * @param expirationInMinutes * @throws EmailException */ - public void sendChangePassword(String link, long expirationInMinutes) throws EmailException; + public void sendExecuteActions(String link, long expirationInMinutes) throws EmailException; public void sendVerifyEmail(String link, long expirationInMinutes) throws EmailException; diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java index e2a639cba2..b620a5968b 100755 --- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java +++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java @@ -82,7 +82,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { } @Override - public void sendChangePassword(String link, long expirationInMinutes) throws EmailException { + public void sendExecuteActions(String link, long expirationInMinutes) throws EmailException { Map attributes = new HashMap(); attributes.put("link", link); attributes.put("linkExpiration", expirationInMinutes); @@ -90,7 +90,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1); attributes.put("realmName", realmName); - send("changePasswordSubject", "changePassword.ftl", attributes); + send("executeActionsSubject", "executeActions.ftl", attributes); } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java index 7d78d34c06..1cbf8887e1 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java @@ -47,12 +47,12 @@ public interface UserResource { public void resetPassword(CredentialRepresentation credentialRepresentation); @PUT - @Path("reset-password-email") - public void resetPasswordEmail(); + @Path("execute-actions-email") + public void executeActionsEmail(); @PUT - @Path("reset-password-email") - public void resetPasswordEmail(@QueryParam("client_id") String clientId); + @Path("execute-actions-email") + public void executeActionsEmail(@QueryParam("client_id") String clientId); @PUT @Path("send-verify-email") diff --git a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java index 52705a2402..c878927d90 100755 --- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java +++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java @@ -77,11 +77,12 @@ public interface ClientSessionModel { UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD, - RECOVER_PASSWORD, + RECOVER_PASSWORD, // deprecated AUTHENTICATE, SOCIAL_CALLBACK, LOGGED_OUT, - RESET_CREDENTIALS + RESET_CREDENTIALS, + EXECUTE_ACTIONS } public enum ExecutionStatus { diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java index f2096ccf66..11755b26f9 100755 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java @@ -8,6 +8,7 @@ import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.events.Details; import org.keycloak.events.EventType; import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.UserCredentialModel; @@ -35,6 +36,13 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor } @Override public void requiredActionChallenge(RequiredActionContext context) { + // if this is EXECUTE_ACTIONS we know that the email sent is valid so we can verify it automatically + if (context.getClientSession().getNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name()) != null) { + context.getUser().setEmailVerified(true); + context.success(); + return; + } + if (Validation.isBlank(context.getUser().getEmail())) { context.ignore(); return; diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java index 57991b39c2..cb7292b6f9 100755 --- a/services/src/main/java/org/keycloak/services/Urls.java +++ b/services/src/main/java/org/keycloak/services/Urls.java @@ -156,8 +156,8 @@ public class Urls { return loginResetCredentialsBuilder(baseUri).build(realmId); } - public static UriBuilder recoverPasswordBuilder(URI baseUri) { - return loginActionsBase(baseUri).path(LoginActionsService.class, "recoverPassword"); + public static UriBuilder executeActionsBuilder(URI baseUri) { + return loginActionsBase(baseUri).path(LoginActionsService.class, "executeActions"); } public static UriBuilder loginResetCredentialsBuilder(URI baseUri) { diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index 0a8d3c6b21..86adcdc3b5 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -274,18 +274,13 @@ public class LoginActionsService { @QueryParam("execution") String execution) { event.event(EventType.LOGIN); Checks checks = new Checks(); - if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionModel.Action.RECOVER_PASSWORD.name())) { + if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name())) { return checks.response; } event.detail(Details.CODE_ID, code); ClientSessionCode clientSessionCode = checks.clientCode; ClientSessionModel clientSession = clientSessionCode.getClientSession(); - if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD.name())) { - TokenManager.dettachClientSession(session.sessions(), realm, clientSession); - clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - } - return processAuthentication(execution, clientSession, null); } @@ -568,20 +563,21 @@ public class LoginActionsService { * @param key * @return */ - @Path("recover-password") + @Path("execute-actions") @GET - public Response recoverPassword(@QueryParam("key") String key) { - event.event(EventType.RESET_PASSWORD); + public Response executeActions(@QueryParam("key") String key) { + event.event(EventType.EXECUTE_ACTIONS); if (key != null) { Checks checks = new Checks(); - if (!checks.verifyCode(key, ClientSessionModel.Action.RECOVER_PASSWORD.name())) { + if (!checks.verifyCode(key, ClientSessionModel.Action.EXECUTE_ACTIONS.name())) { return checks.response; } ClientSessionModel clientSession = checks.clientCode.getClientSession(); clientSession.setNote("END_AFTER_REQUIRED_ACTIONS", "true"); + clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true"); return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event); } else { - event.error(Errors.RESET_CREDENTIAL_DISABLED); + event.error(Errors.INVALID_CODE); return ErrorPage.error(session, Messages.INVALID_CODE); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 6d55bc2405..3158b69d69 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -834,10 +834,10 @@ public class UsersResource { * @param clientId client id * @return */ - @Path("{id}/reset-password-email") + @Path("{id}/execute-actions-email") @PUT @Consumes(MediaType.APPLICATION_JSON) - public Response resetPasswordEmail(@PathParam("id") String id, @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) { + public Response executeActionsEmail(@PathParam("id") String id, @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) { auth.requireManage(); UserModel user = session.users().getUserById(id, realm); @@ -851,17 +851,16 @@ public class UsersResource { ClientSessionModel clientSession = createClientSession(user, redirectUri, clientId); ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); - accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD.name()); + accessCode.setAction(ClientSessionModel.Action.EXECUTE_ACTIONS.name()); try { - user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - UriBuilder builder = Urls.recoverPasswordBuilder(uriInfo.getBaseUri()); + UriBuilder builder = Urls.executeActionsBuilder(uriInfo.getBaseUri()); builder.queryParam("key", accessCode.getCode()); String link = builder.build(realm.getName()).toString(); long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction()); - this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendChangePassword(link, expiration); + this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendExecuteActions(link, expiration); //audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success(); @@ -869,8 +868,8 @@ public class UsersResource { return Response.ok().build(); } catch (EmailException e) { - logger.error("Failed to send password reset email", e); - return ErrorResponse.error("Failed to send email", Response.Status.INTERNAL_SERVER_ERROR); + logger.error("Failed to send execute actions email", e); + return ErrorResponse.error("Failed to send execute actions email", Response.Status.INTERNAL_SERVER_ERROR); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java index aa5c3925a1..25a98a0b8b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java @@ -361,7 +361,7 @@ public class UserTest extends AbstractClientTest { UserResource user = realm.users().get(id); try { - user.resetPasswordEmail(); + user.executeActionsEmail(); fail("Expected failure"); } catch (ClientErrorException e) { assertEquals(400, e.getResponse().getStatus()); @@ -374,7 +374,7 @@ public class UserTest extends AbstractClientTest { userRep.setEmail("user1@localhost"); userRep.setEnabled(false); user.update(userRep); - user.resetPasswordEmail(); + user.executeActionsEmail(); fail("Expected failure"); } catch (ClientErrorException e) { assertEquals(400, e.getResponse().getStatus()); @@ -385,7 +385,7 @@ public class UserTest extends AbstractClientTest { try { userRep.setEnabled(true); user.update(userRep); - user.resetPasswordEmail("invalidClientId"); + user.executeActionsEmail("invalidClientId"); fail("Expected failure"); } catch (ClientErrorException e) { assertEquals(400, e.getResponse().getStatus());