Merge pull request #1537 from patriot1burke/master
auth spi docs and refactor
This commit is contained in:
commit
39fcd84d53
26 changed files with 337 additions and 153 deletions
|
@ -370,9 +370,9 @@ Forms Subflow - ALTERNATIVE
|
||||||
the Authenticator.
|
the Authenticator.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
The getId() method is just the unique name of the component. The create() methods should also
|
The getId() method is just the unique name of the component. The create() method is called by the
|
||||||
be self explanatory. The create(KeycloakSession) method will actually never be called. It is just
|
runtime to allocate and process the Authenticator.
|
||||||
an artifact of the more generic ProviderFactory interface.
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory {
|
public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory {
|
||||||
|
|
||||||
|
@ -384,11 +384,6 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authenticator create() {
|
|
||||||
return SINGLETON;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authenticator create(KeycloakSession session) {
|
public Authenticator create(KeycloakSession session) {
|
||||||
return SINGLETON;
|
return SINGLETON;
|
||||||
|
@ -523,4 +518,253 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<title>Required Action Walkthrough</title>
|
||||||
|
<para>
|
||||||
|
In this section will discuss how to define a required action. In the Authenticator section you may have wondered,
|
||||||
|
"How will we get the user's answer to the secret question entered into the system?". As we showed in the example,
|
||||||
|
if the answer is not set up, a required action will be triggered. This section discusses how to implement
|
||||||
|
the required action for the Secret Question Authenticator.
|
||||||
|
</para>
|
||||||
|
<section>
|
||||||
|
<title>Packaging Classes and Deployment</title>
|
||||||
|
<para>
|
||||||
|
You will package your classes within a single jar. This jar does not have to be separate from other provider classes
|
||||||
|
but it must contain a file named <literal>org.keycloak.authentication.RequiredActionFactory</literal>
|
||||||
|
and must be contained in the <literal>META-INF/services/</literal> directory of your jar. This file must list the fully qualified classname
|
||||||
|
of each RequiredActionFactory implementation you have in the jar. For example:
|
||||||
|
<programlisting>
|
||||||
|
org.keycloak.examples.authenticator.SecretQuestionRequiredActionFactory
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This services/ file is used by Keycloak to scan the providers it has to load into the system.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
To deploy this jar, just copy it to the standalone/configuration/providers directory.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<title>Implement the RequiredActionProvider</title>
|
||||||
|
<para>Required actions must first implement the RequiredActionProvider interface. The RequiredActionProvider.requiredActionChallenge()
|
||||||
|
is the initial call by the flow manager into the required action. This method is responsible for rendering the
|
||||||
|
HTML form that will drive the required action.
|
||||||
|
<programlisting>
|
||||||
|
@Override
|
||||||
|
public void requiredActionChallenge(RequiredActionContext context) {
|
||||||
|
Response challenge = context.form().createForm("secret_question_config.ftl");
|
||||||
|
context.challenge(challenge);
|
||||||
|
|
||||||
|
}
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
You see that RequiredActionContext has similar methods to AuthenticationFlowContext. The form() method allows
|
||||||
|
you to render the page from a Freemarker template. The action URL is preset by the call to this form() method.
|
||||||
|
You just need to reference it within your HTML form. I'll show you this later.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The challenge() method notifies the flow manager that
|
||||||
|
a required action must be executed.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The next method is responsible for processing input from the HTML form of the required action. The action
|
||||||
|
URL of the form will be routed to the RequiredActionProvider.processAction() method
|
||||||
|
<programlisting>
|
||||||
|
@Override
|
||||||
|
public void processAction(RequiredActionContext context) {
|
||||||
|
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("answer"));
|
||||||
|
UserCredentialValueModel model = new UserCredentialValueModel();
|
||||||
|
model.setValue(answer);
|
||||||
|
model.setType(SecretQuestionAuthenticator.CREDENTIAL_TYPE);
|
||||||
|
context.getUser().updateCredentialDirectly(model);
|
||||||
|
context.success();
|
||||||
|
}
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The answer is pulled out of the form post. a UserCredentialValueModel is created and the type and value
|
||||||
|
of the credential are set. Then UserModel.updateCredentialDirectly() is invoked. Finally, RequiredActionContext.success()
|
||||||
|
notifies the container that the required action was successful.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<title>Implement the RequiredActionFactory</title>
|
||||||
|
<para>
|
||||||
|
This class is really simple. It is just responsible for creating the required actin provider instance.
|
||||||
|
<programlisting>
|
||||||
|
public class SecretQuestionRequiredActionFactory implements RequiredActionFactory {
|
||||||
|
|
||||||
|
private static final SecretQuestionRequiredAction SINGLETON = new SecretQuestionRequiredAction();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequiredActionProvider create(KeycloakSession session) {
|
||||||
|
return SINGLETON;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return SecretQuestionRequiredAction.PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayText() {
|
||||||
|
return "Secret Question";
|
||||||
|
}
|
||||||
|
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The getDisplayText() method is just for the admin console when it wants to display a friendly name
|
||||||
|
for the required action.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<title>Enable Required Action</title>
|
||||||
|
<para>
|
||||||
|
The final thing you have to do is go into the admin console. Click on the Authentication left menu.
|
||||||
|
Click on the Required Actions tab. Find your required action, and enable. Alternatively, if you
|
||||||
|
click on the default action checkbox, this required action will be applied anytime a new user is created.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<title>Modifying/Extending the Registration Form</title>
|
||||||
|
<para>
|
||||||
|
It is entirely possible for you to implement your own flow with a set of Authenticators to totally change
|
||||||
|
how regisration is done in Keycloak. But what you'll usually want to do is just add a little be of validation
|
||||||
|
to the out of the box registration page.
|
||||||
|
An additional SPI was created to be able to do this. It basically allows
|
||||||
|
you to add validation of form elements on the page as well as to initialize UserModel attributes and data
|
||||||
|
after the user has been registered. We'll look at the implementation of the recaptcha support that
|
||||||
|
Keycloak provides out of the box to show you how to do this.
|
||||||
|
</para>
|
||||||
|
<section>
|
||||||
|
<title>Implementation FormAction Interface</title>
|
||||||
|
<para>
|
||||||
|
The core interface you have to implement is the FormAction interface. A FormAction is responsible for
|
||||||
|
rendering and processing a portion of the page. Rendering is done in the buildPage() method, validation
|
||||||
|
is done in the validate() method, post validation operations are done in success(). Let's first take a look
|
||||||
|
at buildPage()
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
@Override
|
||||||
|
public void buildPage(FormContext context, LoginFormsProvider form) {
|
||||||
|
AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
|
||||||
|
if (captchaConfig == null || captchaConfig.getConfig() == null
|
||||||
|
|| captchaConfig.getConfig().get(SITE_KEY) == null
|
||||||
|
|| captchaConfig.getConfig().get(SITE_SECRET) == null
|
||||||
|
) {
|
||||||
|
form.addError(new FormMessage(null, Messages.RECAPTCHA_NOT_CONFIGURED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String siteKey = captchaConfig.getConfig().get(SITE_KEY);
|
||||||
|
form.setAttribute("recaptchaRequired", true);
|
||||||
|
form.setAttribute("recaptchaSiteKey", siteKey);
|
||||||
|
form.addScript("https://www.google.com/recaptcha/api.js");
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The buildPage() method is a callback by the form flow to help render the page. It receives a form parameter
|
||||||
|
which is a LoginFormsProvider. You can add additional attributes to the form provider so that they can
|
||||||
|
be displayed in the HTML page generated by the registration Freemarker template.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The code above is from the registration recaptcha plugin. Recaptcha requires some specific settings that
|
||||||
|
must be obtained from configuration. FormActions are configured in the exact same was as Authenticators are.
|
||||||
|
In this example, we pull the Google Recaptcha site key from configuration and add it as an attribute
|
||||||
|
to the form provider. Our regstration template file can read this attribute now.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Recaptcha also has the requirement of loading a javascript script. You can do this by calling LoginFormsProvider.addScript()
|
||||||
|
passing in the URL.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The next meaty part of this interface is the validate() method. This is called immediately upon receiving a form
|
||||||
|
post.
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
@Override
|
||||||
|
public void validate(ValidationContext context) {
|
||||||
|
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
|
List<FormMessage> errors = new ArrayList<>();
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
String captcha = formData.getFirst(G_RECAPTCHA_RESPONSE);
|
||||||
|
if (!Validation.isBlank(captcha)) {
|
||||||
|
AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
|
||||||
|
String secret = captchaConfig.getConfig().get(SITE_SECRET);
|
||||||
|
|
||||||
|
success = validateRecaptcha(context, success, captcha, secret);
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
context.success();
|
||||||
|
} else {
|
||||||
|
errors.add(new FormMessage(null, Messages.RECAPTCHA_FAILED));
|
||||||
|
formData.remove(G_RECAPTCHA_RESPONSE);
|
||||||
|
context.validationError(formData, errors);
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Here we obtain the form data that the Recaptcha widget adds to the form. We obtain the Recaptcha secret key
|
||||||
|
from configuration. We then validate the recaptcha. If successful, ValidationContext.success() is called.
|
||||||
|
If not, we invoke ValidationContext.validationError() passing in the formData (so the user doesn't have to re-enter data),
|
||||||
|
we also specify an error message we want displayed. The error message must point to a message bundle property
|
||||||
|
in the internationalized message bundles. For other registration extensions validate() might be validating the
|
||||||
|
format of a form element, i.e. an alternative email attribute.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
After all validations have been processed then, the form flow then invokes the FormAction.success() method. For recaptcha
|
||||||
|
this is a no-op, but if you have additional metadata you want to add to UserModel, you can do that in success() method.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Finally the FormActionFactory class is really implemented similarly to AuthenticatorFactory, so we won't go over it.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<title>Packaging the Action</title>
|
||||||
|
<para>
|
||||||
|
You will package your classes within a single jar. This jar must contain a file named <literal>org.keycloak.authentication.ForActionFactory</literal>
|
||||||
|
and must be contained in the <literal>META-INF/services/</literal> directory of your jar. This file must list the fully qualified classname
|
||||||
|
of each FormActionFactory implementation you have in the jar. For example:
|
||||||
|
<programlisting>
|
||||||
|
org.keycloak.examples.authenticator.registration.RecaptchaFormActionFactory
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This services/ file is used by Keycloak to scan the providers it has to load into the system.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
To deploy this jar, just copy it to the standalone/configuration/providers directory.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<title>Adding FormAction to the Registration Flow</title>
|
||||||
|
<para>
|
||||||
|
Adding an FormAction to a registration page flow must be done in the admin console.
|
||||||
|
If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
|
||||||
|
defined flows. You cannot modify an built in flows, so, to add the Authenticator we've created you
|
||||||
|
have to copy an existing flow or create your own. I'm hoping the UI is intuitive enough so that you
|
||||||
|
can figure out for yourself how to create a flow and add the FormAction.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Basically you'll have to copy the registration flow. Then click Actions menu to the right of
|
||||||
|
the Registration Form, and pick "Add Execution" to add a new execution. You'll pick the FormAction from the selection list.
|
||||||
|
Make sure your FormAction comes after "Registration User Creation" by using the down errors to move it if your FormAction
|
||||||
|
isn't already listed after "Registration User Creation".
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
After you've created your flow, you have to bind it to registration. If you go
|
||||||
|
to the Authentication menu and go to the Bindings tab you will see options to bind a flow to
|
||||||
|
the browser, registration, or direct grant flow.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
</chapter>
|
</chapter>
|
|
@ -26,11 +26,6 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authenticator create() {
|
|
||||||
return SINGLETON;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authenticator create(KeycloakSession session) {
|
public Authenticator create(KeycloakSession session) {
|
||||||
return SINGLETON;
|
return SINGLETON;
|
||||||
|
|
|
@ -35,11 +35,6 @@ public class SecretQuestionRequiredAction implements RequiredActionProvider {
|
||||||
context.success();
|
context.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProviderId() {
|
|
||||||
return PROVIDER_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.examples.authenticator.SecretQuestionRequiredActionFactory
|
|
@ -23,6 +23,13 @@ import org.keycloak.provider.Provider;
|
||||||
*/
|
*/
|
||||||
public interface LoginFormsProvider extends Provider {
|
public interface LoginFormsProvider extends Provider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a script to the html header
|
||||||
|
*
|
||||||
|
* @param scriptUrl
|
||||||
|
*/
|
||||||
|
void addScript(String scriptUrl);
|
||||||
|
|
||||||
public Response createResponse(UserModel.RequiredAction action);
|
public Response createResponse(UserModel.RequiredAction action);
|
||||||
|
|
||||||
Response createForm(String form);
|
Response createForm(String form);
|
||||||
|
|
|
@ -91,6 +91,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
|
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.freeMarker = freeMarker;
|
this.freeMarker = freeMarker;
|
||||||
|
this.attributes.put("scripts", new LinkedList<String>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addScript(String scriptUrl) {
|
||||||
|
List<String> scripts = (List<String>)this.attributes.get("scripts");
|
||||||
|
scripts.add(scriptUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response createResponse(UserModel.RequiredAction action) {
|
public Response createResponse(UserModel.RequiredAction action) {
|
||||||
|
|
|
@ -16,6 +16,4 @@ import org.keycloak.provider.ProviderFactory;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, ConfigurableAuthenticatorFactory {
|
public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, ConfigurableAuthenticatorFactory {
|
||||||
Authenticator create();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
||||||
return authenticationFlow.processAction(actionExecution);
|
return authenticationFlow.processAction(actionExecution);
|
||||||
} else if (model.getId().equals(actionExecution)) {
|
} else if (model.getId().equals(actionExecution)) {
|
||||||
AuthenticatorFactory factory = (AuthenticatorFactory) processor.getSession().getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator());
|
AuthenticatorFactory factory = (AuthenticatorFactory) processor.getSession().getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator());
|
||||||
Authenticator authenticator = factory.create();
|
Authenticator authenticator = factory.create(processor.getSession());
|
||||||
AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, authenticator, executions);
|
AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, authenticator, executions);
|
||||||
authenticator.action(result);
|
authenticator.action(result);
|
||||||
Response response = processResult(result);
|
Response response = processResult(result);
|
||||||
|
@ -108,7 +108,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
||||||
if (factory == null) {
|
if (factory == null) {
|
||||||
throw new AuthenticationFlowException("Could not find AuthenticatorFactory for: " + model.getAuthenticator(), AuthenticationFlowError.INTERNAL_ERROR);
|
throw new AuthenticationFlowException("Could not find AuthenticatorFactory for: " + model.getAuthenticator(), AuthenticationFlowError.INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
Authenticator authenticator = factory.create();
|
Authenticator authenticator = factory.create(processor.getSession());
|
||||||
AuthenticationProcessor.logger.debugv("authenticator: {0}", factory.getId());
|
AuthenticationProcessor.logger.debugv("authenticator: {0}", factory.getId());
|
||||||
UserModel authUser = processor.getClientSession().getAuthenticatedUser();
|
UserModel authUser = processor.getClientSession().getAuthenticatedUser();
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,14 @@ import org.keycloak.provider.Provider;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface FormAction extends Provider {
|
public interface FormAction extends Provider {
|
||||||
|
/**
|
||||||
|
* When a FormAuthenticator is rendering the challenge page, even FormAction.buildPage() method will be called
|
||||||
|
* This gives the FormAction the opportunity to add additional attributes to the form to be displayed.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @param form
|
||||||
|
*/
|
||||||
|
void buildPage(FormContext context, LoginFormsProvider form);
|
||||||
/**
|
/**
|
||||||
* This is the first phase of form processing. Each FormAction.validate() method is called. This gives the
|
* This is the first phase of form processing. Each FormAction.validate() method is called. This gives the
|
||||||
* FormAction a chance to validate and challenge if user input is invalid.
|
* FormAction a chance to validate and challenge if user input is invalid.
|
||||||
|
@ -53,13 +61,5 @@ public interface FormAction extends Provider {
|
||||||
*/
|
*/
|
||||||
void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
|
void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
|
||||||
|
|
||||||
/**
|
|
||||||
* When a FormAuthenticator is rendering the challenge page, even FormAction.buildPage() method will be called
|
|
||||||
* This gives the FormAction the opportunity to add additional attributes to the form to be displayed.
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* @param form
|
|
||||||
*/
|
|
||||||
void buildPage(FormContext context, LoginFormsProvider form);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,12 +31,12 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
||||||
protected Response challenge;
|
protected Response challenge;
|
||||||
protected HttpRequest httpRequest;
|
protected HttpRequest httpRequest;
|
||||||
protected UserModel user;
|
protected UserModel user;
|
||||||
protected RequiredActionProvider provider;
|
protected RequiredActionFactory factory;
|
||||||
|
|
||||||
public RequiredActionContextResult(UserSessionModel userSession, ClientSessionModel clientSession,
|
public RequiredActionContextResult(UserSessionModel userSession, ClientSessionModel clientSession,
|
||||||
RealmModel realm, EventBuilder eventBuilder, KeycloakSession session,
|
RealmModel realm, EventBuilder eventBuilder, KeycloakSession session,
|
||||||
HttpRequest httpRequest,
|
HttpRequest httpRequest,
|
||||||
UserModel user, RequiredActionProvider provider) {
|
UserModel user, RequiredActionFactory factory) {
|
||||||
this.userSession = userSession;
|
this.userSession = userSession;
|
||||||
this.clientSession = clientSession;
|
this.clientSession = clientSession;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
|
@ -44,7 +44,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.httpRequest = httpRequest;
|
this.httpRequest = httpRequest;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.provider = provider;
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -131,20 +131,20 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
||||||
public URI getActionUrl(String code) {
|
public URI getActionUrl(String code) {
|
||||||
return LoginActionsService.requiredActionProcessor(getUriInfo())
|
return LoginActionsService.requiredActionProcessor(getUriInfo())
|
||||||
.queryParam(OAuth2Constants.CODE, code)
|
.queryParam(OAuth2Constants.CODE, code)
|
||||||
.queryParam("action", provider.getProviderId())
|
.queryParam("action", factory.getId())
|
||||||
.build(getRealm().getName());
|
.build(getRealm().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getActionUrl() {
|
public URI getActionUrl() {
|
||||||
String accessCode = generateAccessCode(provider.getProviderId());
|
String accessCode = generateAccessCode(factory.getId());
|
||||||
return getActionUrl(accessCode);
|
return getActionUrl(accessCode);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginFormsProvider form() {
|
public LoginFormsProvider form() {
|
||||||
String accessCode = generateAccessCode(provider.getProviderId());
|
String accessCode = generateAccessCode(factory.getId());
|
||||||
URI action = getActionUrl(accessCode);
|
URI action = getActionUrl(accessCode);
|
||||||
LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
|
LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
|
||||||
.setUser(getUser())
|
.setUser(getUser())
|
||||||
|
|
|
@ -36,11 +36,4 @@ public interface RequiredActionProvider extends Provider {
|
||||||
* @param context
|
* @param context
|
||||||
*/
|
*/
|
||||||
void processAction(RequiredActionContext context);
|
void processAction(RequiredActionContext context);
|
||||||
|
|
||||||
/**
|
|
||||||
* Provider id of this required action. Must match ProviderFactory.getId().
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
String getProviderId();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,10 @@ import java.util.List;
|
||||||
public class CookieAuthenticatorFactory implements AuthenticatorFactory {
|
public class CookieAuthenticatorFactory implements AuthenticatorFactory {
|
||||||
public static final String PROVIDER_ID = "auth-cookie";
|
public static final String PROVIDER_ID = "auth-cookie";
|
||||||
static CookieAuthenticator SINGLETON = new CookieAuthenticator();
|
static CookieAuthenticator SINGLETON = new CookieAuthenticator();
|
||||||
@Override
|
|
||||||
public Authenticator create() {
|
|
||||||
return SINGLETON;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authenticator create(KeycloakSession session) {
|
public Authenticator create(KeycloakSession session) {
|
||||||
throw new IllegalStateException("illegal call");
|
return SINGLETON;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,15 +18,11 @@ import java.util.List;
|
||||||
public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
|
public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "auth-otp-form";
|
public static final String PROVIDER_ID = "auth-otp-form";
|
||||||
|
public static final OTPFormAuthenticator SINGLETON = new OTPFormAuthenticator();
|
||||||
@Override
|
|
||||||
public Authenticator create() {
|
|
||||||
return new OTPFormAuthenticator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authenticator create(KeycloakSession session) {
|
public Authenticator create(KeycloakSession session) {
|
||||||
throw new IllegalStateException("illegal call");
|
return SINGLETON;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,15 +18,11 @@ import java.util.List;
|
||||||
public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
|
public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "auth-spnego";
|
public static final String PROVIDER_ID = "auth-spnego";
|
||||||
|
public static final SpnegoAuthenticator SINGLETON = new SpnegoAuthenticator();
|
||||||
@Override
|
|
||||||
public Authenticator create() {
|
|
||||||
return new SpnegoAuthenticator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authenticator create(KeycloakSession session) {
|
public Authenticator create(KeycloakSession session) {
|
||||||
throw new IllegalStateException("illegal call");
|
return SINGLETON;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,15 +18,11 @@ import java.util.List;
|
||||||
public class UsernamePasswordFormFactory implements AuthenticatorFactory {
|
public class UsernamePasswordFormFactory implements AuthenticatorFactory {
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "auth-username-password-form";
|
public static final String PROVIDER_ID = "auth-username-password-form";
|
||||||
|
public static final UsernamePasswordForm SINGLETON = new UsernamePasswordForm();
|
||||||
@Override
|
|
||||||
public Authenticator create() {
|
|
||||||
return new UsernamePasswordForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authenticator create(KeycloakSession session) {
|
public Authenticator create(KeycloakSession session) {
|
||||||
throw new IllegalStateException("illegal call");
|
return SINGLETON;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -32,11 +32,6 @@ public abstract class AbstractDirectGrantAuthenticator implements Authenticator,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authenticator create() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
||||||
|
|
|
@ -86,9 +86,7 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory, Con
|
||||||
String siteKey = captchaConfig.getConfig().get(SITE_KEY);
|
String siteKey = captchaConfig.getConfig().get(SITE_KEY);
|
||||||
form.setAttribute("recaptchaRequired", true);
|
form.setAttribute("recaptchaRequired", true);
|
||||||
form.setAttribute("recaptchaSiteKey", siteKey);
|
form.setAttribute("recaptchaSiteKey", siteKey);
|
||||||
List<String> scripts = new LinkedList<>();
|
form.addScript("https://www.google.com/recaptcha/api.js");
|
||||||
scripts.add("https://www.google.com/recaptcha/api.js");
|
|
||||||
form.setAttribute("scripts", scripts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -103,27 +101,7 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory, Con
|
||||||
AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
|
AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
|
||||||
String secret = captchaConfig.getConfig().get(SITE_SECRET);
|
String secret = captchaConfig.getConfig().get(SITE_SECRET);
|
||||||
|
|
||||||
HttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient();
|
success = validateRecaptcha(context, success, captcha, secret);
|
||||||
HttpPost post = new HttpPost("https://www.google.com/recaptcha/api/siteverify");
|
|
||||||
List<NameValuePair> formparams = new LinkedList<>();
|
|
||||||
formparams.add(new BasicNameValuePair("secret", secret));
|
|
||||||
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) {
|
if (success) {
|
||||||
context.success();
|
context.success();
|
||||||
|
@ -138,6 +116,31 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory, Con
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean validateRecaptcha(ValidationContext context, boolean success, String captcha, String secret) {
|
||||||
|
HttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient();
|
||||||
|
HttpPost post = new HttpPost("https://www.google.com/recaptcha/api/siteverify");
|
||||||
|
List<NameValuePair> formparams = new LinkedList<>();
|
||||||
|
formparams.add(new BasicNameValuePair("secret", secret));
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void success(FormContext context) {
|
public void success(FormContext context) {
|
||||||
|
|
||||||
|
|
|
@ -38,13 +38,6 @@ public class TermsAndConditions implements RequiredActionProvider, RequiredActio
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProviderId() {
|
|
||||||
return getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void evaluateTriggers(RequiredActionContext context) {
|
public void evaluateTriggers(RequiredActionContext context) {
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
|
||||||
public void requiredActionChallenge(RequiredActionContext context) {
|
public void requiredActionChallenge(RequiredActionContext context) {
|
||||||
LoginFormsProvider loginFormsProvider = context.getSession()
|
LoginFormsProvider loginFormsProvider = context.getSession()
|
||||||
.getProvider(LoginFormsProvider.class)
|
.getProvider(LoginFormsProvider.class)
|
||||||
.setClientSessionCode(context.generateAccessCode(getProviderId()))
|
.setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.UPDATE_PASSWORD.name()))
|
||||||
.setUser(context.getUser());
|
.setUser(context.getUser());
|
||||||
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
context.challenge(challenge);
|
context.challenge(challenge);
|
||||||
|
@ -92,10 +92,4 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return UserModel.RequiredAction.UPDATE_PASSWORD.name();
|
return UserModel.RequiredAction.UPDATE_PASSWORD.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProviderId() {
|
|
||||||
return getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
|
||||||
@Override
|
@Override
|
||||||
public void requiredActionChallenge(RequiredActionContext context) {
|
public void requiredActionChallenge(RequiredActionContext context) {
|
||||||
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
|
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
|
||||||
.setClientSessionCode(context.generateAccessCode(getProviderId()))
|
.setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.UPDATE_PROFILE.name()))
|
||||||
.setUser(context.getUser());
|
.setUser(context.getUser());
|
||||||
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
|
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
|
||||||
context.challenge(challenge);
|
context.challenge(challenge);
|
||||||
|
@ -67,10 +67,4 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return UserModel.RequiredAction.UPDATE_PROFILE.name();
|
return UserModel.RequiredAction.UPDATE_PROFILE.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProviderId() {
|
|
||||||
return getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
|
||||||
@Override
|
@Override
|
||||||
public void requiredActionChallenge(RequiredActionContext context) {
|
public void requiredActionChallenge(RequiredActionContext context) {
|
||||||
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
|
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
|
||||||
.setClientSessionCode(context.generateAccessCode(getProviderId()))
|
.setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.CONFIGURE_TOTP.name()))
|
||||||
.setUser(context.getUser());
|
.setUser(context.getUser());
|
||||||
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP);
|
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP);
|
||||||
context.challenge(challenge);
|
context.challenge(challenge);
|
||||||
|
@ -69,11 +69,4 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return UserModel.RequiredAction.CONFIGURE_TOTP.name();
|
return UserModel.RequiredAction.CONFIGURE_TOTP.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProviderId() {
|
|
||||||
return getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
|
||||||
LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getUserSession().getId());
|
LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getUserSession().getId());
|
||||||
|
|
||||||
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
|
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
|
||||||
.setClientSessionCode(context.generateAccessCode(getProviderId()))
|
.setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.VERIFY_EMAIL.name()))
|
||||||
.setUser(context.getUser());
|
.setUser(context.getUser());
|
||||||
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
|
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
|
||||||
context.challenge(challenge);
|
context.challenge(challenge);
|
||||||
|
@ -86,11 +86,4 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return UserModel.RequiredAction.VERIFY_EMAIL.name();
|
return UserModel.RequiredAction.VERIFY_EMAIL.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProviderId() {
|
|
||||||
return getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.VerificationException;
|
import org.keycloak.VerificationException;
|
||||||
import org.keycloak.authentication.RequiredActionContext;
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
import org.keycloak.authentication.RequiredActionContextResult;
|
import org.keycloak.authentication.RequiredActionContextResult;
|
||||||
|
import org.keycloak.authentication.RequiredActionFactory;
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
import org.keycloak.broker.provider.IdentityProvider;
|
import org.keycloak.broker.provider.IdentityProvider;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
@ -431,8 +432,9 @@ public class AuthenticationManager {
|
||||||
Set<String> requiredActions = user.getRequiredActions();
|
Set<String> requiredActions = user.getRequiredActions();
|
||||||
for (String action : requiredActions) {
|
for (String action : requiredActions) {
|
||||||
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(action);
|
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(action);
|
||||||
RequiredActionProvider actionProvider = session.getProvider(RequiredActionProvider.class, model.getProviderId());
|
RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, model.getProviderId());
|
||||||
RequiredActionContextResult context = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, actionProvider);
|
RequiredActionProvider actionProvider = factory.create(session);
|
||||||
|
RequiredActionContextResult context = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, factory);
|
||||||
actionProvider.requiredActionChallenge(context);
|
actionProvider.requiredActionChallenge(context);
|
||||||
|
|
||||||
if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
|
if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
|
||||||
|
@ -447,8 +449,8 @@ public class AuthenticationManager {
|
||||||
return context.getChallenge();
|
return context.getChallenge();
|
||||||
}
|
}
|
||||||
else if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
|
else if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
|
||||||
event.clone().event(EventType.CUSTOM_REQUIRED_ACTION).detail(Details.CUSTOM_REQUIRED_ACTION, actionProvider.getProviderId()).success();
|
event.clone().event(EventType.CUSTOM_REQUIRED_ACTION).detail(Details.CUSTOM_REQUIRED_ACTION, factory.getId()).success();
|
||||||
clientSession.getUserSession().getUser().removeRequiredAction(actionProvider.getProviderId());
|
clientSession.getUserSession().getUser().removeRequiredAction(factory.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (client.isConsentRequired()) {
|
if (client.isConsentRequired()) {
|
||||||
|
@ -505,8 +507,9 @@ public class AuthenticationManager {
|
||||||
// see if any required actions need triggering, i.e. an expired password
|
// see if any required actions need triggering, i.e. an expired password
|
||||||
for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
|
for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
|
||||||
if (!model.isEnabled()) continue;
|
if (!model.isEnabled()) continue;
|
||||||
RequiredActionProvider provider = session.getProvider(RequiredActionProvider.class, model.getProviderId());
|
RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, model.getProviderId());
|
||||||
RequiredActionContextResult result = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, provider) {
|
RequiredActionProvider provider = factory.create(session);
|
||||||
|
RequiredActionContextResult result = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, factory) {
|
||||||
@Override
|
@Override
|
||||||
public void challenge(Response response) {
|
public void challenge(Response response) {
|
||||||
throw new RuntimeException("Not allowed to call challenge() within evaluateTriggers()");
|
throw new RuntimeException("Not allowed to call challenge() within evaluateTriggers()");
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.authentication.RequiredActionContext;
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
import org.keycloak.authentication.RequiredActionContextResult;
|
import org.keycloak.authentication.RequiredActionContextResult;
|
||||||
|
import org.keycloak.authentication.RequiredActionFactory;
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailProvider;
|
||||||
|
@ -861,12 +862,13 @@ public class LoginActionsService {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RequiredActionProvider provider = session.getProvider(RequiredActionProvider.class, action);
|
RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, action);
|
||||||
if (provider == null) {
|
if (factory == null) {
|
||||||
logger.error("required action provider was null");
|
logger.error("required action provider was null");
|
||||||
event.error(Errors.INVALID_CODE);
|
event.error(Errors.INVALID_CODE);
|
||||||
throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
|
throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
|
||||||
}
|
}
|
||||||
|
RequiredActionProvider provider = factory.create(session);
|
||||||
Checks checks = new Checks();
|
Checks checks = new Checks();
|
||||||
if (!checks.verifyCode(realm.getBrowserFlow(), code, action)) {
|
if (!checks.verifyCode(realm.getBrowserFlow(), code, action)) {
|
||||||
return checks.response;
|
return checks.response;
|
||||||
|
@ -883,7 +885,7 @@ public class LoginActionsService {
|
||||||
initEvent(clientSession);
|
initEvent(clientSession);
|
||||||
|
|
||||||
|
|
||||||
RequiredActionContextResult context = new RequiredActionContextResult(clientSession.getUserSession(), clientSession, realm, event, session, request, clientSession.getUserSession().getUser(), provider) {
|
RequiredActionContextResult context = new RequiredActionContextResult(clientSession.getUserSession(), clientSession, realm, event, session, request, clientSession.getUserSession().getUser(), factory) {
|
||||||
@Override
|
@Override
|
||||||
public String generateAccessCode(String action) {
|
public String generateAccessCode(String action) {
|
||||||
String clientSessionAction = clientSession.getAction();
|
String clientSessionAction = clientSession.getAction();
|
||||||
|
@ -905,7 +907,7 @@ public class LoginActionsService {
|
||||||
if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
|
if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
|
||||||
event.clone().event(EventType.CUSTOM_REQUIRED_ACTION)
|
event.clone().event(EventType.CUSTOM_REQUIRED_ACTION)
|
||||||
.detail(Details.CUSTOM_REQUIRED_ACTION, action).success();
|
.detail(Details.CUSTOM_REQUIRED_ACTION, action).success();
|
||||||
clientSession.getUserSession().getUser().removeRequiredAction(provider.getProviderId());
|
clientSession.getUserSession().getUser().removeRequiredAction(factory.getId());
|
||||||
return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
|
return AuthenticationManager.nextActionAfterAuthentication(session, clientSession.getUserSession(), clientSession, clientConnection, request, uriInfo, event);
|
||||||
}
|
}
|
||||||
if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
|
if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
|
||||||
|
|
|
@ -54,12 +54,7 @@ public class PassThroughAuthenticator implements Authenticator, AuthenticatorFac
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authenticator create() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDisplayType() {
|
public String getDisplayType() {
|
||||||
return "Dummy Pass Thru";
|
return "Dummy Pass Thru";
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,11 +70,6 @@ public class PassThroughRegistration implements Authenticator, AuthenticatorFact
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authenticator create() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayType() {
|
public String getDisplayType() {
|
||||||
return "Dummy Pass Thru";
|
return "Dummy Pass Thru";
|
||||||
|
|
Loading…
Reference in a new issue