form action refactor
This commit is contained in:
parent
a1c612f833
commit
39aa09ca36
30 changed files with 554 additions and 504 deletions
|
@ -13,6 +13,7 @@ kerberosNotConfigured=Kerberos Not Configured
|
||||||
kerberosNotConfiguredTitle=Kerberos Not Configured
|
kerberosNotConfiguredTitle=Kerberos Not Configured
|
||||||
bypassKerberos=Your browser is not set up for Kerberos login. Please click continue to login in through other means
|
bypassKerberos=Your browser is not set up for Kerberos login. Please click continue to login in through other means
|
||||||
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
||||||
|
recaptchaFailed=Recaptcha Failed
|
||||||
|
|
||||||
registerWithTitle=Registrierung bei {0}
|
registerWithTitle=Registrierung bei {0}
|
||||||
registerWithTitleHtml=Registrierung bei <strong>{0}</strong>
|
registerWithTitleHtml=Registrierung bei <strong>{0}</strong>
|
||||||
|
|
|
@ -30,6 +30,7 @@ codeSuccessTitle=Success code
|
||||||
codeErrorTitle=Error code\: {0}
|
codeErrorTitle=Error code\: {0}
|
||||||
termsTitle=Terms and Conditions
|
termsTitle=Terms and Conditions
|
||||||
termsTitleHtml=Terms and Conditions
|
termsTitleHtml=Terms and Conditions
|
||||||
|
recaptchaFailed=Recaptcha Failed
|
||||||
|
|
||||||
noAccount=New user?
|
noAccount=New user?
|
||||||
username=Username
|
username=Username
|
||||||
|
|
|
@ -13,6 +13,7 @@ bypassKerberos=Your browser is not set up for Kerberos login. Please click cont
|
||||||
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
||||||
kerberosNotConfigured=Kerberos Not Configured
|
kerberosNotConfigured=Kerberos Not Configured
|
||||||
kerberosNotConfiguredTitle=Kerberos Not Configured
|
kerberosNotConfiguredTitle=Kerberos Not Configured
|
||||||
|
recaptchaFailed=Recaptcha Failed
|
||||||
|
|
||||||
registerWithTitle=Registrati come {0}
|
registerWithTitle=Registrati come {0}
|
||||||
registerWithTitleHtml=Registrati come <strong>{0}</strong>
|
registerWithTitleHtml=Registrati come <strong>{0}</strong>
|
||||||
|
|
|
@ -13,6 +13,7 @@ bypassKerberos=Your browser is not set up for Kerberos login. Please click cont
|
||||||
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
||||||
kerberosNotConfigured=Kerberos Not Configured
|
kerberosNotConfigured=Kerberos Not Configured
|
||||||
kerberosNotConfiguredTitle=Kerberos Not Configured
|
kerberosNotConfiguredTitle=Kerberos Not Configured
|
||||||
|
recaptchaFailed=Recaptcha Failed
|
||||||
|
|
||||||
registerWithTitle=Registre-se com {0}
|
registerWithTitle=Registre-se com {0}
|
||||||
registerWithTitleHtml=Registre-se com <strong>{0}</strong>
|
registerWithTitleHtml=Registre-se com <strong>{0}</strong>
|
||||||
|
|
|
@ -107,7 +107,13 @@
|
||||||
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country"/>
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<#if recaptchaRequired??>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<div class="g-recaptcha" data-sitekey="${recaptchaSiteKey}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
<div class="${properties.kcFormGroupClass!}">
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||||
|
|
5
forms/common-themes/src/main/resources/theme/base/login/template.ftl
Normal file → Executable file
5
forms/common-themes/src/main/resources/theme/base/login/template.ftl
Normal file → Executable file
|
@ -21,6 +21,11 @@
|
||||||
<script src="${url.resourcesPath}/${script}" type="text/javascript"></script>
|
<script src="${url.resourcesPath}/${script}" type="text/javascript"></script>
|
||||||
</#list>
|
</#list>
|
||||||
</#if>
|
</#if>
|
||||||
|
<#if scripts??>
|
||||||
|
<#list scripts as script>
|
||||||
|
<script src="${script}" type="text/javascript"></script>
|
||||||
|
</#list>
|
||||||
|
</#if>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="${properties.kcBodyClass!}">
|
<body class="${properties.kcBodyClass!}">
|
||||||
|
|
|
@ -63,6 +63,8 @@ public interface LoginFormsProvider extends Provider {
|
||||||
*/
|
*/
|
||||||
public LoginFormsProvider setErrors(List<FormMessage> messages);
|
public LoginFormsProvider setErrors(List<FormMessage> messages);
|
||||||
|
|
||||||
|
LoginFormsProvider addError(FormMessage errorMessage);
|
||||||
|
|
||||||
public LoginFormsProvider setSuccess(String message, Object ... parameters);
|
public LoginFormsProvider setSuccess(String message, Object ... parameters);
|
||||||
|
|
||||||
public LoginFormsProvider setUser(UserModel user);
|
public LoginFormsProvider setUser(UserModel user);
|
||||||
|
|
|
@ -3,7 +3,6 @@ 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;
|
||||||
|
@ -39,8 +38,8 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
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.services.messages.Messages;
|
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
|
import org.keycloak.services.messages.Messages;
|
||||||
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
@ -52,6 +51,7 @@ import java.net.URI;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -445,11 +445,27 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginFormsProvider setErrors(List<FormMessage> messages) {
|
public LoginFormsProvider setErrors(List<FormMessage> messages) {
|
||||||
|
if (messages == null) return this;
|
||||||
this.messageType = MessageType.ERROR;
|
this.messageType = MessageType.ERROR;
|
||||||
this.messages = new ArrayList<>(messages);
|
this.messages = new ArrayList<>(messages);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoginFormsProvider addError(FormMessage errorMessage) {
|
||||||
|
if (this.messageType != MessageType.ERROR) {
|
||||||
|
this.messageType = null;
|
||||||
|
this.messages = null;
|
||||||
|
}
|
||||||
|
if (messages == null) {
|
||||||
|
this.messageType = MessageType.ERROR;
|
||||||
|
this.messages = new LinkedList<>();
|
||||||
|
}
|
||||||
|
this.messages.add(errorMessage);
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FreeMarkerLoginFormsProvider setSuccess(String message, Object... parameters) {
|
public FreeMarkerLoginFormsProvider setSuccess(String message, Object... parameters) {
|
||||||
setMessage(MessageType.SUCCESS, message, parameters);
|
setMessage(MessageType.SUCCESS, message, parameters);
|
||||||
|
|
|
@ -34,7 +34,9 @@ public class DefaultAuthenticationFlows {
|
||||||
registrationFormFlow.setProviderId("form-flow");
|
registrationFormFlow.setProviderId("form-flow");
|
||||||
registrationFormFlow = realm.addAuthenticationFlow(registrationFormFlow);
|
registrationFormFlow = realm.addAuthenticationFlow(registrationFormFlow);
|
||||||
|
|
||||||
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
AuthenticationExecutionModel execution;
|
||||||
|
|
||||||
|
execution = new AuthenticationExecutionModel();
|
||||||
execution.setParentFlow(registrationFlow.getId());
|
execution.setParentFlow(registrationFlow.getId());
|
||||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
execution.setAuthenticator("registration-page-form");
|
execution.setAuthenticator("registration-page-form");
|
||||||
|
@ -47,7 +49,7 @@ public class DefaultAuthenticationFlows {
|
||||||
execution = new AuthenticationExecutionModel();
|
execution = new AuthenticationExecutionModel();
|
||||||
execution.setParentFlow(registrationFormFlow.getId());
|
execution.setParentFlow(registrationFormFlow.getId());
|
||||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
execution.setAuthenticator("username-validation-action");
|
execution.setAuthenticator("registration-user-creation");
|
||||||
execution.setPriority(20);
|
execution.setPriority(20);
|
||||||
execution.setUserSetupAllowed(false);
|
execution.setUserSetupAllowed(false);
|
||||||
execution.setAutheticatorFlow(false);
|
execution.setAutheticatorFlow(false);
|
||||||
|
@ -56,16 +58,7 @@ public class DefaultAuthenticationFlows {
|
||||||
execution = new AuthenticationExecutionModel();
|
execution = new AuthenticationExecutionModel();
|
||||||
execution.setParentFlow(registrationFormFlow.getId());
|
execution.setParentFlow(registrationFormFlow.getId());
|
||||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
execution.setAuthenticator("profile-validation-action");
|
execution.setAuthenticator("registration-profile-action");
|
||||||
execution.setPriority(30);
|
|
||||||
execution.setUserSetupAllowed(false);
|
|
||||||
execution.setAutheticatorFlow(false);
|
|
||||||
realm.addAuthenticatorExecution(execution);
|
|
||||||
|
|
||||||
execution = new AuthenticationExecutionModel();
|
|
||||||
execution.setParentFlow(registrationFormFlow.getId());
|
|
||||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
|
||||||
execution.setAuthenticator("password-validation-action");
|
|
||||||
execution.setPriority(40);
|
execution.setPriority(40);
|
||||||
execution.setUserSetupAllowed(false);
|
execution.setUserSetupAllowed(false);
|
||||||
execution.setAutheticatorFlow(false);
|
execution.setAutheticatorFlow(false);
|
||||||
|
@ -74,12 +67,22 @@ public class DefaultAuthenticationFlows {
|
||||||
execution = new AuthenticationExecutionModel();
|
execution = new AuthenticationExecutionModel();
|
||||||
execution.setParentFlow(registrationFormFlow.getId());
|
execution.setParentFlow(registrationFormFlow.getId());
|
||||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
execution.setAuthenticator("registration-user-creation");
|
execution.setAuthenticator("registration-password-action");
|
||||||
execution.setPriority(50);
|
execution.setPriority(50);
|
||||||
execution.setUserSetupAllowed(false);
|
execution.setUserSetupAllowed(false);
|
||||||
execution.setAutheticatorFlow(false);
|
execution.setAutheticatorFlow(false);
|
||||||
realm.addAuthenticatorExecution(execution);
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
|
execution = new AuthenticationExecutionModel();
|
||||||
|
execution.setParentFlow(registrationFormFlow.getId());
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
|
||||||
|
execution.setAuthenticator("registration-recaptcha-action");
|
||||||
|
execution.setPriority(60);
|
||||||
|
execution.setUserSetupAllowed(false);
|
||||||
|
execution.setAutheticatorFlow(false);
|
||||||
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -227,7 +227,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAuthenticatedUser(UserModel user) {
|
public void setAuthenticatedUser(UserModel user) {
|
||||||
entity.setAuthUserId(user.getId());
|
if (user == null) entity.setAuthUserId(null);
|
||||||
|
else entity.setAuthUserId(user.getId());
|
||||||
update();
|
update();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,6 +306,7 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAuthenticatedUser(UserModel user) {
|
public void setAuthenticatedUser(UserModel user) {
|
||||||
entity.setUserId(user.getId());
|
if (user == null) entity.setUserId(null);
|
||||||
|
else entity.setUserId(user.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,7 +193,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAuthenticatedUser(UserModel user) {
|
public void setAuthenticatedUser(UserModel user) {
|
||||||
entity.setAuthUserId(user.getId());
|
if (user == null) entity.setAuthUserId(null);
|
||||||
|
else entity.setAuthUserId(user.getId());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,7 +210,8 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAuthenticatedUser(UserModel user) {
|
public void setAuthenticatedUser(UserModel user) {
|
||||||
entity.setAuthUserId(user.getId());
|
if (user == null) entity.setAuthUserId(null);
|
||||||
|
else entity.setAuthUserId(user.getId());
|
||||||
updateMongoEntity();
|
updateMongoEntity();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,6 @@ public class AuthenticationProcessor {
|
||||||
protected EventBuilder event;
|
protected EventBuilder event;
|
||||||
protected HttpRequest request;
|
protected HttpRequest request;
|
||||||
protected String flowId;
|
protected String flowId;
|
||||||
protected String action;
|
|
||||||
/**
|
/**
|
||||||
* This could be an error message forwarded from brokering when the broker failed authentication
|
* This could be an error message forwarded from brokering when the broker failed authentication
|
||||||
* and we want to continue authentication locally. forwardedErrorMessage can then be displayed by
|
* and we want to continue authentication locally. forwardedErrorMessage can then be displayed by
|
||||||
|
@ -151,16 +150,39 @@ public class AuthenticationProcessor {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticationProcessor setAction(String action) {
|
|
||||||
this.action = action;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthenticationProcessor setForwardedErrorMessage(String forwardedErrorMessage) {
|
public AuthenticationProcessor setForwardedErrorMessage(String forwardedErrorMessage) {
|
||||||
this.forwardedErrorMessage = forwardedErrorMessage;
|
this.forwardedErrorMessage = forwardedErrorMessage;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String generateCode() {
|
||||||
|
ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
|
||||||
|
clientSession.setTimestamp(Time.currentTime());
|
||||||
|
return accessCode.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventBuilder newEvent() {
|
||||||
|
this.event = new EventBuilder(realm, session, connection);
|
||||||
|
return this.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventBuilder getEvent() {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpRequest getRequest() {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutheticatedUser(UserModel user) {
|
||||||
|
UserModel previousUser = clientSession.getAuthenticatedUser();
|
||||||
|
if (previousUser != null && !user.getId().equals(previousUser.getId()))
|
||||||
|
throw new AuthException(Error.USER_CONFLICT);
|
||||||
|
validateUser(user);
|
||||||
|
getClientSession().setAuthenticatedUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private class Result implements AuthenticatorContext {
|
private class Result implements AuthenticatorContext {
|
||||||
AuthenticatorConfigModel authenticatorConfig;
|
AuthenticatorConfigModel authenticatorConfig;
|
||||||
AuthenticationExecutionModel execution;
|
AuthenticationExecutionModel execution;
|
||||||
|
@ -178,8 +200,7 @@ public class AuthenticationProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EventBuilder newEvent() {
|
public EventBuilder newEvent() {
|
||||||
AuthenticationProcessor.this.event = new EventBuilder(realm, session, connection);
|
return AuthenticationProcessor.this.newEvent();
|
||||||
return AuthenticationProcessor.this.event;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -213,11 +234,6 @@ public class AuthenticationProcessor {
|
||||||
return authenticatorConfig;
|
return authenticatorConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAction() {
|
|
||||||
return AuthenticationProcessor.this.action;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authenticator getAuthenticator() {
|
public Authenticator getAuthenticator() {
|
||||||
return authenticator;
|
return authenticator;
|
||||||
|
@ -288,11 +304,7 @@ public class AuthenticationProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUser(UserModel user) {
|
public void setUser(UserModel user) {
|
||||||
UserModel previousUser = getUser();
|
setAutheticatedUser(user);
|
||||||
if (previousUser != null && !user.getId().equals(previousUser.getId()))
|
|
||||||
throw new AuthException(Error.USER_CONFLICT);
|
|
||||||
validateUser(user);
|
|
||||||
getClientSession().setAuthenticatedUser(user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -347,11 +359,10 @@ public class AuthenticationProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateAccessCode() {
|
public String generateAccessCode() {
|
||||||
ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
|
return generateCode();
|
||||||
clientSession.setTimestamp(Time.currentTime());
|
|
||||||
return accessCode.getCode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response getChallenge() {
|
public Response getChallenge() {
|
||||||
return challenge;
|
return challenge;
|
||||||
|
|
|
@ -27,16 +27,6 @@ public interface AuthenticatorContext {
|
||||||
|
|
||||||
void setExecution(AuthenticationExecutionModel execution);
|
void setExecution(AuthenticationExecutionModel execution);
|
||||||
|
|
||||||
AuthenticatorConfigModel getAuthenticatorConfig();
|
|
||||||
|
|
||||||
String getAction();
|
|
||||||
|
|
||||||
Authenticator getAuthenticator();
|
|
||||||
|
|
||||||
void setAuthenticator(Authenticator authenticator);
|
|
||||||
|
|
||||||
AuthenticationProcessor.Status getStatus();
|
|
||||||
|
|
||||||
UserModel getUser();
|
UserModel getUser();
|
||||||
|
|
||||||
void setUser(UserModel user);
|
void setUser(UserModel user);
|
||||||
|
@ -55,17 +45,6 @@ public interface AuthenticatorContext {
|
||||||
HttpRequest getHttpRequest();
|
HttpRequest getHttpRequest();
|
||||||
BruteForceProtector getProtector();
|
BruteForceProtector getProtector();
|
||||||
|
|
||||||
AuthenticationExecutionModel.Requirement getCategoryRequirementFromCurrentFlow(String authenticatorCategory);
|
|
||||||
|
|
||||||
void success();
|
|
||||||
void failure(AuthenticationProcessor.Error error);
|
|
||||||
void failure(AuthenticationProcessor.Error error, Response response);
|
|
||||||
void challenge(Response challenge);
|
|
||||||
|
|
||||||
void forceChallenge(Response challenge);
|
|
||||||
|
|
||||||
void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
|
|
||||||
void attempted();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This could be an error message forwarded from brokering when the broker failed authentication
|
* This could be an error message forwarded from brokering when the broker failed authentication
|
||||||
|
@ -81,6 +60,27 @@ public interface AuthenticatorContext {
|
||||||
*/
|
*/
|
||||||
String generateAccessCode();
|
String generateAccessCode();
|
||||||
|
|
||||||
|
AuthenticatorConfigModel getAuthenticatorConfig();
|
||||||
|
|
||||||
|
Authenticator getAuthenticator();
|
||||||
|
|
||||||
|
void setAuthenticator(Authenticator authenticator);
|
||||||
|
|
||||||
|
AuthenticationProcessor.Status getStatus();
|
||||||
|
|
||||||
|
AuthenticationExecutionModel.Requirement getCategoryRequirementFromCurrentFlow(String authenticatorCategory);
|
||||||
|
|
||||||
|
void success();
|
||||||
|
void failure(AuthenticationProcessor.Error error);
|
||||||
|
void failure(AuthenticationProcessor.Error error, Response response);
|
||||||
|
void challenge(Response challenge);
|
||||||
|
|
||||||
|
void forceChallenge(Response challenge);
|
||||||
|
|
||||||
|
void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
|
||||||
|
void attempted();
|
||||||
|
|
||||||
|
|
||||||
Response getChallenge();
|
Response getChallenge();
|
||||||
|
|
||||||
AuthenticationProcessor.Error getError();
|
AuthenticationProcessor.Error getError();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.authentication;
|
package org.keycloak.authentication;
|
||||||
|
|
||||||
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -10,7 +11,8 @@ import org.keycloak.provider.Provider;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface FormAction extends Provider {
|
public interface FormAction extends Provider {
|
||||||
void authenticate(FormActionContext context);
|
void validate(ValidationContext context);
|
||||||
|
void success(FormContext context);
|
||||||
|
|
||||||
boolean requiresUser();
|
boolean requiresUser();
|
||||||
boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
|
boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
|
||||||
|
@ -21,4 +23,6 @@ public interface FormAction extends Provider {
|
||||||
*/
|
*/
|
||||||
void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
|
void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
|
||||||
|
|
||||||
|
void buildPage(FormContext context, LoginFormsProvider form);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package org.keycloak.authentication;
|
|
||||||
|
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public interface FormActionContext extends AuthenticatorContext {
|
|
||||||
FormAuthenticator getFormAuthenticator();
|
|
||||||
AuthenticationExecutionModel getFormExecution();
|
|
||||||
}
|
|
|
@ -2,7 +2,9 @@ package org.keycloak.authentication;
|
||||||
|
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorConfigModel;
|
import org.keycloak.models.AuthenticatorConfigModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
@ -10,10 +12,14 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
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.FormMessage;
|
||||||
import org.keycloak.services.managers.BruteForceProtector;
|
import org.keycloak.services.managers.BruteForceProtector;
|
||||||
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -37,183 +43,99 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
|
||||||
formAuthenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator());
|
formAuthenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator());
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FormContext implements FormActionContext {
|
private class FormContextImpl implements FormContext {
|
||||||
protected AuthenticatorContext delegate;
|
AuthenticationExecutionModel executionModel;
|
||||||
|
AuthenticatorConfigModel authenticatorConfig;
|
||||||
|
|
||||||
private FormContext(AuthenticatorContext delegate) {
|
private FormContextImpl(AuthenticationExecutionModel executionModel) {
|
||||||
this.delegate = delegate;
|
this.executionModel = executionModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EventBuilder newEvent() {
|
public EventBuilder newEvent() {
|
||||||
return delegate.newEvent();
|
return processor.newEvent();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FormAuthenticator getFormAuthenticator() {
|
|
||||||
return formAuthenticator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthenticationExecutionModel getFormExecution() {
|
|
||||||
return formExecution;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EventBuilder getEvent() {
|
public EventBuilder getEvent() {
|
||||||
return delegate.getEvent();
|
return processor.getEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationExecutionModel getExecution() {
|
public AuthenticationExecutionModel getExecution() {
|
||||||
return delegate.getExecution();
|
return executionModel;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setExecution(AuthenticationExecutionModel execution) {
|
|
||||||
delegate.setExecution(execution);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticatorConfigModel getAuthenticatorConfig() {
|
public AuthenticatorConfigModel getAuthenticatorConfig() {
|
||||||
return delegate.getAuthenticatorConfig();
|
if (executionModel.getAuthenticatorConfig() == null) return null;
|
||||||
}
|
if (authenticatorConfig != null) return authenticatorConfig;
|
||||||
|
authenticatorConfig = getRealm().getAuthenticatorConfigById(executionModel.getAuthenticatorConfig());
|
||||||
@Override
|
return authenticatorConfig;
|
||||||
public String getAction() {
|
|
||||||
return delegate.getAction();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authenticator getAuthenticator() {
|
|
||||||
return delegate.getAuthenticator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAuthenticator(Authenticator authenticator) {
|
|
||||||
delegate.setAuthenticator(authenticator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthenticationProcessor.Status getStatus() {
|
|
||||||
return delegate.getStatus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUser() {
|
public UserModel getUser() {
|
||||||
return delegate.getUser();
|
return getClientSession().getAuthenticatedUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUser(UserModel user) {
|
public void setUser(UserModel user) {
|
||||||
delegate.setUser(user);
|
processor.setAutheticatedUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RealmModel getRealm() {
|
public RealmModel getRealm() {
|
||||||
return delegate.getRealm();
|
return processor.getRealm();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientSessionModel getClientSession() {
|
public ClientSessionModel getClientSession() {
|
||||||
return delegate.getClientSession();
|
return processor.getClientSession();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void attachUserSession(UserSessionModel userSession) {
|
|
||||||
delegate.attachUserSession(userSession);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientConnection getConnection() {
|
public ClientConnection getConnection() {
|
||||||
return delegate.getConnection();
|
return processor.getConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UriInfo getUriInfo() {
|
public UriInfo getUriInfo() {
|
||||||
return delegate.getUriInfo();
|
return processor.getUriInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeycloakSession getSession() {
|
public KeycloakSession getSession() {
|
||||||
return delegate.getSession();
|
return processor.getSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpRequest getHttpRequest() {
|
public HttpRequest getHttpRequest() {
|
||||||
return delegate.getHttpRequest();
|
return processor.getRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ValidationContextImpl extends FormContextImpl implements ValidationContext {
|
||||||
|
FormAction action;
|
||||||
|
|
||||||
|
private ValidationContextImpl(AuthenticationExecutionModel executionModel, FormAction action) {
|
||||||
|
super(executionModel);
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success;
|
||||||
|
List<FormMessage> errors = null;
|
||||||
|
MultivaluedMap<String, String> formData = null;
|
||||||
@Override
|
@Override
|
||||||
public BruteForceProtector getProtector() {
|
public void validationError(MultivaluedMap<String, String> formData, List<FormMessage> errors) {
|
||||||
return delegate.getProtector();
|
this.errors = errors;
|
||||||
}
|
this.formData = formData;
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthenticationExecutionModel.Requirement getCategoryRequirementFromCurrentFlow(String authenticatorCategory) {
|
|
||||||
for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
|
|
||||||
FormActionFactory factory = (FormActionFactory) getSession().getKeycloakSessionFactory().getProviderFactory(FormAction.class, formActionExecution.getAuthenticator());
|
|
||||||
if (factory != null && authenticatorCategory.equals(factory.getReferenceCategory())) {
|
|
||||||
return formActionExecution.getRequirement();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void success() {
|
public void success() {
|
||||||
delegate.success();
|
success = true;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failure(AuthenticationProcessor.Error error) {
|
|
||||||
delegate.failure(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failure(AuthenticationProcessor.Error error, Response response) {
|
|
||||||
delegate.failure(error, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void challenge(Response challenge) {
|
|
||||||
delegate.challenge(challenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void forceChallenge(Response challenge) {
|
|
||||||
delegate.forceChallenge(challenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failureChallenge(AuthenticationProcessor.Error error, Response challenge) {
|
|
||||||
delegate.failureChallenge(error, challenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void attempted() {
|
|
||||||
delegate.attempted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getForwardedErrorMessage() {
|
|
||||||
return delegate.getForwardedErrorMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String generateAccessCode() {
|
|
||||||
return delegate.generateAccessCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response getChallenge() {
|
|
||||||
return delegate.getChallenge();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthenticationProcessor.Error getError() {
|
|
||||||
return delegate.getError();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +146,12 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
|
||||||
}
|
}
|
||||||
Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
|
Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
|
||||||
List<FormAction> requiredActions = new LinkedList<>();
|
List<FormAction> requiredActions = new LinkedList<>();
|
||||||
|
List<ValidationContextImpl> successes = new LinkedList<>();
|
||||||
for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
|
for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
|
||||||
|
if (!formActionExecution.isEnabled()) {
|
||||||
|
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
FormAction action = processor.getSession().getProvider(FormAction.class, formActionExecution.getAuthenticator());
|
FormAction action = processor.getSession().getProvider(FormAction.class, formActionExecution.getAuthenticator());
|
||||||
|
|
||||||
UserModel authUser = processor.getClientSession().getAuthenticatedUser();
|
UserModel authUser = processor.getClientSession().getAuthenticatedUser();
|
||||||
|
@ -251,12 +178,20 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticatorContext delegate = processor.createAuthenticatorContext(formActionExecution, null, formActionExecutions);
|
ValidationContextImpl result = new ValidationContextImpl(formActionExecution, action);
|
||||||
FormActionContext result = new FormContext(delegate);
|
action.validate(result);
|
||||||
action.authenticate(result);
|
if (result.success) {
|
||||||
Response challenge = processResult(executionStatus, result, formActionExecution);
|
|
||||||
if (challenge != null) return challenge;
|
|
||||||
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
|
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
|
||||||
|
successes.add(result);
|
||||||
|
} else {
|
||||||
|
processor.logFailure();
|
||||||
|
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
||||||
|
return renderForm(result.formData, result.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ValidationContextImpl context : successes) {
|
||||||
|
context.action.success(context);
|
||||||
}
|
}
|
||||||
// set status and required actions only if form is fully successful
|
// set status and required actions only if form is fully successful
|
||||||
for (Map.Entry<String, ClientSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
|
for (Map.Entry<String, ClientSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
|
||||||
|
@ -270,63 +205,36 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public URI getActionUrl(String executionId, String code) {
|
||||||
|
return LoginActionsService.registrationFormProcessor(processor.getUriInfo())
|
||||||
|
.queryParam(OAuth2Constants.CODE, code)
|
||||||
|
.queryParam("execution", executionId)
|
||||||
|
.build(processor.getRealm().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response processFlow() {
|
public Response processFlow() {
|
||||||
AuthenticatorContext delegate = processor.createAuthenticatorContext(formExecution, null, formActionExecutions);
|
return renderForm(null, null);
|
||||||
FormActionContext result = new FormContext(delegate);
|
|
||||||
formAuthenticator.authenticate(result);
|
|
||||||
Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
|
|
||||||
Response response = processResult(executionStatus, result, formExecution);
|
|
||||||
for (Map.Entry<String, ClientSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
|
|
||||||
processor.getClientSession().setExecutionStatus(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Response renderForm(MultivaluedMap<String, String> formData, List<FormMessage> errors) {
|
||||||
public Response processResult(Map<String, ClientSessionModel.ExecutionStatus> executionStatus, AuthenticatorContext result, AuthenticationExecutionModel execution) {
|
String executionId = formExecution.getId();
|
||||||
AuthenticationProcessor.Status status = result.getStatus();
|
processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, executionId);
|
||||||
if (status == AuthenticationProcessor.Status.SUCCESS) {
|
String code = processor.generateCode();
|
||||||
executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
|
URI actionUrl = getActionUrl(executionId, code);
|
||||||
return null;
|
LoginFormsProvider form = processor.getSession().getProvider(LoginFormsProvider.class)
|
||||||
} else if (status == AuthenticationProcessor.Status.FAILED) {
|
.setActionUri(actionUrl)
|
||||||
AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
|
.setClientSessionCode(code)
|
||||||
processor.logFailure();
|
.setFormData(formData)
|
||||||
executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
|
.setErrors(errors);
|
||||||
if (result.getChallenge() != null) {
|
for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
|
||||||
return sendChallenge(result);
|
if (!formActionExecution.isEnabled()) continue;
|
||||||
|
FormAction action = processor.getSession().getProvider(FormAction.class, formActionExecution.getAuthenticator());
|
||||||
|
FormContext result = new FormContextImpl(formActionExecution);
|
||||||
|
action.buildPage(result, form);
|
||||||
}
|
}
|
||||||
throw new AuthenticationProcessor.AuthException(result.getError());
|
FormContext context = new FormContextImpl(formExecution);
|
||||||
} else if (status == AuthenticationProcessor.Status.FORCE_CHALLENGE) {
|
return formAuthenticator.render(context, form);
|
||||||
executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
|
||||||
return sendChallenge(result);
|
|
||||||
} else if (status == AuthenticationProcessor.Status.CHALLENGE) {
|
|
||||||
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
|
||||||
return sendChallenge(result);
|
|
||||||
} else if (status == AuthenticationProcessor.Status.FAILURE_CHALLENGE) {
|
|
||||||
AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
|
|
||||||
processor.logFailure();
|
|
||||||
executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
|
|
||||||
return sendChallenge(result);
|
|
||||||
} else if (status == AuthenticationProcessor.Status.ATTEMPTED) {
|
|
||||||
AuthenticationProcessor.logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
|
|
||||||
if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
|
|
||||||
throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.INVALID_CREDENTIALS);
|
|
||||||
}
|
}
|
||||||
executionStatus.put(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
|
|
||||||
AuthenticationProcessor.logger.error("Unknown result status");
|
|
||||||
throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Response sendChallenge(AuthenticatorContext result) {
|
|
||||||
processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, formExecution.getId());
|
|
||||||
return result.getChallenge();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.authentication;
|
package org.keycloak.authentication;
|
||||||
|
|
||||||
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
@ -12,6 +13,5 @@ import java.util.List;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface FormAuthenticator extends Provider {
|
public interface FormAuthenticator extends Provider {
|
||||||
void authenticate(AuthenticatorContext context);
|
Response render(FormContext context, LoginFormsProvider form);
|
||||||
Response createChallenge(FormActionContext context, MultivaluedMap<String, String> formData, List<FormMessage> errorMessages);
|
|
||||||
}
|
}
|
||||||
|
|
33
services/src/main/java/org/keycloak/authentication/FormContext.java
Executable file
33
services/src/main/java/org/keycloak/authentication/FormContext.java
Executable file
|
@ -0,0 +1,33 @@
|
||||||
|
package org.keycloak.authentication;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.keycloak.ClientConnection;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.AuthenticatorConfigModel;
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface FormContext {
|
||||||
|
EventBuilder getEvent();
|
||||||
|
EventBuilder newEvent();
|
||||||
|
AuthenticationExecutionModel getExecution();
|
||||||
|
UserModel getUser();
|
||||||
|
void setUser(UserModel user);
|
||||||
|
RealmModel getRealm();
|
||||||
|
ClientSessionModel getClientSession();
|
||||||
|
ClientConnection getConnection();
|
||||||
|
UriInfo getUriInfo();
|
||||||
|
KeycloakSession getSession();
|
||||||
|
HttpRequest getHttpRequest();
|
||||||
|
AuthenticatorConfigModel getAuthenticatorConfig();
|
||||||
|
|
||||||
|
}
|
15
services/src/main/java/org/keycloak/authentication/ValidationContext.java
Executable file
15
services/src/main/java/org/keycloak/authentication/ValidationContext.java
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
package org.keycloak.authentication;
|
||||||
|
|
||||||
|
import org.keycloak.models.utils.FormMessage;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface ValidationContext extends FormContext {
|
||||||
|
void validationError(MultivaluedMap<String, String> formData, List<FormMessage> errors);
|
||||||
|
void success();
|
||||||
|
}
|
|
@ -48,10 +48,6 @@ public class UsernamePasswordForm extends AbstractFormAuthenticator implements A
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticate(AuthenticatorContext context) {
|
public void authenticate(AuthenticatorContext context) {
|
||||||
if (REGISTRATION_FORM_ACTION.equals(context.getAction()) && context.getUser() != null) {
|
|
||||||
context.success();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
|
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
|
||||||
String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
|
String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
package org.keycloak.authentication.forms;
|
package org.keycloak.authentication.forms;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.OAuth2Constants;
|
|
||||||
import org.keycloak.authentication.AuthenticatorContext;
|
|
||||||
import org.keycloak.authentication.FormActionContext;
|
|
||||||
import org.keycloak.authentication.FormAuthenticator;
|
import org.keycloak.authentication.FormAuthenticator;
|
||||||
import org.keycloak.authentication.FormAuthenticatorFactory;
|
import org.keycloak.authentication.FormAuthenticatorFactory;
|
||||||
|
import org.keycloak.authentication.FormContext;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
|
||||||
import org.keycloak.models.utils.FormMessage;
|
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.net.URI;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -35,38 +27,8 @@ public class RegistrationPage implements FormAuthenticator, FormAuthenticatorFac
|
||||||
public static final String PROVIDER_ID = "registration-page-form";
|
public static final String PROVIDER_ID = "registration-page-form";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticate(AuthenticatorContext context) {
|
public Response render(FormContext context, LoginFormsProvider form) {
|
||||||
LoginFormsProvider registrationPage = createForm(context, context.getExecution().getId());
|
return form.createRegistration();
|
||||||
context.challenge(registrationPage.createRegistration());
|
|
||||||
|
|
||||||
}
|
|
||||||
public URI getActionUrl(AuthenticatorContext context, String executionId, String code) {
|
|
||||||
return LoginActionsService.registrationFormProcessor(context.getUriInfo())
|
|
||||||
.queryParam(OAuth2Constants.CODE, code)
|
|
||||||
.queryParam(EXECUTION, executionId)
|
|
||||||
.build(context.getRealm().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response createChallenge(FormActionContext context, MultivaluedMap<String, String> formData, List<FormMessage> errorMessages) {
|
|
||||||
LoginFormsProvider registrationPage = createForm(context, context.getFormExecution().getId());
|
|
||||||
if (formData != null) registrationPage.setFormData(formData);
|
|
||||||
if (errorMessages != null) {
|
|
||||||
registrationPage.setErrors(errorMessages);
|
|
||||||
}
|
|
||||||
return registrationPage.createRegistration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoginFormsProvider createForm(AuthenticatorContext context, String executionId) {
|
|
||||||
AuthenticationExecutionModel.Requirement categoryRequirement = context.getCategoryRequirementFromCurrentFlow(UserCredentialModel.PASSWORD);
|
|
||||||
boolean passwordRequired = categoryRequirement != null && categoryRequirement != AuthenticationExecutionModel.Requirement.DISABLED;
|
|
||||||
String code = context.generateAccessCode();
|
|
||||||
URI actionUrl = getActionUrl(context, executionId, code);
|
|
||||||
return context.getSession().getProvider(LoginFormsProvider.class)
|
|
||||||
.setAttribute("passwordRequired", passwordRequired)
|
|
||||||
.setActionUri(actionUrl)
|
|
||||||
.setClientSessionCode(code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package org.keycloak.authentication.forms;
|
package org.keycloak.authentication.forms;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.AuthenticatorContext;
|
|
||||||
import org.keycloak.authentication.FormAction;
|
import org.keycloak.authentication.FormAction;
|
||||||
import org.keycloak.authentication.FormActionContext;
|
|
||||||
import org.keycloak.authentication.FormActionFactory;
|
import org.keycloak.authentication.FormActionFactory;
|
||||||
import org.keycloak.authentication.FormAuthenticator;
|
import org.keycloak.authentication.FormContext;
|
||||||
|
import org.keycloak.authentication.ValidationContext;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
@ -16,11 +16,11 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
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.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -28,11 +28,11 @@ import java.util.List;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class RegistrationPasswordValidation implements FormAction, FormActionFactory {
|
public class RegistrationPassword implements FormAction, FormActionFactory {
|
||||||
public static final String PROVIDER_ID = "password-validation-action";
|
public static final String PROVIDER_ID = "registration-password-action";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticate(FormActionContext context) {
|
public void validate(ValidationContext context) {
|
||||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
List<FormMessage> errors = new ArrayList<>();
|
List<FormMessage> errors = new ArrayList<>();
|
||||||
context.getEvent().detail(Details.REGISTER_METHOD, "form");
|
context.getEvent().detail(Details.REGISTER_METHOD, "form");
|
||||||
|
@ -51,14 +51,34 @@ public class RegistrationPasswordValidation implements FormAction, FormActionFac
|
||||||
context.getEvent().error(Errors.INVALID_REGISTRATION);
|
context.getEvent().error(Errors.INVALID_REGISTRATION);
|
||||||
formData.remove(RegistrationPage.FIELD_PASSWORD);
|
formData.remove(RegistrationPage.FIELD_PASSWORD);
|
||||||
formData.remove(RegistrationPage.FIELD_PASSWORD_CONFIRM);
|
formData.remove(RegistrationPage.FIELD_PASSWORD_CONFIRM);
|
||||||
Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
|
context.validationError(formData, errors);
|
||||||
context.challenge(challenge);
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
context.success();
|
context.success();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success(FormContext context) {
|
||||||
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
|
String password = formData.getFirst(RegistrationPage.FIELD_PASSWORD);
|
||||||
|
UserCredentialModel credentials = new UserCredentialModel();
|
||||||
|
credentials.setType(CredentialRepresentation.PASSWORD);
|
||||||
|
credentials.setValue(password);
|
||||||
|
UserModel user = context.getUser();
|
||||||
|
try {
|
||||||
|
context.getSession().users().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password")));
|
||||||
|
} catch (Exception me) {
|
||||||
|
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void buildPage(FormContext context, LoginFormsProvider form) {
|
||||||
|
form.setAttribute("passwordRequired", true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean requiresUser() {
|
public boolean requiresUser() {
|
||||||
return false;
|
return false;
|
|
@ -1,17 +1,16 @@
|
||||||
package org.keycloak.authentication.forms;
|
package org.keycloak.authentication.forms;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.AuthenticatorContext;
|
|
||||||
import org.keycloak.authentication.FormAction;
|
import org.keycloak.authentication.FormAction;
|
||||||
import org.keycloak.authentication.FormActionContext;
|
|
||||||
import org.keycloak.authentication.FormActionFactory;
|
import org.keycloak.authentication.FormActionFactory;
|
||||||
import org.keycloak.authentication.FormAuthenticator;
|
import org.keycloak.authentication.FormContext;
|
||||||
|
import org.keycloak.authentication.ValidationContext;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
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;
|
||||||
|
@ -19,7 +18,6 @@ import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -27,12 +25,12 @@ import java.util.List;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class RegistrationProfileValidation implements FormAction, FormActionFactory {
|
public class RegistrationProfile implements FormAction, FormActionFactory {
|
||||||
public static final String PROVIDER_ID = "profile-validation-action";
|
public static final String PROVIDER_ID = "registration-profile-action";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticate(FormActionContext context) {
|
public void validate(ValidationContext context) {
|
||||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
List<FormMessage> errors = new ArrayList<>();
|
List<FormMessage> errors = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -65,8 +63,7 @@ public class RegistrationProfileValidation implements FormAction, FormActionFact
|
||||||
|
|
||||||
if (errors.size() > 0) {
|
if (errors.size() > 0) {
|
||||||
context.getEvent().error(eventError);
|
context.getEvent().error(eventError);
|
||||||
Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
|
context.validationError(formData, errors);
|
||||||
context.challenge(challenge);
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -74,6 +71,20 @@ public class RegistrationProfileValidation implements FormAction, FormActionFact
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success(FormContext context) {
|
||||||
|
UserModel user = context.getUser();
|
||||||
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
|
user.setFirstName(formData.getFirst(RegistrationPage.FIELD_FIRST_NAME));
|
||||||
|
user.setLastName(formData.getFirst(RegistrationPage.FIELD_LAST_NAME));
|
||||||
|
user.setEmail(formData.getFirst(RegistrationPage.FIELD_EMAIL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void buildPage(FormContext context, LoginFormsProvider form) {
|
||||||
|
// complete
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean requiresUser() {
|
public boolean requiresUser() {
|
||||||
return false;
|
return false;
|
|
@ -0,0 +1,169 @@
|
||||||
|
package org.keycloak.authentication.forms;
|
||||||
|
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.authentication.FormAction;
|
||||||
|
import org.keycloak.authentication.FormActionFactory;
|
||||||
|
import org.keycloak.authentication.FormContext;
|
||||||
|
import org.keycloak.authentication.ValidationContext;
|
||||||
|
import org.keycloak.connections.httpclient.HttpClientProvider;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.FormMessage;
|
||||||
|
import org.keycloak.services.messages.Messages;
|
||||||
|
import org.keycloak.services.validation.Validation;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class RegistrationRecaptcha implements FormAction, FormActionFactory {
|
||||||
|
public static final String G_RECAPTCHA_RESPONSE = "g-recaptcha-response";
|
||||||
|
public static final String RECAPTCHA_REFERENCE_CATEGORY = "recaptcha";
|
||||||
|
protected static Logger logger = Logger.getLogger(RegistrationRecaptcha.class);
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "registration-recaptcha-action";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Recaptcha";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getReferenceCategory() {
|
||||||
|
return RECAPTCHA_REFERENCE_CATEGORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfigurable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||||
|
return new AuthenticationExecutionModel.Requirement[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void buildPage(FormContext context, LoginFormsProvider form) {
|
||||||
|
form.setAttribute("recaptchaRequired", true);
|
||||||
|
form.setAttribute("recaptchaSiteKey", "6LcFEAkTAAAAAOaY-5RJk3zIYw4AalNtqfac27Bn");
|
||||||
|
List<String> scripts = new LinkedList<>();
|
||||||
|
scripts.add("https://www.google.com/recaptcha/api.js");
|
||||||
|
form.setAttribute("scripts", scripts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(ValidationContext context) {
|
||||||
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
|
List<FormMessage> errors = new ArrayList<>();
|
||||||
|
boolean success = false;
|
||||||
|
context.getEvent().detail(Details.REGISTER_METHOD, "form");
|
||||||
|
|
||||||
|
String captcha = formData.getFirst(G_RECAPTCHA_RESPONSE);
|
||||||
|
if (Validation.isBlank(captcha)) {
|
||||||
|
|
||||||
|
HttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient();
|
||||||
|
HttpPost post = new HttpPost("https://www.google.com/recaptcha/api/siteverify");
|
||||||
|
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
||||||
|
formparams.add(new BasicNameValuePair("secret", "6LcFEAkTAAAAAM0SErEs9NlfhYpOTRj_vOVJSAMI"));
|
||||||
|
formparams.add(new BasicNameValuePair("response", captcha));
|
||||||
|
formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
|
||||||
|
try {
|
||||||
|
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||||
|
post.setEntity(form);
|
||||||
|
HttpResponse response = httpClient.execute(post);
|
||||||
|
InputStream content = response.getEntity().getContent();
|
||||||
|
try {
|
||||||
|
Map json = JsonSerialization.readValue(content, Map.class);
|
||||||
|
Object val = json.get("success");
|
||||||
|
success = Boolean.TRUE.equals(val);
|
||||||
|
} finally {
|
||||||
|
content.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Recaptcha failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
context.success();
|
||||||
|
} else {
|
||||||
|
String usernameField = RegistrationPage.FIELD_USERNAME;
|
||||||
|
if (context.getRealm().isRegistrationEmailAsUsername()) {
|
||||||
|
usernameField = RegistrationPage.FIELD_EMAIL;
|
||||||
|
}
|
||||||
|
errors.add(new FormMessage(usernameField, Messages.RECAPTCHA_FAILED));
|
||||||
|
formData.remove(G_RECAPTCHA_RESPONSE);
|
||||||
|
context.getEvent().error(Errors.INVALID_REGISTRATION);
|
||||||
|
context.validationError(formData, errors);
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success(FormContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresUser() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FormAction create(KeycloakSession session) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,28 @@
|
||||||
package org.keycloak.authentication.forms;
|
package org.keycloak.authentication.forms;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.AuthenticatorContext;
|
|
||||||
import org.keycloak.authentication.FormAction;
|
import org.keycloak.authentication.FormAction;
|
||||||
import org.keycloak.authentication.FormActionContext;
|
|
||||||
import org.keycloak.authentication.FormActionFactory;
|
import org.keycloak.authentication.FormActionFactory;
|
||||||
import org.keycloak.authentication.FormAuthenticator;
|
import org.keycloak.authentication.FormContext;
|
||||||
|
import org.keycloak.authentication.ValidationContext;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.ModelException;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.AttributeFormDataProcessor;
|
import org.keycloak.services.resources.AttributeFormDataProcessor;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -32,7 +33,67 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
|
||||||
public static final String PROVIDER_ID = "registration-user-creation";
|
public static final String PROVIDER_ID = "registration-user-creation";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticate(FormActionContext context) {
|
public void validate(ValidationContext context) {
|
||||||
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
|
List<FormMessage> errors = new ArrayList<>();
|
||||||
|
context.getEvent().detail(Details.REGISTER_METHOD, "form");
|
||||||
|
|
||||||
|
String email = formData.getFirst(Validation.FIELD_EMAIL);
|
||||||
|
String username = formData.getFirst(RegistrationPage.FIELD_USERNAME);
|
||||||
|
context.getEvent().detail(Details.USERNAME, username);
|
||||||
|
context.getEvent().detail(Details.EMAIL, email);
|
||||||
|
|
||||||
|
String usernameField = RegistrationPage.FIELD_USERNAME;
|
||||||
|
if (context.getRealm().isRegistrationEmailAsUsername()) {
|
||||||
|
username = email;
|
||||||
|
context.getEvent().detail(Details.USERNAME, username);
|
||||||
|
usernameField = RegistrationPage.FIELD_EMAIL;
|
||||||
|
if (Validation.isBlank(email)) {
|
||||||
|
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL));
|
||||||
|
} else if (!Validation.isEmailValid(email)) {
|
||||||
|
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL));
|
||||||
|
formData.remove(Validation.FIELD_EMAIL);
|
||||||
|
}
|
||||||
|
if (errors.size() > 0) {
|
||||||
|
context.getEvent().error(Errors.INVALID_REGISTRATION);
|
||||||
|
context.validationError(formData, errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (email != null && context.getSession().users().getUserByEmail(email, context.getRealm()) != null) {
|
||||||
|
context.getEvent().error(Errors.USERNAME_IN_USE);
|
||||||
|
formData.remove(Validation.FIELD_EMAIL);
|
||||||
|
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.USERNAME_EXISTS));
|
||||||
|
context.validationError(formData, errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Validation.isBlank(username)) {
|
||||||
|
context.getEvent().error(Errors.INVALID_REGISTRATION);
|
||||||
|
errors.add(new FormMessage(RegistrationPage.FIELD_USERNAME, Messages.MISSING_USERNAME));
|
||||||
|
context.validationError(formData, errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (context.getSession().users().getUserByUsername(username, context.getRealm()) != null) {
|
||||||
|
context.getEvent().error(Errors.USERNAME_IN_USE);
|
||||||
|
errors.add(new FormMessage(usernameField, Messages.USERNAME_EXISTS));
|
||||||
|
formData.remove(Validation.FIELD_USERNAME);
|
||||||
|
formData.remove(Validation.FIELD_EMAIL);
|
||||||
|
context.validationError(formData, errors);
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
context.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void buildPage(FormContext context, LoginFormsProvider form) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success(FormContext context) {
|
||||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
String email = formData.getFirst(Validation.FIELD_EMAIL);
|
String email = formData.getFirst(Validation.FIELD_EMAIL);
|
||||||
String username = formData.getFirst(RegistrationPage.FIELD_USERNAME);
|
String username = formData.getFirst(RegistrationPage.FIELD_USERNAME);
|
||||||
|
@ -45,29 +106,12 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
|
||||||
;
|
;
|
||||||
UserModel user = context.getSession().users().addUser(context.getRealm(), username);
|
UserModel user = context.getSession().users().addUser(context.getRealm(), username);
|
||||||
user.setEnabled(true);
|
user.setEnabled(true);
|
||||||
user.setFirstName(formData.getFirst("firstName"));
|
|
||||||
user.setLastName(formData.getFirst("lastName"));
|
|
||||||
|
|
||||||
user.setEmail(email);
|
user.setEmail(email);
|
||||||
context.getClientSession().setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
|
context.getClientSession().setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
|
||||||
AttributeFormDataProcessor.process(formData, context.getRealm(), user);
|
AttributeFormDataProcessor.process(formData, context.getRealm(), user);
|
||||||
context.setUser(user);
|
context.setUser(user);
|
||||||
AuthenticationExecutionModel.Requirement categoryRequirement = context.getCategoryRequirementFromCurrentFlow(UserCredentialModel.PASSWORD);
|
|
||||||
boolean passwordRequired = categoryRequirement != null && categoryRequirement != AuthenticationExecutionModel.Requirement.DISABLED;
|
|
||||||
if (passwordRequired) {
|
|
||||||
String password = formData.getFirst(RegistrationPage.FIELD_PASSWORD);
|
|
||||||
UserCredentialModel credentials = new UserCredentialModel();
|
|
||||||
credentials.setType(CredentialRepresentation.PASSWORD);
|
|
||||||
credentials.setValue(password);
|
|
||||||
|
|
||||||
try {
|
|
||||||
context.getSession().users().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password")));
|
|
||||||
} catch (Exception me) {
|
|
||||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context.getEvent().user(user);
|
context.getEvent().user(user);
|
||||||
context.success();
|
|
||||||
context.getEvent().success();
|
context.getEvent().success();
|
||||||
context.newEvent().event(EventType.LOGIN);
|
context.newEvent().event(EventType.LOGIN);
|
||||||
context.getEvent().client(context.getClientSession().getClient().getClientId())
|
context.getEvent().client(context.getClientSession().getClient().getClientId())
|
||||||
|
|
|
@ -1,152 +0,0 @@
|
||||||
package org.keycloak.authentication.forms;
|
|
||||||
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.authentication.AuthenticatorContext;
|
|
||||||
import org.keycloak.authentication.FormAction;
|
|
||||||
import org.keycloak.authentication.FormActionContext;
|
|
||||||
import org.keycloak.authentication.FormActionFactory;
|
|
||||||
import org.keycloak.authentication.FormAuthenticator;
|
|
||||||
import org.keycloak.events.Details;
|
|
||||||
import org.keycloak.events.Errors;
|
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.models.utils.FormMessage;
|
|
||||||
import org.keycloak.services.messages.Messages;
|
|
||||||
import org.keycloak.services.validation.Validation;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class RegistrationUsernameValidation implements FormAction, FormActionFactory {
|
|
||||||
|
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "username-validation-action";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void authenticate(FormActionContext context) {
|
|
||||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
|
||||||
List<FormMessage> errors = new ArrayList<>();
|
|
||||||
context.getEvent().detail(Details.REGISTER_METHOD, "form");
|
|
||||||
|
|
||||||
String email = formData.getFirst(Validation.FIELD_EMAIL);
|
|
||||||
String username = formData.getFirst(RegistrationPage.FIELD_USERNAME);
|
|
||||||
context.getEvent().detail(Details.USERNAME, username);
|
|
||||||
context.getEvent().detail(Details.EMAIL, email);
|
|
||||||
|
|
||||||
String usernameField = RegistrationPage.FIELD_USERNAME;
|
|
||||||
if (context.getRealm().isRegistrationEmailAsUsername()) {
|
|
||||||
username = email;
|
|
||||||
context.getEvent().detail(Details.USERNAME, username);
|
|
||||||
usernameField = RegistrationPage.FIELD_EMAIL;
|
|
||||||
if (Validation.isBlank(email)) {
|
|
||||||
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL));
|
|
||||||
} else if (!Validation.isEmailValid(email)) {
|
|
||||||
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL));
|
|
||||||
formData.remove(Validation.FIELD_EMAIL);
|
|
||||||
}
|
|
||||||
if (errors.size() > 0) {
|
|
||||||
context.getEvent().error(Errors.INVALID_REGISTRATION);
|
|
||||||
Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
|
|
||||||
context.challenge(challenge);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (email != null && context.getSession().users().getUserByEmail(email, context.getRealm()) != null) {
|
|
||||||
context.getEvent().error(Errors.USERNAME_IN_USE);
|
|
||||||
formData.remove(Validation.FIELD_EMAIL);
|
|
||||||
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.USERNAME_EXISTS));
|
|
||||||
Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
|
|
||||||
context.challenge(challenge);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (Validation.isBlank(username)) {
|
|
||||||
context.getEvent().error(Errors.INVALID_REGISTRATION);
|
|
||||||
errors.add(new FormMessage(RegistrationPage.FIELD_USERNAME, Messages.MISSING_USERNAME));
|
|
||||||
Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
|
|
||||||
context.challenge(challenge);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if (context.getSession().users().getUserByUsername(username, context.getRealm()) != null) {
|
|
||||||
context.getEvent().error(Errors.USERNAME_IN_USE);
|
|
||||||
errors.add(new FormMessage(usernameField, Messages.USERNAME_EXISTS));
|
|
||||||
formData.remove(Validation.FIELD_USERNAME);
|
|
||||||
formData.remove(Validation.FIELD_EMAIL);
|
|
||||||
Response challenge = context.getFormAuthenticator().createChallenge(context, formData, errors);
|
|
||||||
context.challenge(challenge);
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
context.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresUser() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDisplayType() {
|
|
||||||
return "Username Validation";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getReferenceCategory() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConfigurable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
|
||||||
return new AuthenticationExecutionModel.Requirement[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FormAction create(KeycloakSession session) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Config.Scope config) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return PROVIDER_ID;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -57,6 +57,7 @@ public class Messages {
|
||||||
public static final String INVALID_TOTP = "invalidTotpMessage";
|
public static final String INVALID_TOTP = "invalidTotpMessage";
|
||||||
|
|
||||||
public static final String USERNAME_EXISTS = "usernameExistsMessage";
|
public static final String USERNAME_EXISTS = "usernameExistsMessage";
|
||||||
|
public static final String RECAPTCHA_FAILED = "recaptchaFailed";
|
||||||
|
|
||||||
public static final String EMAIL_EXISTS = "emailExistsMessage";
|
public static final String EMAIL_EXISTS = "emailExistsMessage";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
org.keycloak.authentication.forms.RegistrationPasswordValidation
|
org.keycloak.authentication.forms.RegistrationPassword
|
||||||
org.keycloak.authentication.forms.RegistrationProfileValidation
|
org.keycloak.authentication.forms.RegistrationProfile
|
||||||
org.keycloak.authentication.forms.RegistrationUserCreation
|
org.keycloak.authentication.forms.RegistrationUserCreation
|
||||||
org.keycloak.authentication.forms.RegistrationUsernameValidation
|
org.keycloak.authentication.forms.RegistrationRecaptcha
|
Loading…
Reference in a new issue