finish reg

This commit is contained in:
Bill Burke 2015-06-11 21:15:53 -04:00
parent fa99b5415d
commit 392fa21f1e
15 changed files with 418 additions and 282 deletions

View file

@ -1,124 +1,125 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<@layout.registrationLayout; section> <@layout.registrationLayout; section>
<#if section = "title"> <#if section = "title">
${msg("registerWithTitle",(realm.name!''))} ${msg("registerWithTitle",(realm.name!''))}
<#elseif section = "header"> <#elseif section = "header">
${msg("registerWithTitleHtml",(realm.name!''))} ${msg("registerWithTitleHtml",(realm.name!''))}
<#elseif section = "form"> <#elseif section = "form">
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post"> <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
<#if !realm.registrationEmailAsUsername> <#if !realm.registrationEmailAsUsername>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}"> <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label> <label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
</div> </div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<input type="text" id="username" class="${properties.kcInputClass!}" name="username" value="${(register.formData.username!'')?html}" /> <input type="text" id="username" class="${properties.kcInputClass!}" name="username" value="${(register.formData.username!'')?html}" />
</div> </div>
</div> </div>
</#if> </#if>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}"> <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcLabelWrapperClass!}">
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label> <label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
</div> </div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName" value="${(register.formData.firstName!'')?html}" /> <input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName" value="${(register.formData.firstName!'')?html}" />
</div> </div>
</div> </div>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}"> <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcLabelWrapperClass!}">
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label> <label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
</div> </div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName" value="${(register.formData.lastName!'')?html}" /> <input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName" value="${(register.formData.lastName!'')?html}" />
</div> </div>
</div> </div>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}"> <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcLabelWrapperClass!}">
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label> <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
</div> </div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<input type="text" id="email" class="${properties.kcInputClass!}" name="email" value="${(register.formData.email!'')?html}" /> <input type="text" id="email" class="${properties.kcInputClass!}" name="email" value="${(register.formData.email!'')?html}" />
</div> </div>
</div> </div>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password',properties.kcFormGroupErrorClass!)}"> <#if passwordRequired>
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password',properties.kcFormGroupErrorClass!)}">
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> <div class="${properties.kcLabelWrapperClass!}">
</div> <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
<div class="${properties.kcInputWrapperClass!}"> </div>
<input type="password" id="password" class="${properties.kcInputClass!}" name="password" /> <div class="${properties.kcInputWrapperClass!}">
</div> <input type="password" id="password" class="${properties.kcInputClass!}" name="password" />
</div> </div>
</div>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password-confirm',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password-confirm',properties.kcFormGroupErrorClass!)}">
<label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> <div class="${properties.kcLabelWrapperClass!}">
</div> <label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
<div class="${properties.kcInputWrapperClass!}"> </div>
<input type="password" id="password-confirm" class="${properties.kcInputClass!}" name="password-confirm" /> <div class="${properties.kcInputWrapperClass!}">
</div> <input type="password" id="password-confirm" class="${properties.kcInputClass!}" name="password-confirm" />
</div> </div>
</div>
<div class="form-group"> </#if>
<div class="${properties.kcLabelWrapperClass!}"> <div class="form-group">
<label for="user.attributes.street" class="${properties.kcLabelClass!}">${msg("street")}</label> <div class="${properties.kcLabelWrapperClass!}">
</div> <label for="user.attributes.street" class="${properties.kcLabelClass!}">${msg("street")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.street" name="user.attributes.street"/> <div class="${properties.kcInputWrapperClass!}">
</div> <input type="text" class="${properties.kcInputClass!}" id="user.attributes.street" name="user.attributes.street"/>
</div> </div>
<div class="form-group"> </div>
<div class="${properties.kcLabelWrapperClass!}"> <div class="form-group">
<label for="user.attributes.locality" class="${properties.kcLabelClass!}">${msg("locality")}</label> <div class="${properties.kcLabelWrapperClass!}">
</div> <label for="user.attributes.locality" class="${properties.kcLabelClass!}">${msg("locality")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.locality" name="user.attributes.locality"/> <div class="${properties.kcInputWrapperClass!}">
</div> <input type="text" class="${properties.kcInputClass!}" id="user.attributes.locality" name="user.attributes.locality"/>
</div> </div>
<div class="form-group"> </div>
<div class="${properties.kcLabelWrapperClass!}"> <div class="form-group">
<label for="user.attributes.region" class="${properties.kcLabelClass!}">${msg("region")}</label> <div class="${properties.kcLabelWrapperClass!}">
</div> <label for="user.attributes.region" class="${properties.kcLabelClass!}">${msg("region")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.region" name="user.attributes.region"/> <div class="${properties.kcInputWrapperClass!}">
</div> <input type="text" class="${properties.kcInputClass!}" id="user.attributes.region" name="user.attributes.region"/>
</div> </div>
<div class="form-group"> </div>
<div class="${properties.kcLabelWrapperClass!}"> <div class="form-group">
<label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${msg("postal_code")}</label> <div class="${properties.kcLabelWrapperClass!}">
</div> <label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${msg("postal_code")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.postal_code" name="user.attributes.postal_code"/> <div class="${properties.kcInputWrapperClass!}">
</div> <input type="text" class="${properties.kcInputClass!}" id="user.attributes.postal_code" name="user.attributes.postal_code"/>
</div> </div>
<div class="form-group"> </div>
<div class="${properties.kcLabelWrapperClass!}"> <div class="form-group">
<label for="user.attributes.country" class="${properties.kcLabelClass!}">${msg("country")}</label> <div class="${properties.kcLabelWrapperClass!}">
</div> <label for="user.attributes.country" class="${properties.kcLabelClass!}">${msg("country")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country"/> <div class="${properties.kcInputWrapperClass!}">
</div> <input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country"/>
</div> </div>
</div>
<div class="${properties.kcFormGroupClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}"> <div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcFormOptionsWrapperClass!}"> <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
<span><a href="${url.loginUrl}">${msg("backToLogin")}</a></span> <div class="${properties.kcFormOptionsWrapperClass!}">
</div> <span><a href="${url.loginUrl}">${msg("backToLogin")}</a></span>
</div> </div>
</div>
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/> <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
</div> <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
</div> </div>
</form> </div>
</#if> </form>
</#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -71,6 +71,8 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setFormData(MultivaluedMap<String, String> formData); public LoginFormsProvider setFormData(MultivaluedMap<String, String> formData);
LoginFormsProvider setAttribute(String name, Object value);
public LoginFormsProvider setStatus(Response.Status status); public LoginFormsProvider setStatus(Response.Status status);
LoginFormsProvider setActionUri(URI requestUri); LoginFormsProvider setActionUri(URI requestUri);

View file

@ -0,0 +1,36 @@
package org.keycloak.login.freemarker;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorUtil;
import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.Urls;
import java.net.URI;
import java.util.List;
/**
*/
public class AuthenticatorConfiguredMethod implements TemplateMethodModelEx {
private final RealmModel realm;
private final UserModel user;
private final KeycloakSession session;
public AuthenticatorConfiguredMethod(RealmModel realm, UserModel user, KeycloakSession session) {
this.realm = realm;
this.user = user;
this.session = session;
}
@Override
public Object exec(List list) throws TemplateModelException {
String providerId = list.get(0).toString();
Authenticator authenticator = session.getProvider(Authenticator.class, providerId);
return authenticator.configuredFor(session, realm, user);
}
}

View file

@ -3,6 +3,7 @@ package org.keycloak.login.freemarker;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.email.EmailException; import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider; import org.keycloak.email.EmailProvider;
import org.keycloak.freemarker.BrowserSecurityHeaderSetup; import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
@ -85,6 +86,7 @@ import java.util.concurrent.TimeUnit;
private UserModel user; private UserModel user;
private ClientSessionModel clientSession; private ClientSessionModel clientSession;
private final Map<String, Object> attributes = new HashMap<String, Object>();
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
this.session = session; this.session = session;
@ -160,8 +162,6 @@ import java.util.concurrent.TimeUnit;
uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode); uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
} }
Map<String, Object> attributes = new HashMap<String, Object>();
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme; Theme theme;
try { try {
@ -207,6 +207,9 @@ import java.util.concurrent.TimeUnit;
} }
URI baseUri = uriBuilder.build(); URI baseUri = uriBuilder.build();
attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri)); attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
if (realm != null && user != null && session != null) {
attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session));
}
if (realm != null) { if (realm != null) {
attributes.put("realm", new RealmBean(realm)); attributes.put("realm", new RealmBean(realm));
@ -275,7 +278,8 @@ import java.util.concurrent.TimeUnit;
} }
@Override @Override
public Response createForm(String form, Map<String, Object> attributes) { public Response createForm(String form, Map<String, Object> extraAttributes) {
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
ClientModel client = session.getContext().getClient(); ClientModel client = session.getContext().getClient();
UriInfo uriInfo = session.getContext().getUri(); UriInfo uriInfo = session.getContext().getUri();
@ -350,6 +354,9 @@ import java.util.concurrent.TimeUnit;
attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle)); attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
} }
} }
if (realm != null && user != null && session != null) {
attributes.put("authenticatorConfigured", new AuthenticatorConfiguredMethod(realm, user, session));
}
try { try {
String result = freeMarker.processTemplate(attributes, form, theme); String result = freeMarker.processTemplate(attributes, form, theme);
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result); Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
@ -393,6 +400,7 @@ import java.util.concurrent.TimeUnit;
return createResponse(LoginFormsPages.ERROR); return createResponse(LoginFormsPages.ERROR);
} }
public Response createOAuthGrant(ClientSessionModel clientSession) { public Response createOAuthGrant(ClientSessionModel clientSession) {
this.clientSession = clientSession; this.clientSession = clientSession;
return createResponse(LoginFormsPages.OAUTH_GRANT); return createResponse(LoginFormsPages.OAUTH_GRANT);
@ -475,6 +483,12 @@ import java.util.concurrent.TimeUnit;
return this; return this;
} }
@Override
public LoginFormsProvider setAttribute(String name, Object value) {
this.attributes.put(name, value);
return this;
}
@Override @Override
public LoginFormsProvider setStatus(Response.Status status) { public LoginFormsProvider setStatus(Response.Status status) {
this.status = status; this.status = status;

View file

@ -15,4 +15,5 @@ public interface Authenticator extends Provider {
boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user); boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
String getRequiredAction(); String getRequiredAction();
} }

View file

@ -0,0 +1,48 @@
package org.keycloak.authentication;
import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
import org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory;
import org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.representations.idm.CredentialRepresentation;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AuthenticatorUtil {
public static AuthenticationExecutionModel findExecutionByAuthenticator(RealmModel realm, String flowId, String authProviderId) {
for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
if (model.isAutheticatorFlow()) {
AuthenticationExecutionModel recurse = findExecutionByAuthenticator(realm, model.getAuthenticator(), authProviderId);
if (recurse != null) return recurse;
}
AuthenticatorModel authenticator = realm.getAuthenticatorById(model.getAuthenticator());
if (authenticator.getProviderId().equals(authProviderId)) {
return model;
}
}
return null;
}
public static boolean isEnabled(RealmModel realm, String flowId, String authProviderId) {
AuthenticationExecutionModel execution = findExecutionByAuthenticator(realm, flowId, authProviderId);
if (execution == null) {
return false;
}
return execution.isEnabled();
}
public static boolean isRequired(RealmModel realm, String flowId, String authProviderId) {
AuthenticationExecutionModel execution = findExecutionByAuthenticator(realm, flowId, authProviderId);
if (execution == null) {
return false;
}
return execution.isRequired();
}
}

View file

@ -95,6 +95,7 @@ public class TermsAndConditions implements RequiredActionProvider, RequiredActio
public Response invokeRequiredAction(RequiredActionContext context) { public Response invokeRequiredAction(RequiredActionContext context) {
return context.getSession().getProvider(LoginFormsProvider.class) return context.getSession().getProvider(LoginFormsProvider.class)
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
.setUser(context.getUser())
.createForm("terms.ftl", new HashMap<String, Object>()); .createForm("terms.ftl", new HashMap<String, Object>());
} }

View file

@ -33,6 +33,7 @@ public class AbstractFormAuthenticator {
code.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); code.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
URI action = getActionUrl(context, code, LOGIN_FORM_ACTION); URI action = getActionUrl(context, code, LOGIN_FORM_ACTION);
return context.getSession().getProvider(LoginFormsProvider.class) return context.getSession().getProvider(LoginFormsProvider.class)
.setUser(context.getUser())
.setActionUri(action) .setActionUri(action)
.setClientSessionCode(code.getCode()); .setClientSessionCode(code.getCode());
} }

View file

@ -27,7 +27,7 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
@Override @Override
public void authenticate(AuthenticatorContext context) { public void authenticate(AuthenticatorContext context) {
if (!isAction(context, LOGIN_FORM_ACTION)) { if (!isAction(context, LOGIN_FORM_ACTION) && !isAction(context, REGISTRATION_FORM_ACTION)) {
context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR); context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR);
return; return;
} }
@ -35,7 +35,7 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
} }
public void validatePassword(AuthenticatorContext context) { public void validatePassword(AuthenticatorContext context) {
MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters(); MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
List<UserCredentialModel> credentials = new LinkedList<>(); List<UserCredentialModel> credentials = new LinkedList<>();
String password = inputData.getFirst(CredentialRepresentation.PASSWORD); String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
if (password == null) { if (password == null) {

View file

@ -51,7 +51,6 @@ public class LoginFormUsernameAuthenticator extends AbstractFormAuthenticator im
formData.add("rememberMe", "on"); formData.add("rememberMe", "on");
} }
} }
if (loginHint != null) formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
Response challengeResponse = challenge(context, formData); Response challengeResponse = challenge(context, formData);
context.challenge(challengeResponse); context.challenge(challengeResponse);
return; return;

View file

@ -25,8 +25,11 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection; import org.keycloak.ClientConnection;
import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.AuthenticatorUtil;
import org.keycloak.authentication.RequiredActionContext; import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.authentication.authenticators.AbstractFormAuthenticator;
import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
import org.keycloak.email.EmailException; import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider; import org.keycloak.email.EmailProvider;
import org.keycloak.events.Details; import org.keycloak.events.Details;
@ -35,6 +38,7 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.login.LoginFormsProvider; import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
@ -49,10 +53,12 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.models.utils.FormMessage; import org.keycloak.models.utils.FormMessage;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.PasswordToken; import org.keycloak.representations.PasswordToken;
@ -273,6 +279,7 @@ public class LoginActionsService {
return session.getProvider(LoginFormsProvider.class) return session.getProvider(LoginFormsProvider.class)
.setClientSessionCode(clientSessionCode.getCode()) .setClientSessionCode(clientSessionCode.getCode())
.setAttribute("passwordRequired", isPasswordRequired())
.createRegistration(); .createRegistration();
} }
@ -490,14 +497,13 @@ public class LoginActionsService {
* Registration * Registration
* *
* @param code * @param code
* @param formData
* @return * @return
*/ */
@Path("request/registration") @Path("request/registration")
@POST @POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processRegister(@QueryParam("code") String code, public Response processRegister(@QueryParam("code") String code) {
final MultivaluedMap<String, String> formData) { MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
event.event(EventType.REGISTER); event.event(EventType.REGISTER);
if (!checkSsl()) { if (!checkSsl()) {
event.error(Errors.SSL_REQUIRED); event.error(Errors.SSL_REQUIRED);
@ -553,20 +559,23 @@ public class LoginActionsService {
session.getContext().setClient(client); session.getContext().setClient(client);
List<String> requiredCredentialTypes = new LinkedList<String>(); List<String> requiredCredentialTypes = new LinkedList<>();
for (RequiredCredentialModel m : realm.getRequiredCredentials()) { boolean passwordRequired = isPasswordRequired();
requiredCredentialTypes.add(m.getType()); if (passwordRequired) {
requiredCredentialTypes.add(CredentialRepresentation.PASSWORD);
} }
// Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm
List<FormMessage> errors = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes, realm.getPasswordPolicy()); List<FormMessage> errors = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes, realm.getPasswordPolicy());
if (errors != null && !errors.isEmpty()) { if (errors != null && !errors.isEmpty()) {
event.error(Errors.INVALID_REGISTRATION); event.error(Errors.INVALID_REGISTRATION);
return session.getProvider(LoginFormsProvider.class) return session.getProvider(LoginFormsProvider.class)
.setErrors(errors) .setErrors(errors)
.setFormData(formData) .setFormData(formData)
.setClientSessionCode(clientCode.getCode()) .setClientSessionCode(clientCode.getCode())
.setAttribute("passwordRequired", isPasswordRequired())
.createRegistration(); .createRegistration();
} }
@ -577,6 +586,7 @@ public class LoginActionsService {
.setError(Messages.USERNAME_EXISTS) .setError(Messages.USERNAME_EXISTS)
.setFormData(formData) .setFormData(formData)
.setClientSessionCode(clientCode.getCode()) .setClientSessionCode(clientCode.getCode())
.setAttribute("passwordRequired", isPasswordRequired())
.createRegistration(); .createRegistration();
} }
@ -587,6 +597,7 @@ public class LoginActionsService {
.setError(Messages.EMAIL_EXISTS) .setError(Messages.EMAIL_EXISTS)
.setFormData(formData) .setFormData(formData)
.setClientSessionCode(clientCode.getCode()) .setClientSessionCode(clientCode.getCode())
.setAttribute("passwordRequired", isPasswordRequired())
.createRegistration(); .createRegistration();
} }
@ -597,7 +608,7 @@ public class LoginActionsService {
user.setEmail(email); user.setEmail(email);
if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) { if (passwordRequired) {
UserCredentialModel credentials = new UserCredentialModel(); UserCredentialModel credentials = new UserCredentialModel();
credentials.setType(CredentialRepresentation.PASSWORD); credentials.setType(CredentialRepresentation.PASSWORD);
credentials.setValue(formData.getFirst("password")); credentials.setValue(formData.getFirst("password"));
@ -626,13 +637,35 @@ public class LoginActionsService {
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
} }
} }
clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
AttributeFormDataProcessor.process(formData, realm, user); AttributeFormDataProcessor.process(formData, realm, user);
event.user(user).success(); event.user(user).success();
event = new EventBuilder(realm, session, clientConnection); event = new EventBuilder(realm, session, clientConnection);
clientSession.setAuthenticatedUser(user);
AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setClientSession(clientSession)
.setFlowId(flow.getId())
.setConnection(clientConnection)
.setEventBuilder(event)
.setProtector(authManager.getProtector())
.setRealm(realm)
.setAction(AbstractFormAuthenticator.REGISTRATION_FORM_ACTION)
.setSession(session)
.setUriInfo(uriInfo)
.setRequest(request);
return processLogin(code, formData); try {
return processor.authenticate();
} catch (Exception e) {
return processor.handleBrowserException(e);
}
}
public boolean isPasswordRequired() {
AuthenticationFlowModel browserFlow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
return AuthenticatorUtil.isRequired(realm, browserFlow.getId(), LoginFormPasswordAuthenticatorFactory.PROVIDER_ID);
} }
/** /**

View file

@ -1,134 +1,134 @@
package org.keycloak.services.validation; package org.keycloak.services.validation;
import org.keycloak.models.PasswordPolicy; import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage; import org.keycloak.models.utils.FormMessage;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class Validation { public class Validation {
public static final String FIELD_PASSWORD_CONFIRM = "password-confirm"; public static final String FIELD_PASSWORD_CONFIRM = "password-confirm";
public static final String FIELD_EMAIL = "email"; public static final String FIELD_EMAIL = "email";
public static final String FIELD_LAST_NAME = "lastName"; public static final String FIELD_LAST_NAME = "lastName";
public static final String FIELD_FIRST_NAME = "firstName"; public static final String FIELD_FIRST_NAME = "firstName";
public static final String FIELD_PASSWORD = "password"; public static final String FIELD_PASSWORD = "password";
public static final String FIELD_USERNAME = "username"; public static final String FIELD_USERNAME = "username";
// Actually allow same emails like angular. See ValidationTest.testEmailValidation() // Actually allow same emails like angular. See ValidationTest.testEmailValidation()
private static final Pattern EMAIL_PATTERN = Pattern.compile("[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*"); private static final Pattern EMAIL_PATTERN = Pattern.compile("[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*");
public static List<FormMessage> validateRegistrationForm(RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes, PasswordPolicy policy) { public static List<FormMessage> validateRegistrationForm(RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes, PasswordPolicy policy) {
List<FormMessage> errors = new ArrayList<>(); List<FormMessage> errors = new ArrayList<>();
if (!realm.isRegistrationEmailAsUsername() && isBlank(formData.getFirst(FIELD_USERNAME))) { if (!realm.isRegistrationEmailAsUsername() && isBlank(formData.getFirst(FIELD_USERNAME))) {
addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME); addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
} }
if (isBlank(formData.getFirst(FIELD_FIRST_NAME))) { if (isBlank(formData.getFirst(FIELD_FIRST_NAME))) {
addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME); addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
} }
if (isBlank(formData.getFirst(FIELD_LAST_NAME))) { if (isBlank(formData.getFirst(FIELD_LAST_NAME))) {
addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME); addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME);
} }
if (isBlank(formData.getFirst(FIELD_EMAIL))) { if (isBlank(formData.getFirst(FIELD_EMAIL))) {
addError(errors, FIELD_EMAIL, Messages.MISSING_EMAIL); addError(errors, FIELD_EMAIL, Messages.MISSING_EMAIL);
} else if (!isEmailValid(formData.getFirst(FIELD_EMAIL))) { } else if (!isEmailValid(formData.getFirst(FIELD_EMAIL))) {
addError(errors, FIELD_EMAIL, Messages.INVALID_EMAIL); addError(errors, FIELD_EMAIL, Messages.INVALID_EMAIL);
} }
if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) { if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
if (isBlank(formData.getFirst(FIELD_PASSWORD))) { if (isBlank(formData.getFirst(FIELD_PASSWORD))) {
addError(errors, FIELD_PASSWORD, Messages.MISSING_PASSWORD); addError(errors, FIELD_PASSWORD, Messages.MISSING_PASSWORD);
} else if (!formData.getFirst(FIELD_PASSWORD).equals(formData.getFirst(FIELD_PASSWORD_CONFIRM))) { } else if (!formData.getFirst(FIELD_PASSWORD).equals(formData.getFirst(FIELD_PASSWORD_CONFIRM))) {
addError(errors, FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM); addError(errors, FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM);
} }
} }
if (formData.getFirst(FIELD_PASSWORD) != null) { if (formData.getFirst(FIELD_PASSWORD) != null) {
PasswordPolicy.Error err = policy.validate(realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD)); PasswordPolicy.Error err = policy.validate(realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
if (err != null) if (err != null)
errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters())); errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters()));
} }
return errors; return errors;
} }
private static void addError(List<FormMessage> errors, String field, String message){ private static void addError(List<FormMessage> errors, String field, String message){
errors.add(new FormMessage(field, message)); errors.add(new FormMessage(field, message));
} }
public static List<FormMessage> validateUpdateProfileForm(MultivaluedMap<String, String> formData) { public static List<FormMessage> validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
return validateUpdateProfileForm(null, formData); return validateUpdateProfileForm(null, formData);
} }
public static List<FormMessage> validateUpdateProfileForm(RealmModel realm, MultivaluedMap<String, String> formData) { public static List<FormMessage> validateUpdateProfileForm(RealmModel realm, MultivaluedMap<String, String> formData) {
List<FormMessage> errors = new ArrayList<>(); List<FormMessage> errors = new ArrayList<>();
if (realm != null && realm.isEditUsernameAllowed() && isBlank(formData.getFirst(FIELD_USERNAME))) { if (realm != null && realm.isEditUsernameAllowed() && isBlank(formData.getFirst(FIELD_USERNAME))) {
addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME); addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
} }
if (isBlank(formData.getFirst(FIELD_FIRST_NAME))) { if (isBlank(formData.getFirst(FIELD_FIRST_NAME))) {
addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME); addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
} }
if (isBlank(formData.getFirst(FIELD_LAST_NAME))) { if (isBlank(formData.getFirst(FIELD_LAST_NAME))) {
addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME); addError(errors, FIELD_LAST_NAME, Messages.MISSING_LAST_NAME);
} }
if (isBlank(formData.getFirst(FIELD_EMAIL))) { if (isBlank(formData.getFirst(FIELD_EMAIL))) {
addError(errors, FIELD_EMAIL, Messages.MISSING_EMAIL); addError(errors, FIELD_EMAIL, Messages.MISSING_EMAIL);
} else if (!isEmailValid(formData.getFirst(FIELD_EMAIL))) { } else if (!isEmailValid(formData.getFirst(FIELD_EMAIL))) {
addError(errors, FIELD_EMAIL, Messages.INVALID_EMAIL); addError(errors, FIELD_EMAIL, Messages.INVALID_EMAIL);
} }
return errors; return errors;
} }
/** /**
* Validate if user object contains all mandatory fields. * Validate if user object contains all mandatory fields.
* *
* @param realm user is for * @param realm user is for
* @param user to validate * @param user to validate
* @return true if user object contains all mandatory values, false if some mandatory value is missing * @return true if user object contains all mandatory values, false if some mandatory value is missing
*/ */
public static boolean validateUserMandatoryFields(RealmModel realm, UserModel user){ public static boolean validateUserMandatoryFields(RealmModel realm, UserModel user){
return!(isBlank(user.getFirstName()) || isBlank(user.getLastName()) || isBlank(user.getEmail())); return!(isBlank(user.getFirstName()) || isBlank(user.getLastName()) || isBlank(user.getEmail()));
} }
/** /**
* Check if string is empty (null or lenght is 0) * Check if string is empty (null or lenght is 0)
* *
* @param s to check * @param s to check
* @return true if string is empty * @return true if string is empty
*/ */
public static boolean isEmpty(String s) { public static boolean isEmpty(String s) {
return s == null || s.length() == 0; return s == null || s.length() == 0;
} }
/** /**
* Check if string is blank (null or lenght is 0 or contains only white characters) * Check if string is blank (null or lenght is 0 or contains only white characters)
* *
* @param s to check * @param s to check
* @return true if string is blank * @return true if string is blank
*/ */
public static boolean isBlank(String s) { public static boolean isBlank(String s) {
return s == null || s.trim().length() == 0; return s == null || s.trim().length() == 0;
} }
public static boolean isEmailValid(String email) { public static boolean isEmailValid(String email) {
return EMAIL_PATTERN.matcher(email).matches(); return EMAIL_PATTERN.matcher(email).matches();
} }
} }

View file

@ -160,7 +160,7 @@ public class RequiredActionEmailVerificationTest {
MimeMessage message = greenMail.getReceivedMessages()[0]; MimeMessage message = greenMail.getReceivedMessages()[0];
Event sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).user(userId).detail("username", "verifyEmail").detail("email", "email@mail.com").assertEvent(); Event sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).user(userId).detail("username", "verifyemail").detail("email", "email@mail.com").assertEvent();
String sessionId = sendEvent.getSessionId(); String sessionId = sendEvent.getSessionId();
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID); String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
@ -171,9 +171,9 @@ public class RequiredActionEmailVerificationTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectRequiredAction(EventType.VERIFY_EMAIL).user(userId).session(sessionId).detail("username", "verifyEmail").detail("email", "email@mail.com").detail(Details.CODE_ID, mailCodeId).assertEvent(); events.expectRequiredAction(EventType.VERIFY_EMAIL).user(userId).session(sessionId).detail("username", "verifyemail").detail("email", "email@mail.com").detail(Details.CODE_ID, mailCodeId).assertEvent();
events.expectLogin().user(userId).session(sessionId).detail("username", "verifyEmail").detail(Details.CODE_ID, mailCodeId).assertEvent(); events.expectLogin().user(userId).session(sessionId).detail("username", "verifyemail").detail(Details.CODE_ID, mailCodeId).assertEvent();
} }
@Test @Test

View file

@ -109,11 +109,11 @@ public class RequiredActionTotpSetupTest {
totpPage.configure(totp.generate(totpPage.getTotpSecret())); totpPage.configure(totp.generate(totpPage.getTotpSecret()));
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp").assertEvent().getSessionId(); String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent().getSessionId();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setupTotp").assertEvent(); events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
} }
@Test @Test
@ -165,9 +165,9 @@ public class RequiredActionTotpSetupTest {
// After totp config, user should be on the app page // After totp config, user should be on the app page
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent(); events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent(); Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
// Logout // Logout
oauth.openLogout(); oauth.openLogout();

View file

@ -137,7 +137,7 @@ public class RegisterTest {
String userId = events.expectRegister("registerPasswordPolicy", "registerPasswordPolicy@email").assertEvent().getUserId(); String userId = events.expectRegister("registerPasswordPolicy", "registerPasswordPolicy@email").assertEvent().getUserId();
events.expectLogin().user(userId).detail(Details.USERNAME, "registerPasswordPolicy").assertEvent(); events.expectLogin().user(userId).detail(Details.USERNAME, "registerpasswordpolicy").assertEvent();
} finally { } finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() { keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override @Override
@ -190,7 +190,7 @@ public class RegisterTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
String userId = events.expectRegister("registerUserSuccess", "registerUserSuccess@email").assertEvent().getUserId(); String userId = events.expectRegister("registerUserSuccess", "registerUserSuccess@email").assertEvent().getUserId();
events.expectLogin().detail("username", "registerUserSuccess").user(userId).assertEvent(); events.expectLogin().detail("username", "registerusersuccess").user(userId).assertEvent();
} }
@Test @Test
@ -250,7 +250,7 @@ public class RegisterTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
String userId = events.expectRegister("registerUserSuccessE@email", "registerUserSuccessE@email").assertEvent().getUserId(); String userId = events.expectRegister("registerUserSuccessE@email", "registerUserSuccessE@email").assertEvent().getUserId();
events.expectLogin().detail("username", "registerUserSuccessE@email").user(userId).assertEvent(); events.expectLogin().detail("username", "registerusersuccesse@email").user(userId).assertEvent();
} finally { } finally {
configureRelamRegistrationEmailAsUsername(false); configureRelamRegistrationEmailAsUsername(false);
} }