refactor auth-spi, auth demo, and docs

This commit is contained in:
Bill Burke 2015-08-14 14:38:59 -04:00
parent 1f13f6372a
commit 6d7be80930
9 changed files with 83 additions and 19 deletions

View file

@ -624,8 +624,8 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
<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.
Click on the Required Actions tab. Click on the Register button and choose your new Required Action.
Your new required action should now be displayed and enabled in the required actions list.
</para>
</section>
</section>

View file

@ -14,13 +14,19 @@ Then registering the provider by editing keycloak-server.json and adding the mod
],
You then have to copy the secret-question.ftl file to the standalone/configuration/themes/base/login directory.
You then have to copy the secret-question.ftl and secret-question-config.ftl files to the standalone/configuration/themes/base/login directory.
After you do all this, you then have to reboot keycloak. When reboot is complete, you will need to log into
the admin console to create a new flow with your new authenticator.
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 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 Authenticator. We're looking to add a screencast
have to copy an existing flow or create your own.
Next you have to register your required action.
Click on the Required Actions tab. Click on the Register button and choose your new Required Action.
Your new required action should now be displayed and enabled in the required actions list.
I'm hoping the UI is intuitive enough so that you
can figure out for yourself how to create a flow and add the Authenticator and Required Action. We're looking to add a screencast
to show this in action.

View file

@ -0,0 +1,33 @@
<#import "template.ftl" as layout>
<@layout.registrationLayout; section>
<#if section = "title">
${msg("loginTitle",realm.name)}
<#elseif section = "header">
Setup Secret Question
<#elseif section = "form">
<form id="kc-totp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="totp" class="${properties.kcLabelClass!}">What is your mom's first name?</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input id="totp" name="secret_answer" type="text" class="${properties.kcInputClass!}" />
</div>
</div>
<div class="${properties.kcFormGroupClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
<div class="${properties.kcFormOptionsWrapperClass!}">
</div>
</div>
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
<div class="${properties.kcFormButtonsWrapperClass!}">
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doSubmit")}"/>
</div>
</div>
</div>
</form>
</#if>
</@layout.registrationLayout>

View file

@ -13,6 +13,8 @@ import org.keycloak.services.util.CookieHelper;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -24,7 +26,11 @@ public class SecretQuestionAuthenticator implements Authenticator {
protected boolean hasCookie(AuthenticationFlowContext context) {
Cookie cookie = context.getHttpRequest().getHttpHeaders().getCookies().get("SECRET_QUESTION_ANSWERED");
return cookie != null;
boolean result = cookie != null;
if (result) {
System.out.println("Bypassing secret question because cookie as set");
}
return result;
}
@Override
@ -33,12 +39,17 @@ public class SecretQuestionAuthenticator implements Authenticator {
context.success();
return;
}
Response challenge = context.form().createForm("secret_question.ftl");
Response challenge = context.form().createForm("secret-question.ftl");
context.challenge(challenge);
}
@Override
public void action(AuthenticationFlowContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
if (formData.containsKey("cancel")) {
context.cancelLogin();
return;
}
boolean validated = validateAnswer(context);
if (!validated) {
Response challenge = context.form()
@ -58,11 +69,12 @@ public class SecretQuestionAuthenticator implements Authenticator {
maxCookieAge = Integer.valueOf(config.getConfig().get("cookie.max.age"));
}
URI uri = context.getUriInfo().getBaseUriBuilder().path("realms").path(context.getRealm().getName()).build();
CookieHelper.addCookie("SECRET_QUESTION_ANSWERED", "true",
context.getUriInfo().getBaseUri().getPath() + "/realms/" + context.getRealm().getName(),
uri.getRawPath(),
null, null,
maxCookieAge,
true, true);
false, true);
}
protected boolean validateAnswer(AuthenticationFlowContext context) {

View file

@ -20,14 +20,14 @@ public class SecretQuestionRequiredAction implements RequiredActionProvider {
@Override
public void requiredActionChallenge(RequiredActionContext context) {
Response challenge = context.form().createForm("secret_question_config.ftl");
Response challenge = context.form().createForm("secret-question-config.ftl");
context.challenge(challenge);
}
@Override
public void processAction(RequiredActionContext context) {
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("answer"));
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("secret_answer"));
UserCredentialValueModel model = new UserCredentialValueModel();
model.setValue(answer);
model.setType(SecretQuestionAuthenticator.CREDENTIAL_TYPE);

View file

@ -217,4 +217,11 @@ public interface AuthenticationFlowContext {
* @return
*/
URI getActionUrl();
/**
* End the flow and redirect browser based on protocol specific respones. This should only be executed
* in browser-based flows.
*
*/
void cancelLogin();
}

View file

@ -17,6 +17,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.AuthenticationManager;
@ -367,6 +368,17 @@ public class AuthenticationProcessor {
public URI getActionUrl() {
return getActionUrl(generateAccessCode());
}
@Override
public void cancelLogin() {
getEvent().error(Errors.REJECTED_BY_USER);
LoginProtocol protocol = getSession().getProvider(LoginProtocol.class, getClientSession().getAuthMethod());
protocol.setRealm(getRealm())
.setHttpHeaders(getHttpRequest().getHttpHeaders())
.setUriInfo(getUriInfo());
Response response = protocol.cancelLogin(getClientSession());
forceChallenge(response);
}
}
public void logFailure() {

View file

@ -26,13 +26,7 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl
public void action(AuthenticationFlowContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
if (formData.containsKey("cancel")) {
context.getEvent().error(Errors.REJECTED_BY_USER);
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
protocol.setRealm(context.getRealm())
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
.setUriInfo(context.getUriInfo());
Response response = protocol.cancelLogin(context.getClientSession());
context.forceChallenge(response);
context.cancelLogin();
return;
}
if (!validateForm(context, formData)) {

View file

@ -167,7 +167,7 @@ public class AccountTest {
});
}
@Test
//@Test
public void ideTesting() throws Exception {
Thread.sleep(100000000);
}