diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java index 8a4a487ab5..281089a59b 100644 --- a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java +++ b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java @@ -37,5 +37,7 @@ public interface AccountProvider extends Provider { AccountProvider setSessions(List sessions); + AccountProvider setPasswordSet(boolean passwordSet); + AccountProvider setFeatures(boolean social, boolean events, boolean passwordUpdateSupported); } diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java index 552d59ae0a..e7a632b820 100755 --- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java +++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java @@ -8,6 +8,7 @@ import org.keycloak.account.freemarker.model.AccountSocialBean; import org.keycloak.account.freemarker.model.FeaturesBean; import org.keycloak.account.freemarker.model.LogBean; import org.keycloak.account.freemarker.model.MessageBean; +import org.keycloak.account.freemarker.model.PasswordBean; import org.keycloak.account.freemarker.model.ReferrerBean; import org.keycloak.account.freemarker.model.SessionsBean; import org.keycloak.account.freemarker.model.TotpBean; @@ -50,6 +51,7 @@ public class FreeMarkerAccountProvider implements AccountProvider { private boolean socialEnabled; private boolean eventsEnabled; private boolean passwordUpdateSupported; + private boolean passwordSet; private KeycloakSession session; private FreeMarkerUtil freeMarker; @@ -133,6 +135,8 @@ public class FreeMarkerAccountProvider implements AccountProvider { case SESSIONS: attributes.put("sessions", new SessionsBean(realm, sessions)); break; + case PASSWORD: + attributes.put("password", new PasswordBean(passwordSet)); } try { @@ -146,6 +150,11 @@ public class FreeMarkerAccountProvider implements AccountProvider { } } + public AccountProvider setPasswordSet(boolean passwordSet) { + this.passwordSet = passwordSet; + return this; + } + @Override public AccountProvider setError(String message) { this.message = message; diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java index af845d1078..e84470f951 100755 --- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java +++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java @@ -4,6 +4,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.SocialLinkModel; import org.keycloak.models.UserModel; +import org.keycloak.services.resources.AccountService; import org.keycloak.services.resources.flows.Urls; import org.keycloak.social.SocialLoader; import org.keycloak.social.SocialProvider; @@ -52,7 +53,7 @@ public class AccountSocialBean { } // Removing last social provider is not possible if you don't have other possibility to authenticate - this.removeLinkPossible = availableLinks > 1 || user.getFederationLink() != null; + this.removeLinkPossible = availableLinks > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(user); } private SocialLinkModel getSocialLink(Set userSocialLinks, String socialProviderId) { diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/PasswordBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/PasswordBean.java new file mode 100644 index 0000000000..2e2c3bdb47 --- /dev/null +++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/PasswordBean.java @@ -0,0 +1,18 @@ +package org.keycloak.account.freemarker.model; + +/** + * @author Stian Thorgersen + */ +public class PasswordBean { + + private boolean passwordSet; + + public PasswordBean(boolean passwordSet) { + this.passwordSet = passwordSet; + } + + public boolean isPasswordSet() { + return passwordSet; + } + +} diff --git a/forms/common-themes/src/main/resources/theme/account/base/password.ftl b/forms/common-themes/src/main/resources/theme/account/base/password.ftl index e5d5531e42..ccf7d0f9e8 100755 --- a/forms/common-themes/src/main/resources/theme/account/base/password.ftl +++ b/forms/common-themes/src/main/resources/theme/account/base/password.ftl @@ -11,15 +11,17 @@
-
-
- -
+ <#if password.passwordSet> +
+
+ +
-
- +
+ +
-
+
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 0900be8cb2..7e190b427a 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -43,6 +43,7 @@ import org.keycloak.models.ModelReadOnlyException; import org.keycloak.models.RealmModel; import org.keycloak.models.SocialLinkModel; import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.ModelToRepresentation; @@ -264,6 +265,10 @@ public class AccountService { @Path("password") @GET public Response passwordPage() { + if (auth != null) { + account.setPasswordSet(isPasswordSet(auth.getUser())); + } + return forwardToPage("password", AccountPages.PASSWORD); } @@ -491,29 +496,31 @@ public class AccountService { UserModel user = auth.getUser(); + boolean requireCurrent = isPasswordSet(user); + account.setPasswordSet(requireCurrent); + String password = formData.getFirst("password"); String passwordNew = formData.getFirst("password-new"); String passwordConfirm = formData.getFirst("password-confirm"); - if (Validation.isEmpty(passwordNew)) { - setReferrerOnPage(); - return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD); - } else if (!passwordNew.equals(passwordConfirm)) { - setReferrerOnPage(); - return account.setError(Messages.INVALID_PASSWORD_CONFIRM).createResponse(AccountPages.PASSWORD); - } + if (requireCurrent) { + if (Validation.isEmpty(passwordNew)) { + setReferrerOnPage(); + return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD); + } - UserCredentialModel cred = UserCredentialModel.password(password); - if (Validation.isEmpty(password)) { - setReferrerOnPage(); - return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD); - } else { + UserCredentialModel cred = UserCredentialModel.password(password); if (!session.users().validCredentials(realm, user, cred)) { setReferrerOnPage(); return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD); } } + if (!passwordNew.equals(passwordConfirm)) { + setReferrerOnPage(); + return account.setError(Messages.INVALID_PASSWORD_CONFIRM).createResponse(AccountPages.PASSWORD); + } + try { session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew)); } catch (ModelReadOnlyException mre) { @@ -528,7 +535,7 @@ public class AccountService { event.event(EventType.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success(); setReferrerOnPage(); - return account.setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD); + return account.setPasswordSet(true).setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD); } @Path("social-update") @@ -583,7 +590,7 @@ public class AccountService { if (link != null) { // Removing last social provider is not possible if you don't have other possibility to authenticate - if (session.users().getSocialLinks(user, realm).size() > 1 || user.getFederationLink() != null) { + if (session.users().getSocialLinks(user, realm).size() > 1 || user.getFederationLink() != null || isPasswordSet(user)) { session.users().removeSocialLink(realm, user, providerId); logger.debugv("Social provider {0} removed successfully from user {1}", providerId, user.getUsername()); @@ -681,6 +688,16 @@ public class AccountService { return oauth.redirect(uriInfo, accountUri.toString()); } + public static boolean isPasswordSet(UserModel user) { + boolean passwordSet = false; + for (UserCredentialValueModel c : user.getCredentialsDirectly()) { + if (c.getType().equals(CredentialRepresentation.PASSWORD)) { + passwordSet = true; + } + } + return passwordSet; + } + private String[] getReferrer() { String referrer = uriInfo.getQueryParameters().getFirst("referrer"); if (referrer == null) {