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.
|
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?".
|
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.
|
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.
|
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>
|
<modules>
|
||||||
<module>rest</module>
|
<module>rest</module>
|
||||||
<module>authenticator</module>
|
|
||||||
</modules>
|
</modules>
|
||||||
</project>
|
</project>
|
||||||
|
|
Loading…
Reference in a new issue