[KEYCLOAK-8811] Remove duplicated examples from Keycloak codebase
This commit is contained in:
parent
04445c8a23
commit
376ccdbe35
27 changed files with 0 additions and 1732 deletions
|
@ -43,8 +43,6 @@ Providers
|
|||
|
||||
Example providers for Event Listener SPI, Event Store SPI and User Federation SPI. For more information look at:
|
||||
|
||||
* Event Listener that outputs event detalis to sysout - `providers/event-listener-sysout/README.md`
|
||||
* Event Store that stores events in memory - `providers/event-store-mem/README.md`
|
||||
* User Federation that loads users from a text file - `providers/federation-provider/README.md`
|
||||
|
||||
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
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.
|
||||
|
|
@ -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>4.8.0.Final-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,34 +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 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>
|
|
@ -1,136 +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.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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,119 +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.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,134 +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.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));
|
||||
}
|
||||
}
|
|
@ -1,37 +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> {
|
||||
@Override
|
||||
public String getId() {
|
||||
return "secret-question";
|
||||
}
|
||||
|
||||
@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.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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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,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
|
|
@ -1,29 +0,0 @@
|
|||
Example Event Listener that prints events to System.out
|
||||
=======================================================
|
||||
|
||||
To deploy copy target/event-listener-sysout-example.jar to providers directory. Alternatively you can deploy as a module by running:
|
||||
|
||||
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private"
|
||||
|
||||
Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
|
||||
|
||||
<providers>
|
||||
...
|
||||
<provider>module:org.keycloak.examples.event-sysout</provider>
|
||||
</providers>
|
||||
|
||||
Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events,
|
||||
followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and
|
||||
login again to see events printed to System.out.
|
||||
|
||||
The example event listener can be configured to exclude certain events, for example to exclude REFRESH_TOKEN and
|
||||
CODE_TO_TOKEN events add the following to `standalone.xml`:
|
||||
|
||||
...
|
||||
<spi name="eventsListener">
|
||||
<provider name="sysout">
|
||||
<properties>
|
||||
<property name="exclude-events" value="["REFRESH_TOKEN", "CODE_TO_TOKEN"]"/>
|
||||
</properties>
|
||||
</provider
|
||||
</spi>
|
|
@ -1,53 +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>4.8.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Event Listener System.out Example</name>
|
||||
<description/>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>event-listener-sysout-example</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</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>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>event-listener-sysout-example</finalName>
|
||||
</build>
|
||||
</project>
|
|
@ -1,127 +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.providers.events;
|
||||
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventListenerProvider;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class SysoutEventListenerProvider implements EventListenerProvider {
|
||||
|
||||
private Set<EventType> excludedEvents;
|
||||
private Set<OperationType> excludedAdminOperations;
|
||||
|
||||
public SysoutEventListenerProvider(Set<EventType> excludedEvents, Set<OperationType> excludedAdminOpearations) {
|
||||
this.excludedEvents = excludedEvents;
|
||||
this.excludedAdminOperations = excludedAdminOpearations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
// Ignore excluded events
|
||||
if (excludedEvents != null && excludedEvents.contains(event.getType())) {
|
||||
return;
|
||||
} else {
|
||||
System.out.println("EVENT: " + toString(event));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(AdminEvent event, boolean includeRepresentation) {
|
||||
// Ignore excluded operations
|
||||
if (excludedAdminOperations != null && excludedAdminOperations.contains(event.getOperationType())) {
|
||||
return;
|
||||
} else {
|
||||
System.out.println("EVENT: " + toString(event));
|
||||
}
|
||||
}
|
||||
|
||||
private String toString(Event event) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("type=");
|
||||
sb.append(event.getType());
|
||||
sb.append(", realmId=");
|
||||
sb.append(event.getRealmId());
|
||||
sb.append(", clientId=");
|
||||
sb.append(event.getClientId());
|
||||
sb.append(", userId=");
|
||||
sb.append(event.getUserId());
|
||||
sb.append(", ipAddress=");
|
||||
sb.append(event.getIpAddress());
|
||||
|
||||
if (event.getError() != null) {
|
||||
sb.append(", error=");
|
||||
sb.append(event.getError());
|
||||
}
|
||||
|
||||
if (event.getDetails() != null) {
|
||||
for (Map.Entry<String, String> e : event.getDetails().entrySet()) {
|
||||
sb.append(", ");
|
||||
sb.append(e.getKey());
|
||||
if (e.getValue() == null || e.getValue().indexOf(' ') == -1) {
|
||||
sb.append("=");
|
||||
sb.append(e.getValue());
|
||||
} else {
|
||||
sb.append("='");
|
||||
sb.append(e.getValue());
|
||||
sb.append("'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String toString(AdminEvent adminEvent) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("operationType=");
|
||||
sb.append(adminEvent.getOperationType());
|
||||
sb.append(", realmId=");
|
||||
sb.append(adminEvent.getAuthDetails().getRealmId());
|
||||
sb.append(", clientId=");
|
||||
sb.append(adminEvent.getAuthDetails().getClientId());
|
||||
sb.append(", userId=");
|
||||
sb.append(adminEvent.getAuthDetails().getUserId());
|
||||
sb.append(", ipAddress=");
|
||||
sb.append(adminEvent.getAuthDetails().getIpAddress());
|
||||
sb.append(", resourcePath=");
|
||||
sb.append(adminEvent.getResourcePath());
|
||||
|
||||
if (adminEvent.getError() != null) {
|
||||
sb.append(", error=");
|
||||
sb.append(adminEvent.getError());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,76 +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.providers.events;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.events.EventListenerProvider;
|
||||
import org.keycloak.events.EventListenerProviderFactory;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class SysoutEventListenerProviderFactory implements EventListenerProviderFactory {
|
||||
|
||||
private Set<EventType> excludedEvents;
|
||||
private Set<OperationType> excludedAdminOperations;
|
||||
|
||||
@Override
|
||||
public EventListenerProvider create(KeycloakSession session) {
|
||||
return new SysoutEventListenerProvider(excludedEvents, excludedAdminOperations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
String[] excludes = config.getArray("exclude-events");
|
||||
if (excludes != null) {
|
||||
excludedEvents = new HashSet<>();
|
||||
for (String e : excludes) {
|
||||
excludedEvents.add(EventType.valueOf(e));
|
||||
}
|
||||
}
|
||||
|
||||
String[] excludesOperations = config.getArray("excludesOperations");
|
||||
if (excludesOperations != null) {
|
||||
excludedAdminOperations = new HashSet<>();
|
||||
for (String e : excludesOperations) {
|
||||
excludedAdminOperations.add(OperationType.valueOf(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "sysout";
|
||||
}
|
||||
|
||||
}
|
|
@ -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.providers.events.SysoutEventListenerProviderFactory
|
|
@ -1,29 +0,0 @@
|
|||
Example Event Store that stores events in memory
|
||||
================================================
|
||||
|
||||
To deploy copy target/event-store-mem-example.jar to providers directory. Alternatively you can deploy as a module by running:
|
||||
|
||||
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-inmem --resources=target/event-store-mem-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private"
|
||||
|
||||
Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
|
||||
|
||||
<providers>
|
||||
...
|
||||
<provider>module:org.keycloak.examples.event-inmem</provider>
|
||||
</providers>
|
||||
|
||||
Then edit `standalone/configuration/standalone.xml`, change:
|
||||
|
||||
<spi name="eventsStore">
|
||||
<default-provider>jpa</default-provider>
|
||||
</spi>
|
||||
|
||||
to:
|
||||
|
||||
<spi name="eventsStore">
|
||||
<default-provider>in-mem</default-provider>
|
||||
</spi>
|
||||
|
||||
Then start (or restart)the server. Once started open the admin console, select your realm, then click on Events,
|
||||
followed by config. Set the toggle for Enabled to ON. After this try to logout and login again then open the Events tab
|
||||
again in the admin console to view events from the in-mem provider.
|
|
@ -1,53 +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>4.8.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Event Store In-Mem Example</name>
|
||||
<description/>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>event-store-mem-example</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</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>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>event-store-mem-example</finalName>
|
||||
</build>
|
||||
</project>
|
|
@ -1,196 +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.providers.events;
|
||||
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.events.admin.AdminEventQuery;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
|
||||
*/
|
||||
public class MemAdminEventQuery implements AdminEventQuery {
|
||||
|
||||
private List<AdminEvent> adminEvents;
|
||||
|
||||
private int first;
|
||||
private int max;
|
||||
|
||||
public MemAdminEventQuery(List<AdminEvent> events) {
|
||||
this.adminEvents = events;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public AdminEventQuery realm(String realmId) {
|
||||
Iterator<AdminEvent> itr = adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!itr.next().getRealmId().equals(realmId)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery operation(OperationType... operations) {
|
||||
Iterator<AdminEvent> itr = this.adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
AdminEvent next = itr.next();
|
||||
boolean include = false;
|
||||
for (OperationType e : operations) {
|
||||
if (next.getOperationType().equals(e)) {
|
||||
include = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!include) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery resourceType(ResourceType... resourceTypes) {
|
||||
|
||||
Iterator<AdminEvent> itr = this.adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
AdminEvent next = itr.next();
|
||||
boolean include = false;
|
||||
for (ResourceType e : resourceTypes) {
|
||||
if (next.getResourceType().equals(e)) {
|
||||
include = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!include) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery authRealm(String authRealmId) {
|
||||
Iterator<AdminEvent> itr = adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!itr.next().getAuthDetails().getRealmId().equals(authRealmId)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery authClient(String authClientId) {
|
||||
Iterator<AdminEvent> itr = adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!itr.next().getAuthDetails().getClientId().equals(authClientId)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery authUser(String authUserId) {
|
||||
Iterator<AdminEvent> itr = adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!itr.next().getAuthDetails().getUserId().equals(authUserId)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery authIpAddress(String ipAddress) {
|
||||
Iterator<AdminEvent> itr = adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!itr.next().getAuthDetails().getIpAddress().equals(ipAddress)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery resourcePath(String resourcePath) {
|
||||
Iterator<AdminEvent> itr = this.adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if(!Pattern.compile(resourcePath).matcher(itr.next().getResourcePath()).find()) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return (AdminEventQuery) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery fromTime(Date fromTime) {
|
||||
Iterator<AdminEvent> itr = this.adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!(itr.next().getTime() >= fromTime.getTime())) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery toTime(Date toTime) {
|
||||
Iterator<AdminEvent> itr = this.adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!(itr.next().getTime() <= toTime.getTime())) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery firstResult(int result) {
|
||||
this.first = result;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery maxResults(int results) {
|
||||
this.max = results;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AdminEvent> getResultList() {
|
||||
if (adminEvents.size() < first) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
int end = first + max <= adminEvents.size() ? first + max : adminEvents.size();
|
||||
|
||||
return adminEvents.subList(first, end);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,150 +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.providers.events;
|
||||
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventQuery;
|
||||
import org.keycloak.events.EventType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class MemEventQuery implements EventQuery {
|
||||
|
||||
private List<Event> events;
|
||||
|
||||
private int first;
|
||||
private int max;
|
||||
|
||||
public MemEventQuery(List<Event> events) {
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery type(EventType... types) {
|
||||
Iterator<Event> itr = this.events.iterator();
|
||||
while (itr.hasNext()) {
|
||||
Event next = itr.next();
|
||||
boolean include = false;
|
||||
for (EventType e : types) {
|
||||
if (next.getType().equals(e)) {
|
||||
include = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!include) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery realm(String realmId) {
|
||||
Iterator<Event> itr = this.events.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!itr.next().getRealmId().equals(realmId)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery client(String clientId) {
|
||||
Iterator<Event> itr = this.events.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!itr.next().getClientId().equals(clientId)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery user(String userId) {
|
||||
Iterator<Event> itr = this.events.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!itr.next().getUserId().equals(userId)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery fromDate(Date fromDate) {
|
||||
Iterator<Event> itr = this.events.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!(itr.next().getTime() >= fromDate.getTime())) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery toDate(Date toDate) {
|
||||
Iterator<Event> itr = this.events.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!(itr.next().getTime() <= toDate.getTime())) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery ipAddress(String ipAddress) {
|
||||
Iterator<Event> itr = this.events.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (!itr.next().getIpAddress().equals(ipAddress)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery firstResult(int result) {
|
||||
this.first = result;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery maxResults(int results) {
|
||||
this.max = results;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Event> getResultList() {
|
||||
if (events.size() < first) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
int end = first + max <= events.size() ? first + max : events.size();
|
||||
|
||||
return events.subList(first, end);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,139 +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.providers.events;
|
||||
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventQuery;
|
||||
import org.keycloak.events.EventStoreProvider;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.events.admin.AdminEventQuery;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class MemEventStoreProvider implements EventStoreProvider {
|
||||
private final List<Event> events;
|
||||
private final Set<EventType> excludedEvents;
|
||||
private final List<AdminEvent> adminEvents;
|
||||
private final Set<OperationType> excludedOperations;
|
||||
|
||||
public MemEventStoreProvider(List<Event> events, Set<EventType> excludedEvents,
|
||||
List<AdminEvent> adminEvents, Set<OperationType> excludedOperations) {
|
||||
this.events = events;
|
||||
this.excludedEvents = excludedEvents;
|
||||
|
||||
this.adminEvents = adminEvents;
|
||||
this.excludedOperations = excludedOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery createQuery() {
|
||||
return new MemEventQuery(new LinkedList<>(events));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(String realmId) {
|
||||
synchronized(events) {
|
||||
Iterator<Event> itr = events.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (itr.next().getRealmId().equals(realmId)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(String realmId, long olderThan) {
|
||||
synchronized(events) {
|
||||
Iterator<Event> itr = events.iterator();
|
||||
while (itr.hasNext()) {
|
||||
Event e = itr.next();
|
||||
if (e.getRealmId().equals(realmId) && e.getTime() < olderThan) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
if (excludedEvents == null || !excludedEvents.contains(event.getType())) {
|
||||
events.add(0, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery createAdminQuery() {
|
||||
return new MemAdminEventQuery(new LinkedList<>(adminEvents));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAdmin() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAdmin(String realmId) {
|
||||
synchronized(adminEvents) {
|
||||
Iterator<AdminEvent> itr = adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (itr.next().getRealmId().equals(realmId)) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAdmin(String realmId, long olderThan) {
|
||||
synchronized(adminEvents) {
|
||||
Iterator<AdminEvent> itr = adminEvents.iterator();
|
||||
while (itr.hasNext()) {
|
||||
AdminEvent e = itr.next();
|
||||
if (e.getRealmId().equals(realmId) && e.getTime() < olderThan) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) {
|
||||
if (excludedOperations == null || !excludedOperations.contains(adminEvent.getOperationType())) {
|
||||
adminEvents.add(0, adminEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,89 +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.providers.events;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventStoreProvider;
|
||||
import org.keycloak.events.EventStoreProviderFactory;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class MemEventStoreProviderFactory implements EventStoreProviderFactory {
|
||||
|
||||
private List<Event> events;
|
||||
private Set<EventType> excludedEvents;
|
||||
private List<AdminEvent> adminEvents;
|
||||
private Set<OperationType> excludedOperations;
|
||||
|
||||
@Override
|
||||
public EventStoreProvider create(KeycloakSession session) {
|
||||
return new MemEventStoreProvider(events, excludedEvents, adminEvents, excludedOperations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
events = Collections.synchronizedList(new LinkedList<Event>());
|
||||
adminEvents = Collections.synchronizedList(new LinkedList<AdminEvent>());
|
||||
|
||||
String[] excludes = config.getArray("exclude-events");
|
||||
if (excludes != null) {
|
||||
excludedEvents = new HashSet<>();
|
||||
for (String e : excludes) {
|
||||
excludedEvents.add(EventType.valueOf(e));
|
||||
}
|
||||
}
|
||||
|
||||
String excludesOperations = config.get("excludesOperations");
|
||||
if (excludesOperations != null) {
|
||||
excludedOperations = new HashSet<>();
|
||||
for (String e : excludesOperations.split(",")) {
|
||||
excludedOperations.add(OperationType.valueOf(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
events = null;
|
||||
excludedEvents = null;
|
||||
adminEvents = null;
|
||||
excludedOperations = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "in-mem";
|
||||
}
|
||||
}
|
|
@ -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.providers.events.MemEventStoreProviderFactory
|
|
@ -31,9 +31,6 @@
|
|||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>event-listener-sysout</module>
|
||||
<module>event-store-mem</module>
|
||||
<module>authenticator</module>
|
||||
<module>rest</module>
|
||||
<module>domain-extension</module>
|
||||
</modules>
|
||||
|
|
Loading…
Reference in a new issue