KEYCLOAK-652
Social user can't set password
This commit is contained in:
parent
fb18789b05
commit
39c0be5d99
6 changed files with 71 additions and 22 deletions
|
@ -37,5 +37,7 @@ public interface AccountProvider extends Provider {
|
||||||
|
|
||||||
AccountProvider setSessions(List<UserSessionModel> sessions);
|
AccountProvider setSessions(List<UserSessionModel> sessions);
|
||||||
|
|
||||||
|
AccountProvider setPasswordSet(boolean passwordSet);
|
||||||
|
|
||||||
AccountProvider setFeatures(boolean social, boolean events, boolean passwordUpdateSupported);
|
AccountProvider setFeatures(boolean social, boolean events, boolean passwordUpdateSupported);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.keycloak.account.freemarker.model.AccountSocialBean;
|
||||||
import org.keycloak.account.freemarker.model.FeaturesBean;
|
import org.keycloak.account.freemarker.model.FeaturesBean;
|
||||||
import org.keycloak.account.freemarker.model.LogBean;
|
import org.keycloak.account.freemarker.model.LogBean;
|
||||||
import org.keycloak.account.freemarker.model.MessageBean;
|
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.ReferrerBean;
|
||||||
import org.keycloak.account.freemarker.model.SessionsBean;
|
import org.keycloak.account.freemarker.model.SessionsBean;
|
||||||
import org.keycloak.account.freemarker.model.TotpBean;
|
import org.keycloak.account.freemarker.model.TotpBean;
|
||||||
|
@ -50,6 +51,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
||||||
private boolean socialEnabled;
|
private boolean socialEnabled;
|
||||||
private boolean eventsEnabled;
|
private boolean eventsEnabled;
|
||||||
private boolean passwordUpdateSupported;
|
private boolean passwordUpdateSupported;
|
||||||
|
private boolean passwordSet;
|
||||||
private KeycloakSession session;
|
private KeycloakSession session;
|
||||||
private FreeMarkerUtil freeMarker;
|
private FreeMarkerUtil freeMarker;
|
||||||
|
|
||||||
|
@ -133,6 +135,8 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
||||||
case SESSIONS:
|
case SESSIONS:
|
||||||
attributes.put("sessions", new SessionsBean(realm, sessions));
|
attributes.put("sessions", new SessionsBean(realm, sessions));
|
||||||
break;
|
break;
|
||||||
|
case PASSWORD:
|
||||||
|
attributes.put("password", new PasswordBean(passwordSet));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -146,6 +150,11 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccountProvider setPasswordSet(boolean passwordSet) {
|
||||||
|
this.passwordSet = passwordSet;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AccountProvider setError(String message) {
|
public AccountProvider setError(String message) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.SocialLinkModel;
|
import org.keycloak.models.SocialLinkModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.services.resources.AccountService;
|
||||||
import org.keycloak.services.resources.flows.Urls;
|
import org.keycloak.services.resources.flows.Urls;
|
||||||
import org.keycloak.social.SocialLoader;
|
import org.keycloak.social.SocialLoader;
|
||||||
import org.keycloak.social.SocialProvider;
|
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
|
// 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<SocialLinkModel> userSocialLinks, String socialProviderId) {
|
private SocialLinkModel getSocialLink(Set<SocialLinkModel> userSocialLinks, String socialProviderId) {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.keycloak.account.freemarker.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class PasswordBean {
|
||||||
|
|
||||||
|
private boolean passwordSet;
|
||||||
|
|
||||||
|
public PasswordBean(boolean passwordSet) {
|
||||||
|
this.passwordSet = passwordSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPasswordSet() {
|
||||||
|
return passwordSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action="${url.passwordUrl}" class="form-horizontal" method="post">
|
<form action="${url.passwordUrl}" class="form-horizontal" method="post">
|
||||||
|
<#if password.passwordSet>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-2 col-md-2">
|
<div class="col-sm-2 col-md-2">
|
||||||
<label for="password" class="control-label">${rb.password}</label>
|
<label for="password" class="control-label">${rb.password}</label>
|
||||||
|
@ -20,6 +21,7 @@
|
||||||
<input type="password" class="form-control" id="password" name="password" autofocus>
|
<input type="password" class="form-control" id="password" name="password" autofocus>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-2 col-md-2">
|
<div class="col-sm-2 col-md-2">
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.keycloak.models.ModelReadOnlyException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.SocialLinkModel;
|
import org.keycloak.models.SocialLinkModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
@ -264,6 +265,10 @@ public class AccountService {
|
||||||
@Path("password")
|
@Path("password")
|
||||||
@GET
|
@GET
|
||||||
public Response passwordPage() {
|
public Response passwordPage() {
|
||||||
|
if (auth != null) {
|
||||||
|
account.setPasswordSet(isPasswordSet(auth.getUser()));
|
||||||
|
}
|
||||||
|
|
||||||
return forwardToPage("password", AccountPages.PASSWORD);
|
return forwardToPage("password", AccountPages.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,29 +496,31 @@ public class AccountService {
|
||||||
|
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
|
boolean requireCurrent = isPasswordSet(user);
|
||||||
|
account.setPasswordSet(requireCurrent);
|
||||||
|
|
||||||
String password = formData.getFirst("password");
|
String password = formData.getFirst("password");
|
||||||
String passwordNew = formData.getFirst("password-new");
|
String passwordNew = formData.getFirst("password-new");
|
||||||
String passwordConfirm = formData.getFirst("password-confirm");
|
String passwordConfirm = formData.getFirst("password-confirm");
|
||||||
|
|
||||||
|
if (requireCurrent) {
|
||||||
if (Validation.isEmpty(passwordNew)) {
|
if (Validation.isEmpty(passwordNew)) {
|
||||||
setReferrerOnPage();
|
setReferrerOnPage();
|
||||||
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCredentialModel cred = UserCredentialModel.password(password);
|
UserCredentialModel cred = UserCredentialModel.password(password);
|
||||||
if (Validation.isEmpty(password)) {
|
|
||||||
setReferrerOnPage();
|
|
||||||
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
|
|
||||||
} else {
|
|
||||||
if (!session.users().validCredentials(realm, user, cred)) {
|
if (!session.users().validCredentials(realm, user, cred)) {
|
||||||
setReferrerOnPage();
|
setReferrerOnPage();
|
||||||
return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
|
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 {
|
try {
|
||||||
session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
|
session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
|
||||||
} catch (ModelReadOnlyException mre) {
|
} catch (ModelReadOnlyException mre) {
|
||||||
|
@ -528,7 +535,7 @@ public class AccountService {
|
||||||
event.event(EventType.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success();
|
event.event(EventType.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success();
|
||||||
|
|
||||||
setReferrerOnPage();
|
setReferrerOnPage();
|
||||||
return account.setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
|
return account.setPasswordSet(true).setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("social-update")
|
@Path("social-update")
|
||||||
|
@ -583,7 +590,7 @@ public class AccountService {
|
||||||
if (link != null) {
|
if (link != null) {
|
||||||
|
|
||||||
// Removing last social provider is not possible if you don't have other possibility to authenticate
|
// 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);
|
session.users().removeSocialLink(realm, user, providerId);
|
||||||
|
|
||||||
logger.debugv("Social provider {0} removed successfully from user {1}", providerId, user.getUsername());
|
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());
|
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() {
|
private String[] getReferrer() {
|
||||||
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
|
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
|
||||||
if (referrer == null) {
|
if (referrer == null) {
|
||||||
|
|
Loading…
Reference in a new issue