keycloak-uncached
Changes
docbook/reference/en/en-US/modules/auth-spi.xml 260(+252 -8)
examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticatorFactory.java 5(+0 -5)
examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredAction.java 5(+0 -5)
examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory 1(+1 -0)
forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java 7(+7 -0)
services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java 6(+1 -5)
services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java 8(+2 -6)
services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java 8(+2 -6)
services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java 8(+2 -6)
services/src/main/java/org/keycloak/authentication/authenticators/directgrant/AbstractDirectGrantAuthenticator.java 5(+0 -5)
Details
docbook/reference/en/en-US/modules/auth-spi.xml 260(+252 -8)
diff --git a/docbook/reference/en/en-US/modules/auth-spi.xml b/docbook/reference/en/en-US/modules/auth-spi.xml
index 05e7f42..29c8ce1 100755
--- a/docbook/reference/en/en-US/modules/auth-spi.xml
+++ b/docbook/reference/en/en-US/modules/auth-spi.xml
@@ -370,9 +370,9 @@ Forms Subflow - ALTERNATIVE
the Authenticator.
</para>
<para>
- The getId() method is just the unique name of the component. The create() methods should also
- be self explanatory. The create(KeycloakSession) method will actually never be called. It is just
- an artifact of the more generic ProviderFactory interface.
+ The getId() method is just the unique name of the component. The create() method is called by the
+ runtime to allocate and process the Authenticator.
+
<programlisting>
public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory {
@@ -385,11 +385,6 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
}
@Override
- public Authenticator create() {
- return SINGLETON;
- }
-
- @Override
public Authenticator create(KeycloakSession session) {
return SINGLETON;
}
@@ -523,4 +518,253 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
</para>
</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>
\ No newline at end of file
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticatorFactory.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticatorFactory.java
index 09be4c1..94db2ae 100755
--- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticatorFactory.java
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticatorFactory.java
@@ -27,11 +27,6 @@ public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory,
}
@Override
- public Authenticator create() {
- return SINGLETON;
- }
-
- @Override
public Authenticator create(KeycloakSession session) {
return SINGLETON;
}
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredAction.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredAction.java
index 6644b8e..d0e2de7 100755
--- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredAction.java
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionRequiredAction.java
@@ -36,11 +36,6 @@ public class SecretQuestionRequiredAction implements RequiredActionProvider {
}
@Override
- public String getProviderId() {
- return PROVIDER_ID;
- }
-
- @Override
public void close() {
}
diff --git a/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory b/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory
new file mode 100755
index 0000000..88b6a06
--- /dev/null
+++ b/examples/providers/authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory
@@ -0,0 +1 @@
+org.keycloak.examples.authenticator.SecretQuestionRequiredActionFactory
\ No newline at end of file
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index 745e395..039a5bf 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -23,6 +23,13 @@ import org.keycloak.provider.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);
Response createForm(String form);
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 12c27d4..0b74d1e 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -91,6 +91,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
this.session = session;
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) {
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
index ccc854b..cd0c727 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
@@ -16,6 +16,4 @@ import org.keycloak.provider.ProviderFactory;
* @version $Revision: 1 $
*/
public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, ConfigurableAuthenticatorFactory {
- Authenticator create();
-
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java
index 4845b12..76b6af1 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java
@@ -17,14 +17,10 @@ import java.util.List;
public class CookieAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "auth-cookie";
static CookieAuthenticator SINGLETON = new CookieAuthenticator();
- @Override
- public Authenticator create() {
- return SINGLETON;
- }
@Override
public Authenticator create(KeycloakSession session) {
- throw new IllegalStateException("illegal call");
+ return SINGLETON;
}
@Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java
index 1a01e5a..9f76dec 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java
@@ -18,15 +18,11 @@ import java.util.List;
public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "auth-otp-form";
-
- @Override
- public Authenticator create() {
- return new OTPFormAuthenticator();
- }
+ public static final OTPFormAuthenticator SINGLETON = new OTPFormAuthenticator();
@Override
public Authenticator create(KeycloakSession session) {
- throw new IllegalStateException("illegal call");
+ return SINGLETON;
}
@Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java
index ecfa068..f5c7408 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java
@@ -18,15 +18,11 @@ import java.util.List;
public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "auth-spnego";
-
- @Override
- public Authenticator create() {
- return new SpnegoAuthenticator();
- }
+ public static final SpnegoAuthenticator SINGLETON = new SpnegoAuthenticator();
@Override
public Authenticator create(KeycloakSession session) {
- throw new IllegalStateException("illegal call");
+ return SINGLETON;
}
@Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java
index 392a02a..e9afe05 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java
@@ -18,15 +18,11 @@ import java.util.List;
public class UsernamePasswordFormFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "auth-username-password-form";
-
- @Override
- public Authenticator create() {
- return new UsernamePasswordForm();
- }
+ public static final UsernamePasswordForm SINGLETON = new UsernamePasswordForm();
@Override
public Authenticator create(KeycloakSession session) {
- throw new IllegalStateException("illegal call");
+ return SINGLETON;
}
@Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/AbstractDirectGrantAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/AbstractDirectGrantAuthenticator.java
index 2faa7bc..13754aa 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/AbstractDirectGrantAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/AbstractDirectGrantAuthenticator.java
@@ -33,11 +33,6 @@ public abstract class AbstractDirectGrantAuthenticator implements Authenticator,
}
@Override
- public Authenticator create() {
- return this;
- }
-
- @Override
public void close() {
}
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index 1df2545..18f1703 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -54,7 +54,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
return authenticationFlow.processAction(actionExecution);
} else if (model.getId().equals(actionExecution)) {
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);
authenticator.action(result);
Response response = processResult(result);
@@ -108,7 +108,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
if (factory == null) {
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());
UserModel authUser = processor.getClientSession().getAuthenticatedUser();
diff --git a/services/src/main/java/org/keycloak/authentication/FormAction.java b/services/src/main/java/org/keycloak/authentication/FormAction.java
index 3dcdc4e..72a1576 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAction.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAction.java
@@ -16,6 +16,14 @@ import org.keycloak.provider.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
* 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);
- /**
- * 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);
}
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
index df75e4f..3c1817c 100755
--- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationRecaptcha.java
@@ -86,9 +86,7 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory, Con
String siteKey = captchaConfig.getConfig().get(SITE_KEY);
form.setAttribute("recaptchaRequired", true);
form.setAttribute("recaptchaSiteKey", siteKey);
- List<String> scripts = new LinkedList<>();
- scripts.add("https://www.google.com/recaptcha/api.js");
- form.setAttribute("scripts", scripts);
+ form.addScript("https://www.google.com/recaptcha/api.js");
}
@Override
@@ -103,27 +101,7 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory, Con
AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
String secret = captchaConfig.getConfig().get(SITE_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);
- }
+ success = validateRecaptcha(context, success, captcha, secret);
}
if (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
public void success(FormContext context) {
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 279b25c..b9e06f8 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -31,12 +31,12 @@ public class RequiredActionContextResult implements RequiredActionContext {
protected Response challenge;
protected HttpRequest httpRequest;
protected UserModel user;
- protected RequiredActionProvider provider;
+ protected RequiredActionFactory factory;
public RequiredActionContextResult(UserSessionModel userSession, ClientSessionModel clientSession,
RealmModel realm, EventBuilder eventBuilder, KeycloakSession session,
HttpRequest httpRequest,
- UserModel user, RequiredActionProvider provider) {
+ UserModel user, RequiredActionFactory factory) {
this.userSession = userSession;
this.clientSession = clientSession;
this.realm = realm;
@@ -44,7 +44,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
this.session = session;
this.httpRequest = httpRequest;
this.user = user;
- this.provider = provider;
+ this.factory = factory;
}
@Override
@@ -131,20 +131,20 @@ public class RequiredActionContextResult implements RequiredActionContext {
public URI getActionUrl(String code) {
return LoginActionsService.requiredActionProcessor(getUriInfo())
.queryParam(OAuth2Constants.CODE, code)
- .queryParam("action", provider.getProviderId())
+ .queryParam("action", factory.getId())
.build(getRealm().getName());
}
@Override
public URI getActionUrl() {
- String accessCode = generateAccessCode(provider.getProviderId());
+ String accessCode = generateAccessCode(factory.getId());
return getActionUrl(accessCode);
}
@Override
public LoginFormsProvider form() {
- String accessCode = generateAccessCode(provider.getProviderId());
+ String accessCode = generateAccessCode(factory.getId());
URI action = getActionUrl(accessCode);
LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
.setUser(getUser())
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
index f6b1e62..94a9f2a 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java
@@ -36,11 +36,4 @@ public interface RequiredActionProvider extends Provider {
* @param context
*/
void processAction(RequiredActionContext context);
-
- /**
- * Provider id of this required action. Must match ProviderFactory.getId().
- *
- * @return
- */
- String getProviderId();
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java b/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java
index e3ecc9d..cb5ecd2 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java
@@ -39,13 +39,6 @@ public class TermsAndConditions implements RequiredActionProvider, RequiredActio
@Override
- public String getProviderId() {
- return getId();
- }
-
-
-
- @Override
public void evaluateTriggers(RequiredActionContext context) {
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
index 4ef413d..f88cdc8 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
@@ -51,7 +51,7 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
public void requiredActionChallenge(RequiredActionContext context) {
LoginFormsProvider loginFormsProvider = context.getSession()
.getProvider(LoginFormsProvider.class)
- .setClientSessionCode(context.generateAccessCode(getProviderId()))
+ .setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.UPDATE_PASSWORD.name()))
.setUser(context.getUser());
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
context.challenge(challenge);
@@ -92,10 +92,4 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
public String getId() {
return UserModel.RequiredAction.UPDATE_PASSWORD.name();
}
-
- @Override
- public String getProviderId() {
- return getId();
- }
-
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
index 4cb8493..9ca78a6 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
@@ -25,7 +25,7 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
@Override
public void requiredActionChallenge(RequiredActionContext context) {
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
- .setClientSessionCode(context.generateAccessCode(getProviderId()))
+ .setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.UPDATE_PROFILE.name()))
.setUser(context.getUser());
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
@@ -67,10 +67,4 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
public String getId() {
return UserModel.RequiredAction.UPDATE_PROFILE.name();
}
-
- @Override
- public String getProviderId() {
- return getId();
- }
-
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java
index c958357..61b035a 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java
@@ -27,7 +27,7 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
@Override
public void requiredActionChallenge(RequiredActionContext context) {
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
- .setClientSessionCode(context.generateAccessCode(getProviderId()))
+ .setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.CONFIGURE_TOTP.name()))
.setUser(context.getUser());
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP);
context.challenge(challenge);
@@ -69,11 +69,4 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
public String getId() {
return UserModel.RequiredAction.CONFIGURE_TOTP.name();
}
-
- @Override
- public String getProviderId() {
- return getId();
- }
-
-
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
index ba0ee06..f2096cc 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
@@ -44,7 +44,7 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getUserSession().getId());
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
- .setClientSessionCode(context.generateAccessCode(getProviderId()))
+ .setClientSessionCode(context.generateAccessCode(UserModel.RequiredAction.VERIFY_EMAIL.name()))
.setUser(context.getUser());
Response challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
context.challenge(challenge);
@@ -86,11 +86,4 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
public String getId() {
return UserModel.RequiredAction.VERIFY_EMAIL.name();
}
-
- @Override
- public String getProviderId() {
- return getId();
- }
-
-
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index bd50ff0..82e0d4d 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -8,6 +8,7 @@ import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionContextResult;
+import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.events.Details;
@@ -431,8 +432,9 @@ public class AuthenticationManager {
Set<String> requiredActions = user.getRequiredActions();
for (String action : requiredActions) {
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(action);
- RequiredActionProvider actionProvider = session.getProvider(RequiredActionProvider.class, model.getProviderId());
- RequiredActionContextResult context = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, actionProvider);
+ RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, model.getProviderId());
+ RequiredActionProvider actionProvider = factory.create(session);
+ RequiredActionContextResult context = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, factory);
actionProvider.requiredActionChallenge(context);
if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
@@ -447,8 +449,8 @@ public class AuthenticationManager {
return context.getChallenge();
}
else if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
- event.clone().event(EventType.CUSTOM_REQUIRED_ACTION).detail(Details.CUSTOM_REQUIRED_ACTION, actionProvider.getProviderId()).success();
- clientSession.getUserSession().getUser().removeRequiredAction(actionProvider.getProviderId());
+ event.clone().event(EventType.CUSTOM_REQUIRED_ACTION).detail(Details.CUSTOM_REQUIRED_ACTION, factory.getId()).success();
+ clientSession.getUserSession().getUser().removeRequiredAction(factory.getId());
}
}
if (client.isConsentRequired()) {
@@ -505,8 +507,9 @@ public class AuthenticationManager {
// see if any required actions need triggering, i.e. an expired password
for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
if (!model.isEnabled()) continue;
- RequiredActionProvider provider = session.getProvider(RequiredActionProvider.class, model.getProviderId());
- RequiredActionContextResult result = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, provider) {
+ RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, model.getProviderId());
+ RequiredActionProvider provider = factory.create(session);
+ RequiredActionContextResult result = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, user, factory) {
@Override
public void challenge(Response response) {
throw new RuntimeException("Not allowed to call challenge() within evaluateTriggers()");
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index a907237..e65d0de 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -27,6 +27,7 @@ import org.keycloak.ClientConnection;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionContextResult;
+import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
@@ -861,12 +862,13 @@ public class LoginActionsService {
}
- RequiredActionProvider provider = session.getProvider(RequiredActionProvider.class, action);
- if (provider == null) {
+ RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, action);
+ if (factory == null) {
logger.error("required action provider was null");
event.error(Errors.INVALID_CODE);
throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
}
+ RequiredActionProvider provider = factory.create(session);
Checks checks = new Checks();
if (!checks.verifyCode(realm.getBrowserFlow(), code, action)) {
return checks.response;
@@ -883,7 +885,7 @@ public class LoginActionsService {
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
public String generateAccessCode(String action) {
String clientSessionAction = clientSession.getAction();
@@ -905,7 +907,7 @@ public class LoginActionsService {
if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
event.clone().event(EventType.CUSTOM_REQUIRED_ACTION)
.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);
}
if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java
index 1b83d29..f252e2f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java
@@ -54,12 +54,7 @@ public class PassThroughAuthenticator implements Authenticator, AuthenticatorFac
}
- @Override
- public Authenticator create() {
- return this;
- }
-
- @Override
+ @Override
public String getDisplayType() {
return "Dummy Pass Thru";
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
index 0616ee6..35aa312 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
@@ -71,11 +71,6 @@ public class PassThroughRegistration implements Authenticator, AuthenticatorFact
}
@Override
- public Authenticator create() {
- return this;
- }
-
- @Override
public String getDisplayType() {
return "Dummy Pass Thru";
}