[KEYCLOAK-10157] Recover authenticator example for the Keycloak repository (#6025)
This commit is contained in:
parent
b8aa1916d8
commit
9af4276310
14 changed files with 751 additions and 0 deletions
23
examples/providers/authenticator/README.md
Executable file
23
examples/providers/authenticator/README.md
Executable file
|
@ -0,0 +1,23 @@
|
|||
Example Custom Authenticator
|
||||
===================================================
|
||||
|
||||
1. First, Keycloak must be running.
|
||||
|
||||
2. Execute the follow. This will build the example and deploy it
|
||||
|
||||
$ mvn clean install wildfly:deploy
|
||||
|
||||
3. Copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
|
||||
|
||||
4. Login to admin console. Hit browser refresh if you are already logged in so that the new providers show up.
|
||||
|
||||
5. 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. Copy the "Browser" flow.
|
||||
|
||||
6. In your copy, click the "Actions" menu item and "Add Execution". Pick Secret Question
|
||||
|
||||
7. Next you have to register the required action that you created. Click on the Required Actions tab in the Authenticaiton menu.
|
||||
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.
|
||||
|
73
examples/providers/authenticator/pom.xml
Executable file
73
examples/providers/authenticator/pom.xml
Executable file
|
@ -0,0 +1,73 @@
|
|||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-examples-providers-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Authenticator Example</name>
|
||||
<description/>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>authenticator-required-action-example</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi-private</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>authenticator-required-action-example</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.wildfly.plugins</groupId>
|
||||
<artifactId>wildfly-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
33
examples/providers/authenticator/secret-question-config.ftl
Executable file
33
examples/providers/authenticator/secret-question-config.ftl
Executable 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>
|
34
examples/providers/authenticator/secret-question.ftl
Executable file
34
examples/providers/authenticator/secret-question.ftl
Executable file
|
@ -0,0 +1,34 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "title">
|
||||
${msg("loginTitle",realm.name)}
|
||||
<#elseif section = "header">
|
||||
${msg("loginTitleHtml",realm.name)}
|
||||
<#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("doLogIn")}"/>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doCancel")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.examples.authenticator;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpResponse;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.common.util.ServerCookie;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import javax.ws.rs.core.Cookie;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionAuthenticator implements Authenticator {
|
||||
|
||||
public static final String CREDENTIAL_TYPE = "secret_question";
|
||||
|
||||
protected boolean hasCookie(AuthenticationFlowContext context) {
|
||||
Cookie cookie = context.getHttpRequest().getHttpHeaders().getCookies().get("SECRET_QUESTION_ANSWERED");
|
||||
boolean result = cookie != null;
|
||||
if (result) {
|
||||
System.out.println("Bypassing secret question because cookie is set");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticationFlowContext context) {
|
||||
if (hasCookie(context)) {
|
||||
context.success();
|
||||
return;
|
||||
}
|
||||
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()
|
||||
.setError("badSecret")
|
||||
.createForm("secret-question.ftl");
|
||||
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge);
|
||||
return;
|
||||
}
|
||||
setCookie(context);
|
||||
context.success();
|
||||
}
|
||||
|
||||
protected void setCookie(AuthenticationFlowContext context) {
|
||||
AuthenticatorConfigModel config = context.getAuthenticatorConfig();
|
||||
int maxCookieAge = 60 * 60 * 24 * 30; // 30 days
|
||||
if (config != null) {
|
||||
maxCookieAge = Integer.valueOf(config.getConfig().get("cookie.max.age"));
|
||||
|
||||
}
|
||||
URI uri = context.getUriInfo().getBaseUriBuilder().path("realms").path(context.getRealm().getName()).build();
|
||||
addCookie("SECRET_QUESTION_ANSWERED", "true",
|
||||
uri.getRawPath(),
|
||||
null, null,
|
||||
maxCookieAge,
|
||||
false, true);
|
||||
}
|
||||
|
||||
public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
|
||||
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
|
||||
StringBuffer cookieBuf = new StringBuffer();
|
||||
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly);
|
||||
String cookie = cookieBuf.toString();
|
||||
response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, cookie);
|
||||
}
|
||||
|
||||
|
||||
protected boolean validateAnswer(AuthenticationFlowContext context) {
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
String secret = formData.getFirst("secret_answer");
|
||||
UserCredentialModel input = new UserCredentialModel();
|
||||
input.setType(SecretQuestionCredentialProvider.SECRET_QUESTION);
|
||||
input.setValue(secret);
|
||||
return context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(), input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return session.userCredentialManager().isConfiguredFor(realm, user, SecretQuestionCredentialProvider.SECRET_QUESTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
user.addRequiredAction(SecretQuestionRequiredAction.PROVIDER_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.examples.authenticator;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authentication.ConfigurableAuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "secret-question-authenticator";
|
||||
private static final SecretQuestionAuthenticator SINGLETON = new SecretQuestionAuthenticator();
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||
AuthenticationExecutionModel.Requirement.DISABLED
|
||||
};
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSetupAllowed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName("cookie.max.age");
|
||||
property.setLabel("Cookie Max Age");
|
||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
property.setHelpText("Max age in seconds of the SECRET_QUESTION_COOKIE.");
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "A secret question that a user has to answer. i.e. What is your mother's maiden name.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Secret Question";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return "Secret Question";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.examples.authenticator;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.credential.CredentialInputUpdater;
|
||||
import org.keycloak.credential.CredentialInputValidator;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.credential.CredentialProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.cache.CachedUserModel;
|
||||
import org.keycloak.models.cache.OnUserCache;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionCredentialProvider implements CredentialProvider, CredentialInputValidator, CredentialInputUpdater, OnUserCache {
|
||||
public static final String SECRET_QUESTION = "SECRET_QUESTION";
|
||||
public static final String CACHE_KEY = SecretQuestionCredentialProvider.class.getName() + "." + SECRET_QUESTION;
|
||||
|
||||
protected KeycloakSession session;
|
||||
|
||||
public SecretQuestionCredentialProvider(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public CredentialModel getSecret(RealmModel realm, UserModel user) {
|
||||
CredentialModel secret = null;
|
||||
if (user instanceof CachedUserModel) {
|
||||
CachedUserModel cached = (CachedUserModel)user;
|
||||
secret = (CredentialModel)cached.getCachedWith().get(CACHE_KEY);
|
||||
|
||||
} else {
|
||||
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
|
||||
if (!creds.isEmpty()) secret = creds.get(0);
|
||||
}
|
||||
return secret;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (!SECRET_QUESTION.equals(input.getType())) return false;
|
||||
if (!(input instanceof UserCredentialModel)) return false;
|
||||
UserCredentialModel credInput = (UserCredentialModel) input;
|
||||
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
|
||||
if (creds.isEmpty()) {
|
||||
CredentialModel secret = new CredentialModel();
|
||||
secret.setType(SECRET_QUESTION);
|
||||
secret.setValue(credInput.getValue());
|
||||
secret.setCreatedDate(Time.currentTimeMillis());
|
||||
session.userCredentialManager().createCredential(realm ,user, secret);
|
||||
} else {
|
||||
creds.get(0).setValue(credInput.getValue());
|
||||
session.userCredentialManager().updateCredential(realm, user, creds.get(0));
|
||||
}
|
||||
session.userCache().evict(realm, user);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
||||
if (!SECRET_QUESTION.equals(credentialType)) return;
|
||||
|
||||
List<CredentialModel> credentials = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
|
||||
for (CredentialModel cred : credentials) {
|
||||
session.userCredentialManager().removeStoredCredential(realm, user, cred.getId());
|
||||
}
|
||||
session.userCache().evict(realm, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
|
||||
if (!session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION).isEmpty()) {
|
||||
Set<String> set = new HashSet<>();
|
||||
set.add(SECRET_QUESTION);
|
||||
return set;
|
||||
} else {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCredentialType(String credentialType) {
|
||||
return SECRET_QUESTION.equals(credentialType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
|
||||
if (!SECRET_QUESTION.equals(credentialType)) return false;
|
||||
return getSecret(realm, user) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (!SECRET_QUESTION.equals(input.getType())) return false;
|
||||
if (!(input instanceof UserCredentialModel)) return false;
|
||||
|
||||
String secret = getSecret(realm, user).getValue();
|
||||
|
||||
return secret != null && ((UserCredentialModel)input).getValue().equals(secret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
|
||||
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
|
||||
if (!creds.isEmpty()) user.getCachedWith().put(CACHE_KEY, creds.get(0));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.examples.authenticator;
|
||||
|
||||
import org.keycloak.credential.CredentialProvider;
|
||||
import org.keycloak.credential.CredentialProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionCredentialProviderFactory implements CredentialProviderFactory<SecretQuestionCredentialProvider> {
|
||||
@Override
|
||||
public String getId() {
|
||||
return "secret-question";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialProvider create(KeycloakSession session) {
|
||||
return new SecretQuestionCredentialProvider(session);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.examples.authenticator;
|
||||
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionRequiredAction implements RequiredActionProvider {
|
||||
public static final String PROVIDER_ID = "secret_question_config";
|
||||
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requiredActionChallenge(RequiredActionContext context) {
|
||||
Response challenge = context.form().createForm("secret-question-config.ftl");
|
||||
context.challenge(challenge);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAction(RequiredActionContext context) {
|
||||
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("secret_answer"));
|
||||
UserCredentialModel input = new UserCredentialModel();
|
||||
input.setType(SecretQuestionCredentialProvider.SECRET_QUESTION);
|
||||
input.setValue(answer);
|
||||
context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), input);
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.examples.authenticator;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
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";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.examples.authenticator.SecretQuestionAuthenticatorFactory
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.examples.authenticator.SecretQuestionRequiredActionFactory
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.examples.authenticator.SecretQuestionCredentialProviderFactory
|
|
@ -33,5 +33,6 @@
|
|||
<modules>
|
||||
<module>rest</module>
|
||||
<module>domain-extension</module>
|
||||
<module>authenticator</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
Loading…
Reference in a new issue