Move authenticator example to quickstarts (#27850)
Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
parent
1099f03fe6
commit
1f772d2957
18 changed files with 1 additions and 924 deletions
|
@ -151,7 +151,7 @@ Let's walk through the steps from when a client first redirects to keycloak to a
|
|||
|
||||
In this section, we'll take a look at the Authenticator interface.
|
||||
For this, we are going to implement an authenticator that requires that a user enter in the answer to a secret question like "What is your mother's maiden name?".
|
||||
This example is fully implemented and contained in the examples/providers/authenticator directory of the demo distribution of {project_name}.
|
||||
This example is fully implemented and contained in the {quickstartRepo_link}[{quickstartRepo_name}] repository under `extension/authenticator`.
|
||||
|
||||
To create an authenticator, you must at minimum implement the org.keycloak.authentication.AuthenticatorFactory and Authenticator interfaces.
|
||||
The Authenticator interface defines the logic. The AuthenticatorFactory is responsible for creating instances of an Authenticator.
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
Example Custom Authenticator
|
||||
===================================================
|
||||
|
||||
1. First, Keycloak must be running. See [Getting Started](https://github.com/keycloak/keycloak#getting-started), or you
|
||||
can build distribution from [source](https://github.com/keycloak/keycloak/blob/main/docs/building.md).
|
||||
|
||||
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` server 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 **Flows** tab, you will be able to view the currently
|
||||
defined flows. You cannot modify a 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 in **Forms** subflow and **Add Execution**. Pick `Secret Question` and change
|
||||
the **Requirement** choice.
|
||||
|
||||
7. Go to the **Bindings** tab in **Authentication** menu and change the default **Browser Flow** to your copy of the browser flow
|
||||
and click `Save`.
|
||||
|
||||
8. Next you have to register the required action that you created. Click on the **Required Actions** tab in the **Authentication** menu.
|
||||
Click on the `Register` button and choose your new Required Action. You can also choose the `Default Action` for the Required Action
|
||||
and each new user has to set the secret answer.
|
||||
Your new required action should now be displayed and enabled in the required actions list.
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
<!--
|
||||
~ 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>999.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>
|
|
@ -1,33 +0,0 @@
|
|||
<#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>
|
|
@ -1,35 +0,0 @@
|
|||
<#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 type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* 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.http.HttpResponse;
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.CredentialValidator;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.credential.CredentialProvider;
|
||||
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 jakarta.ws.rs.core.Cookie;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import jakarta.ws.rs.core.NewCookie;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionAuthenticator implements Authenticator, CredentialValidator<SecretQuestionCredentialProvider> {
|
||||
|
||||
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) {
|
||||
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(context, "SECRET_QUESTION_ANSWERED", "true",
|
||||
uri.getRawPath(),
|
||||
null, null,
|
||||
maxCookieAge,
|
||||
false, true);
|
||||
}
|
||||
|
||||
public void addCookie(AuthenticationFlowContext context, String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
|
||||
HttpResponse response = context.getSession().getContext().getHttpResponse();
|
||||
response.setCookieIfAbsent(new NewCookie.Builder(name)
|
||||
.version(1)
|
||||
.path(path)
|
||||
.domain(domain)
|
||||
.comment(comment)
|
||||
.maxAge(maxAge)
|
||||
.secure(secure)
|
||||
.httpOnly(httpOnly)
|
||||
.sameSite(null)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
protected boolean validateAnswer(AuthenticationFlowContext context) {
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
String secret = formData.getFirst("secret_answer");
|
||||
String credentialId = formData.getFirst("credentialId");
|
||||
if (credentialId == null || credentialId.isEmpty()) {
|
||||
credentialId = getCredentialProvider(context.getSession())
|
||||
.getDefaultCredential(context.getSession(), context.getRealm(), context.getUser()).getId();
|
||||
}
|
||||
|
||||
UserCredentialModel input = new UserCredentialModel(credentialId, getType(context.getSession()), secret);
|
||||
return getCredentialProvider(context.getSession()).isValid(context.getRealm(), context.getUser(), input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return getCredentialProvider(session).isConfiguredFor(realm, user, getType(session));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
user.addRequiredAction(SecretQuestionRequiredAction.PROVIDER_ID);
|
||||
}
|
||||
|
||||
public List<RequiredActionFactory> getRequiredActions(KeycloakSession session) {
|
||||
return Collections.singletonList((SecretQuestionRequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, SecretQuestionRequiredAction.PROVIDER_ID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretQuestionCredentialProvider getCredentialProvider(KeycloakSession session) {
|
||||
return (SecretQuestionCredentialProvider)session.getProvider(CredentialProvider.class, SecretQuestionCredentialProviderFactory.PROVIDER_ID);
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
* 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.ALTERNATIVE,
|
||||
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() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* 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.logging.Logger;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.credential.CredentialInputValidator;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.credential.CredentialProvider;
|
||||
import org.keycloak.credential.CredentialTypeMetadata;
|
||||
import org.keycloak.credential.CredentialTypeMetadataContext;
|
||||
import org.keycloak.examples.authenticator.credential.SecretQuestionCredentialModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionCredentialProvider implements CredentialProvider<SecretQuestionCredentialModel>, CredentialInputValidator {
|
||||
private static final Logger logger = Logger.getLogger(SecretQuestionCredentialProvider.class);
|
||||
|
||||
protected KeycloakSession session;
|
||||
|
||||
public SecretQuestionCredentialProvider(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (!(input instanceof UserCredentialModel)) {
|
||||
logger.debug("Expected instance of UserCredentialModel for CredentialInput");
|
||||
return false;
|
||||
}
|
||||
if (!input.getType().equals(getType())) {
|
||||
return false;
|
||||
}
|
||||
String challengeResponse = input.getChallengeResponse();
|
||||
if (challengeResponse == null) {
|
||||
return false;
|
||||
}
|
||||
CredentialModel credentialModel = user.credentialManager().getStoredCredentialById(input.getCredentialId());
|
||||
SecretQuestionCredentialModel sqcm = getCredentialFromModel(credentialModel);
|
||||
return sqcm.getSecretQuestionSecretData().getAnswer().equals(challengeResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCredentialType(String credentialType) {
|
||||
return getType().equals(credentialType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
|
||||
if (!supportsCredentialType(credentialType)) return false;
|
||||
return user.credentialManager().getStoredCredentialsByTypeStream(credentialType).findAny().isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialModel createCredential(RealmModel realm, UserModel user, SecretQuestionCredentialModel credentialModel) {
|
||||
if (credentialModel.getCreatedDate() == null) {
|
||||
credentialModel.setCreatedDate(Time.currentTimeMillis());
|
||||
}
|
||||
return user.credentialManager().createStoredCredential(credentialModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteCredential(RealmModel realm, UserModel user, String credentialId) {
|
||||
return user.credentialManager().removeStoredCredentialById(credentialId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretQuestionCredentialModel getCredentialFromModel(CredentialModel model) {
|
||||
return SecretQuestionCredentialModel.createFromCredentialModel(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialTypeMetadata getCredentialTypeMetadata(CredentialTypeMetadataContext metadataContext) {
|
||||
return CredentialTypeMetadata.builder()
|
||||
.type(getType())
|
||||
.category(CredentialTypeMetadata.Category.TWO_FACTOR)
|
||||
.displayName(SecretQuestionCredentialProviderFactory.PROVIDER_ID)
|
||||
.helpText("secret-question-text")
|
||||
.createAction(SecretQuestionAuthenticatorFactory.PROVIDER_ID)
|
||||
.removeable(false)
|
||||
.build(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return SecretQuestionCredentialModel.TYPE;
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* 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> {
|
||||
|
||||
public static final String PROVIDER_ID = "secret-question";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialProvider create(KeycloakSession session) {
|
||||
return new SecretQuestionCredentialProvider(session);
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* 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.CredentialRegistrator;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.credential.CredentialProvider;
|
||||
import org.keycloak.examples.authenticator.credential.SecretQuestionCredentialModel;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionRequiredAction implements RequiredActionProvider, CredentialRegistrator {
|
||||
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"));
|
||||
SecretQuestionCredentialProvider sqcp = (SecretQuestionCredentialProvider) context.getSession().getProvider(CredentialProvider.class, "secret-question");
|
||||
sqcp.createCredential(context.getRealm(), context.getUser(), SecretQuestionCredentialModel.createSecretQuestion("What is your mom's first name?", answer));
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* 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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* 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.credential;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.examples.authenticator.credential.dto.SecretQuestionCredentialData;
|
||||
import org.keycloak.examples.authenticator.credential.dto.SecretQuestionSecretData;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:alistair.doswald@elca.ch">Alistair Doswald</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionCredentialModel extends CredentialModel {
|
||||
public static final String TYPE = "SECRET_QUESTION";
|
||||
|
||||
private final SecretQuestionCredentialData credentialData;
|
||||
private final SecretQuestionSecretData secretData;
|
||||
|
||||
private SecretQuestionCredentialModel(SecretQuestionCredentialData credentialData, SecretQuestionSecretData secretData) {
|
||||
this.credentialData = credentialData;
|
||||
this.secretData = secretData;
|
||||
}
|
||||
|
||||
private SecretQuestionCredentialModel(String question, String answer) {
|
||||
credentialData = new SecretQuestionCredentialData(question);
|
||||
secretData = new SecretQuestionSecretData(answer);
|
||||
}
|
||||
|
||||
public static SecretQuestionCredentialModel createSecretQuestion(String question, String answer) {
|
||||
SecretQuestionCredentialModel credentialModel = new SecretQuestionCredentialModel(question, answer);
|
||||
credentialModel.fillCredentialModelFields();
|
||||
return credentialModel;
|
||||
}
|
||||
|
||||
public static SecretQuestionCredentialModel createFromCredentialModel(CredentialModel credentialModel){
|
||||
try {
|
||||
SecretQuestionCredentialData credentialData = JsonSerialization.readValue(credentialModel.getCredentialData(), SecretQuestionCredentialData.class);
|
||||
SecretQuestionSecretData secretData = JsonSerialization.readValue(credentialModel.getSecretData(), SecretQuestionSecretData.class);
|
||||
|
||||
SecretQuestionCredentialModel secretQuestionCredentialModel = new SecretQuestionCredentialModel(credentialData, secretData);
|
||||
secretQuestionCredentialModel.setUserLabel(credentialModel.getUserLabel());
|
||||
secretQuestionCredentialModel.setCreatedDate(credentialModel.getCreatedDate());
|
||||
secretQuestionCredentialModel.setType(TYPE);
|
||||
secretQuestionCredentialModel.setId(credentialModel.getId());
|
||||
secretQuestionCredentialModel.setSecretData(credentialModel.getSecretData());
|
||||
secretQuestionCredentialModel.setCredentialData(credentialModel.getCredentialData());
|
||||
return secretQuestionCredentialModel;
|
||||
} catch (IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SecretQuestionCredentialData getSecretQuestionCredentialData() {
|
||||
return credentialData;
|
||||
}
|
||||
|
||||
public SecretQuestionSecretData getSecretQuestionSecretData() {
|
||||
return secretData;
|
||||
}
|
||||
|
||||
private void fillCredentialModelFields(){
|
||||
try {
|
||||
setCredentialData(JsonSerialization.writeValueAsString(credentialData));
|
||||
setSecretData(JsonSerialization.writeValueAsString(secretData));
|
||||
setType(TYPE);
|
||||
setCreatedDate(Time.currentTimeMillis());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* 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.credential.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:alistair.doswald@elca.ch">Alistair Doswald</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionCredentialData {
|
||||
|
||||
private final String question;
|
||||
|
||||
@JsonCreator
|
||||
public SecretQuestionCredentialData(@JsonProperty("question") String question) {
|
||||
this.question = question;
|
||||
}
|
||||
|
||||
public String getQuestion() {
|
||||
return question;
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* 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.credential.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:alistair.doswald@elca.ch">Alistair Doswald</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SecretQuestionSecretData {
|
||||
|
||||
private final String answer;
|
||||
|
||||
@JsonCreator
|
||||
public SecretQuestionSecretData(@JsonProperty("answer") String answer) {
|
||||
this.answer = answer;
|
||||
}
|
||||
|
||||
public String getAnswer() {
|
||||
return answer;
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#
|
||||
# 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
|
|
@ -1,18 +0,0 @@
|
|||
#
|
||||
# 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
|
|
@ -1 +0,0 @@
|
|||
org.keycloak.examples.authenticator.SecretQuestionCredentialProviderFactory
|
|
@ -32,6 +32,5 @@
|
|||
|
||||
<modules>
|
||||
<module>rest</module>
|
||||
<module>authenticator</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
Loading…
Reference in a new issue