Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
798fd84698
183 changed files with 5208 additions and 1649 deletions
|
@ -9,7 +9,7 @@ For more information about Keycloak visit [Keycloak homepage](http://keycloak.or
|
|||
Building
|
||||
--------
|
||||
|
||||
Ensure you have JDK 8 (or newer), Maven 3.2.1 (or newer) and Git installed
|
||||
Ensure you have JDK 8 (or newer), Maven 3.1.1 (or newer) and Git installed
|
||||
|
||||
java -version
|
||||
mvn -version
|
||||
|
|
|
@ -142,9 +142,13 @@ public class AuthenticatedActionsHandler {
|
|||
AuthorizationContext authorizationContext = policyEnforcer.enforce(facade);
|
||||
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) facade.getSecurityContext();
|
||||
|
||||
session.setAuthorizationContext(authorizationContext);
|
||||
if (session != null) {
|
||||
session.setAuthorizationContext(authorizationContext);
|
||||
|
||||
return authorizationContext.isGranted();
|
||||
return authorizationContext.isGranted();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to enforce policy decisions.", e);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.adapters.authorization;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Response;
|
||||
|
@ -66,40 +67,51 @@ public abstract class AbstractPolicyEnforcer {
|
|||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
AccessToken accessToken = httpFacade.getSecurityContext().getToken();
|
||||
Request request = httpFacade.getRequest();
|
||||
Response response = httpFacade.getResponse();
|
||||
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
|
||||
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
|
||||
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
|
||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||
if (securityContext != null) {
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken);
|
||||
if (accessToken != null) {
|
||||
Request request = httpFacade.getRequest();
|
||||
Response response = httpFacade.getResponse();
|
||||
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
|
||||
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
|
||||
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
||||
|
||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken);
|
||||
}
|
||||
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
||||
response.sendError(403, "Could not find a configuration for path [" + path + "].");
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
|
||||
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
|
||||
|
||||
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
|
||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||
response.sendError(403, "Authorization failed.");
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
||||
response.sendError(403, "Could not find a configuration for path [" + path + "].");
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
|
||||
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
|
||||
|
||||
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
|
||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||
response.sendError(403, "Authorization failed.");
|
||||
}
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
|
@ -125,14 +137,17 @@ public abstract class AbstractPolicyEnforcer {
|
|||
}
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
boolean hasPermission = false;
|
||||
|
||||
for (Permission permission : permissions) {
|
||||
if (permission.getResourceSetId() != null) {
|
||||
if (isResourcePermission(actualPathConfig, permission)) {
|
||||
hasPermission = true;
|
||||
|
||||
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
||||
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
||||
|
@ -143,11 +158,16 @@ public abstract class AbstractPolicyEnforcer {
|
|||
}
|
||||
} else {
|
||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||
hasPermission = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission && EnforcementMode.PERMISSIVE.equals(actualPathConfig.getEnforcementMode())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions);
|
||||
|
||||
return false;
|
||||
|
@ -218,6 +238,7 @@ public abstract class AbstractPolicyEnforcer {
|
|||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setParentConfig(originalConfig);
|
||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||
|
||||
this.paths.add(config);
|
||||
|
||||
|
|
|
@ -105,7 +105,16 @@ public class PolicyEnforcer {
|
|||
}
|
||||
|
||||
private List<PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||
if (enforcerConfig.getPaths().isEmpty()) {
|
||||
boolean loadPathsFromServer = true;
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
if (!PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
loadPathsFromServer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (loadPathsFromServer) {
|
||||
LOGGER.info("No path provided in configuration.");
|
||||
return configureAllPathsForResourceServer(protectedResource);
|
||||
} else {
|
||||
|
|
|
@ -105,7 +105,7 @@ public class SamlPrincipal implements Serializable, Principal {
|
|||
* @return
|
||||
*/
|
||||
public List<String> getFriendlyAttributes(String friendlyName) {
|
||||
List<String> list = friendlyAttributes.get(name);
|
||||
List<String> list = friendlyAttributes.get(friendlyName);
|
||||
if (list != null) {
|
||||
return Collections.unmodifiableList(list);
|
||||
} else {
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
<description>KeyCloak AuthZ: Client API</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.authorization.client.*
|
||||
</keycloak.osgi.export>
|
||||
|
@ -63,6 +65,14 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
@ -98,4 +108,4 @@
|
|||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
|
|
@ -48,7 +48,7 @@ public class HttpMethod<R> {
|
|||
private HttpMethodResponse<R> response;
|
||||
|
||||
public HttpMethod(Configuration configuration, RequestBuilder builder) {
|
||||
this(configuration, builder, new HashMap<>(), new HashMap<>());
|
||||
this(configuration, builder, new HashMap<String, String>(), new HashMap<String, String>());
|
||||
}
|
||||
|
||||
public HttpMethod(Configuration configuration, RequestBuilder builder, HashMap<String, String> params, HashMap<String, String> headers) {
|
||||
|
@ -155,4 +155,4 @@ public class HttpMethod<R> {
|
|||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public class HttpMethodResponse<R> {
|
|||
});
|
||||
}
|
||||
|
||||
public HttpMethodResponse<R> json(Class<R> responseType) {
|
||||
public HttpMethodResponse<R> json(final Class<R> responseType) {
|
||||
return new HttpMethodResponse<R>(this.method) {
|
||||
@Override
|
||||
public R execute() {
|
||||
|
|
|
@ -45,7 +45,7 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Role-Based";
|
||||
return "Role";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -43,7 +43,7 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "User-Based";
|
||||
return "User";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -30,7 +30,7 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Drools";
|
||||
return "Rule";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,9 +18,11 @@
|
|||
package org.keycloak;
|
||||
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessToken.Authorization;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -44,7 +46,17 @@ public class AuthorizationContext {
|
|||
}
|
||||
|
||||
public boolean hasPermission(String resourceName, String scopeName) {
|
||||
for (Permission permission : authzToken.getAuthorization().getPermissions()) {
|
||||
if (this.authzToken == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Authorization authorization = this.authzToken.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Permission permission : authorization.getPermissions()) {
|
||||
for (PathConfig pathHolder : this.paths) {
|
||||
if (pathHolder.getName().equals(resourceName)) {
|
||||
if (pathHolder.getId().equals(permission.getResourceSetId())) {
|
||||
|
@ -60,7 +72,17 @@ public class AuthorizationContext {
|
|||
}
|
||||
|
||||
public boolean hasResourcePermission(String resourceName) {
|
||||
for (Permission permission : authzToken.getAuthorization().getPermissions()) {
|
||||
if (this.authzToken == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Authorization authorization = this.authzToken.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Permission permission : authorization.getPermissions()) {
|
||||
for (PathConfig pathHolder : this.paths) {
|
||||
if (pathHolder.getName().equals(resourceName)) {
|
||||
if (pathHolder.getId().equals(permission.getResourceSetId())) {
|
||||
|
@ -74,7 +96,17 @@ public class AuthorizationContext {
|
|||
}
|
||||
|
||||
public boolean hasScopePermission(String scopeName) {
|
||||
for (Permission permission : authzToken.getAuthorization().getPermissions()) {
|
||||
if (this.authzToken == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Authorization authorization = this.authzToken.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Permission permission : authorization.getPermissions()) {
|
||||
if (permission.getScopes().contains(scopeName)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -84,7 +116,17 @@ public class AuthorizationContext {
|
|||
}
|
||||
|
||||
public List<Permission> getPermissions() {
|
||||
return this.authzToken.getAuthorization().getPermissions();
|
||||
if (this.authzToken == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Authorization authorization = this.authzToken.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(authorization.getPermissions());
|
||||
}
|
||||
|
||||
public boolean isGranted() {
|
||||
|
|
|
@ -122,6 +122,9 @@ public class PolicyEnforcerConfig {
|
|||
private List<String> scopes = Collections.emptyList();
|
||||
private String id;
|
||||
|
||||
@JsonProperty("enforcement-mode")
|
||||
private EnforcementMode enforcementMode = EnforcementMode.ENFORCING;
|
||||
|
||||
@JsonIgnore
|
||||
private PathConfig parentConfig;
|
||||
|
||||
|
@ -173,6 +176,14 @@ public class PolicyEnforcerConfig {
|
|||
return id;
|
||||
}
|
||||
|
||||
public EnforcementMode getEnforcementMode() {
|
||||
return enforcementMode;
|
||||
}
|
||||
|
||||
public void setEnforcementMode(EnforcementMode enforcementMode) {
|
||||
this.enforcementMode = enforcementMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PathConfig{" +
|
||||
|
@ -181,6 +192,7 @@ public class PolicyEnforcerConfig {
|
|||
", path='" + path + '\'' +
|
||||
", scopes=" + scopes +
|
||||
", id='" + id + '\'' +
|
||||
", enforcerMode='" + enforcementMode + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
|
4
dependencies/server-all/pom.xml
vendored
4
dependencies/server-all/pom.xml
vendored
|
@ -255,6 +255,10 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.sonatype.sisu.inject</groupId>
|
||||
<artifactId>guice-servlet</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.sonatype.plexus</groupId>
|
||||
<artifactId>plexus-cipher</artifactId>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
|
@ -39,6 +40,9 @@
|
|||
<include>org/keycloak/keycloak-as7-subsystem/**</include>
|
||||
<include>org/keycloak/keycloak-adapter-subsystem/**</include>
|
||||
<include>org/keycloak/keycloak-servlet-oauth-client/**</include>
|
||||
|
||||
<!-- Authorization -->
|
||||
<include>org/keycloak/keycloak-authz-client/**</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>**/*.war</exclude>
|
||||
|
|
|
@ -91,6 +91,10 @@
|
|||
<maven-resource group="org.keycloak" artifact="keycloak-servlet-oauth-client"/>
|
||||
</module-def>
|
||||
|
||||
<!-- Authorization -->
|
||||
<module-def name="org.keycloak.keycloak-authz-client">
|
||||
<maven-resource group="org.keycloak" artifact="keycloak-authz-client"/>
|
||||
</module-def>
|
||||
</target>
|
||||
|
||||
<target name="clean-target">
|
||||
|
|
|
@ -102,6 +102,11 @@
|
|||
<groupId>com.fasterxml.jackson.jaxrs</groupId>
|
||||
<artifactId>jackson-jaxrs-json-provider</artifactId>
|
||||
</dependency>
|
||||
<!-- Authorization -->
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-client</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
<module name="org.keycloak.keycloak-common"/>
|
||||
<module name="org.keycloak.keycloak-core"/>
|
||||
<module name="org.keycloak.keycloak-adapter-spi"/>
|
||||
<module name="org.keycloak.keycloak-authz-client"/>
|
||||
</dependencies>
|
||||
|
||||
</module>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
~ JBoss, Home of Professional Open Source.
|
||||
~ Copyright 2016 Red Hat, Inc., and individual 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.
|
||||
-->
|
||||
|
||||
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-authz-client">
|
||||
<resources>
|
||||
<!-- Insert resources here -->
|
||||
</resources>
|
||||
<dependencies>
|
||||
<module name="org.bouncycastle" />
|
||||
<module name="javax.api"/>
|
||||
<module name="javax.activation.api"/>
|
||||
<module name="sun.jdk" optional="true" />
|
||||
<module name="javax.ws.rs.api"/>
|
||||
<module name="org.keycloak.keycloak-core"/>
|
||||
<module name="org.keycloak.keycloak-common"/>
|
||||
<module name="org.apache.httpcomponents"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-core"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-annotations"/>
|
||||
<module name="com.fasterxml.jackson.core.jackson-databind"/>
|
||||
<module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/>
|
||||
</dependencies>
|
||||
|
||||
</module>
|
|
@ -39,6 +39,9 @@
|
|||
<include>org/keycloak/keycloak-as7-subsystem/**</include>
|
||||
<include>org/keycloak/keycloak-adapter-subsystem/**</include>
|
||||
<include>org/keycloak/keycloak-servlet-oauth-client/**</include>
|
||||
|
||||
<!-- Authorization -->
|
||||
<include>org/keycloak/keycloak-authz-client/**</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>**/*.war</exclude>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<resources>
|
||||
<artifact name="${org.eclipse.sisu:org.eclipse.sisu.inject}"/>
|
||||
<artifact name="${org.eclipse.sisu:org.eclipse.sisu.plexus}"/>
|
||||
<artifact name="${org.sonatype.sisu.inject:guice-servlet}"/>
|
||||
</resources>
|
||||
<dependencies>
|
||||
<module name="javax.api"/>
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
<module name="org.keycloak.keycloak-server-spi"/>
|
||||
<module name="org.keycloak.keycloak-server-spi-private"/>
|
||||
<module name="org.infinispan"/>
|
||||
<module name="org.infinispan.commons"/>
|
||||
<module name="org.infinispan.cachestore.remote"/>
|
||||
<module name="org.infinispan.client.hotrod"/>
|
||||
<module name="org.jboss.logging"/>
|
||||
<module name="javax.api"/>
|
||||
</dependencies>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<module name="org.keycloak.keycloak-js-adapter" services="import"/>
|
||||
<module name="org.keycloak.keycloak-kerberos-federation" services="import"/>
|
||||
<module name="org.keycloak.keycloak-ldap-federation" services="import"/>
|
||||
<module name="org.keycloak.keycloak-sssd-federation" services="import"/>
|
||||
<module name="org.keycloak.keycloak-sssd-federation" optional="true" services="import"/>
|
||||
<module name="org.keycloak.keycloak-server-spi" services="import"/>
|
||||
<module name="org.keycloak.keycloak-server-spi-private" services="import"/>
|
||||
<module name="org.keycloak.keycloak-model-jpa" services="import"/>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
<resources>
|
||||
<artifact name="${org.keycloak:keycloak-sssd-federation}"/>
|
||||
<resource-root path="/usr/share/java/jna.jar"/>
|
||||
</resources>
|
||||
|
||||
<dependencies>
|
||||
|
|
|
@ -2,9 +2,9 @@ embed-server --server-config=standalone-ha.xml
|
|||
/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",jta=false,driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true)
|
||||
/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak")
|
||||
/subsystem=infinispan/cache-container=keycloak/transport=TRANSPORT:add(lock-timeout=60000)
|
||||
/subsystem=infinispan/cache-container=keycloak/invalidation-cache=realms:add(mode="SYNC")
|
||||
/subsystem=infinispan/cache-container=keycloak/invalidation-cache=users:add(mode="SYNC")
|
||||
/subsystem=infinispan/cache-container=keycloak/invalidation-cache=users/eviction=EVICTION:add(max-entries=10000,strategy=LRU)
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add()
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=users:add()
|
||||
/subsystem=infinispan/cache-container=keycloak/local-cache=users/eviction=EVICTION:add(max-entries=10000,strategy=LRU)
|
||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1")
|
||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:add(mode="SYNC",owners="1")
|
||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1")
|
||||
|
|
|
@ -12,5 +12,5 @@ Then registering the provider by editing `standalone/configuration/standalone.xm
|
|||
<provider>module:org.keycloak.examples.hello-rest-example</provider>
|
||||
</providers>
|
||||
|
||||
Then start (or restart) the server. Once started open http://localhost:8080/realms/master/hello and you should see the message _Hello master_.
|
||||
You can also invoke the endpoint for other realms by replacing `master` with the realm name in the above url.
|
||||
Then start (or restart) the server. Once started open http://localhost:8080/auth/realms/master/hello and you should see the message _Hello master_.
|
||||
You can also invoke the endpoint for other realms by replacing `master` with the realm name in the above url.
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<jboss-deployment-structure>
|
||||
<deployment>
|
||||
<dependencies>
|
||||
<module name="org.apache.httpcomponents" />
|
||||
</dependencies>
|
||||
</deployment>
|
||||
</jboss-deployment-structure>
|
|
@ -49,6 +49,7 @@
|
|||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
|
@ -70,10 +71,6 @@
|
|||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.jnr</groupId>
|
||||
<artifactId>jnr-unixsocket</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 cx.ath.matthew;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
|
||||
*/
|
||||
public class LibraryLoader {
|
||||
|
||||
private static final String[] PATHS = {"/usr/lib/", "/usr/lib64/", "/usr/local/lib/", "/opt/local/lib/"};
|
||||
private static final String LIBRARY_NAME = "libunix_dbus_java";
|
||||
private static final String VERSION = "0.0.8";
|
||||
private static boolean loadSucceeded;
|
||||
|
||||
public static LibraryLoader load() {
|
||||
for (String path : PATHS) {
|
||||
try {
|
||||
System.load(String.format("%s/%s.so.%s", path, LIBRARY_NAME, VERSION));
|
||||
loadSucceeded = true;
|
||||
break;
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
loadSucceeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
return new LibraryLoader();
|
||||
}
|
||||
|
||||
public boolean succeed() {
|
||||
return loadSucceeded;
|
||||
}
|
||||
}
|
|
@ -26,25 +26,25 @@
|
|||
*/
|
||||
package cx.ath.matthew.unix;
|
||||
|
||||
import jnr.unixsocket.UnixSocketChannel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.Channels;
|
||||
|
||||
public class USInputStream extends InputStream {
|
||||
public static final int MSG_DONTWAIT = 0x40;
|
||||
private UnixSocketChannel channel;
|
||||
|
||||
private native int native_recv(int sock, byte[] b, int off, int len, int flags, int timeout) throws IOException;
|
||||
|
||||
private int sock;
|
||||
boolean closed = false;
|
||||
private byte[] onebuf = new byte[1];
|
||||
private UnixSocket us;
|
||||
private boolean blocking = true;
|
||||
private int flags = 0;
|
||||
private int timeout = 0;
|
||||
|
||||
public USInputStream(UnixSocketChannel channel, UnixSocket us) {
|
||||
public USInputStream(int sock, UnixSocket us) {
|
||||
this.sock = sock;
|
||||
this.us = us;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
|
@ -65,8 +65,7 @@ public class USInputStream extends InputStream {
|
|||
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (closed) throw new NotConnectedException();
|
||||
int count = receive(b, off, len);
|
||||
|
||||
int count = native_recv(sock, b, off, len, flags, timeout);
|
||||
/* Yes, I really want to do this. Recv returns 0 for 'connection shut down'.
|
||||
* read() returns -1 for 'end of stream.
|
||||
* Recv returns -1 for 'EAGAIN' (all other errors cause an exception to be raised)
|
||||
|
@ -92,21 +91,4 @@ public class USInputStream extends InputStream {
|
|||
public void setSoTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/*
|
||||
* Taken from JRuby with small modifications
|
||||
* @see <a href="https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/ext/socket/RubyUNIXSocket.java">RubyUNIXSocket.java</a>
|
||||
*/
|
||||
private int receive(byte[] dataBytes, int off, int len) {
|
||||
int recvStatus = -1;
|
||||
try {
|
||||
InputStream inputStream = Channels.newInputStream(channel);
|
||||
recvStatus = inputStream.read(dataBytes, off, len);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return recvStatus;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,31 +26,22 @@
|
|||
*/
|
||||
package cx.ath.matthew.unix;
|
||||
|
||||
import jnr.constants.platform.linux.SocketLevel;
|
||||
import jnr.posix.CmsgHdr;
|
||||
import jnr.posix.MsgHdr;
|
||||
import jnr.posix.POSIX;
|
||||
import jnr.posix.POSIXFactory;
|
||||
import jnr.unixsocket.UnixSocketChannel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class USOutputStream extends OutputStream {
|
||||
private native int native_send(int sock, byte[] b, int off, int len) throws IOException;
|
||||
|
||||
private UnixSocketChannel channel;
|
||||
private native int native_send(int sock, byte[][] b) throws IOException;
|
||||
|
||||
private int sock;
|
||||
boolean closed = false;
|
||||
private byte[] onebuf = new byte[1];
|
||||
private UnixSocket us;
|
||||
|
||||
public USOutputStream(UnixSocketChannel channel, int sock, UnixSocket us) {
|
||||
public USOutputStream(int sock, UnixSocket us) {
|
||||
this.sock = sock;
|
||||
this.us = us;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
|
@ -61,9 +52,14 @@ public class USOutputStream extends OutputStream {
|
|||
public void flush() {
|
||||
} // no-op, we do not buffer
|
||||
|
||||
public void write(byte[][] b) throws IOException {
|
||||
if (closed) throw new NotConnectedException();
|
||||
native_send(sock, b);
|
||||
}
|
||||
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
if (closed) throw new NotConnectedException();
|
||||
send(sock, b, off, len);
|
||||
native_send(sock, b, off, len);
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
|
@ -79,46 +75,4 @@ public class USOutputStream extends OutputStream {
|
|||
public UnixSocket getSocket() {
|
||||
return us;
|
||||
}
|
||||
|
||||
/*
|
||||
* Taken from JRuby with small modifications
|
||||
* @see <a href="https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/ext/socket/RubyUNIXSocket.java">RubyUNIXSocket.java</a>
|
||||
*/
|
||||
private void send(int sock, ByteBuffer[] outIov) {
|
||||
|
||||
final POSIX posix = POSIXFactory.getNativePOSIX();
|
||||
MsgHdr outMessage = posix.allocateMsgHdr();
|
||||
|
||||
outMessage.setIov(outIov);
|
||||
|
||||
CmsgHdr outControl = outMessage.allocateControl(4);
|
||||
outControl.setLevel(SocketLevel.SOL_SOCKET.intValue());
|
||||
outControl.setType(0x01);
|
||||
|
||||
ByteBuffer fdBuf = ByteBuffer.allocateDirect(4);
|
||||
fdBuf.order(ByteOrder.nativeOrder());
|
||||
fdBuf.putInt(0, channel.getFD());
|
||||
outControl.setData(fdBuf);
|
||||
|
||||
posix.sendmsg(sock, outMessage, 0);
|
||||
|
||||
}
|
||||
|
||||
private void send(int sock, byte[] dataBytes, int off, int len) {
|
||||
ByteBuffer[] outIov = new ByteBuffer[1];
|
||||
outIov[0] = ByteBuffer.allocateDirect(dataBytes.length);
|
||||
outIov[0].put(dataBytes, off, len);
|
||||
outIov[0].flip();
|
||||
|
||||
send(sock, outIov);
|
||||
}
|
||||
|
||||
protected void send(int sock, byte[] dataBytes) {
|
||||
ByteBuffer[] outIov = new ByteBuffer[1];
|
||||
outIov[0] = ByteBuffer.allocateDirect(dataBytes.length);
|
||||
outIov[0].put(dataBytes);
|
||||
outIov[0].flip();
|
||||
|
||||
send(sock, outIov);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Java Unix Sockets Library
|
||||
*
|
||||
* Copyright (c) Matthew Johnson 2004
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* To Contact the author, please email src@matthew.ath.cx
|
||||
*
|
||||
*/
|
||||
package cx.ath.matthew.unix;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An IO Exception which occurred during UNIX Socket IO
|
||||
*/
|
||||
public class UnixIOException extends IOException {
|
||||
private int no;
|
||||
private String message;
|
||||
|
||||
public UnixIOException(int no, String message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
this.no = no;
|
||||
}
|
||||
}
|
|
@ -26,11 +26,9 @@
|
|||
*/
|
||||
package cx.ath.matthew.unix;
|
||||
|
||||
import cx.ath.matthew.LibraryLoader;
|
||||
import cx.ath.matthew.debug.Debug;
|
||||
import jnr.unixsocket.UnixSocketAddress;
|
||||
import jnr.unixsocket.UnixSocketChannel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
@ -39,8 +37,25 @@ import java.io.OutputStream;
|
|||
* Represents a UnixSocket.
|
||||
*/
|
||||
public class UnixSocket {
|
||||
static {
|
||||
LibraryLoader.load();
|
||||
}
|
||||
|
||||
private UnixSocketChannel channel;
|
||||
private native void native_set_pass_cred(int sock, boolean passcred) throws IOException;
|
||||
|
||||
private native int native_connect(String address, boolean abs) throws IOException;
|
||||
|
||||
private native void native_close(int sock) throws IOException;
|
||||
|
||||
private native int native_getPID(int sock);
|
||||
|
||||
private native int native_getUID(int sock);
|
||||
|
||||
private native int native_getGID(int sock);
|
||||
|
||||
private native void native_send_creds(int sock, byte data) throws IOException;
|
||||
|
||||
private native byte native_recv_creds(int sock, int[] creds) throws IOException;
|
||||
|
||||
private UnixSocketAddress address = null;
|
||||
private USOutputStream os = null;
|
||||
|
@ -58,8 +73,8 @@ public class UnixSocket {
|
|||
this.sock = sock;
|
||||
this.address = address;
|
||||
this.connected = true;
|
||||
this.os = new USOutputStream(channel, sock, this);
|
||||
this.is = new USInputStream(channel, this);
|
||||
this.os = new USOutputStream(sock, this);
|
||||
this.is = new USInputStream(sock, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,7 +98,7 @@ public class UnixSocket {
|
|||
* @param address The Unix Socket address to connect to
|
||||
*/
|
||||
public UnixSocket(String address) throws IOException {
|
||||
this(new UnixSocketAddress(new File(address)));
|
||||
this(new UnixSocketAddress(address));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,11 +108,9 @@ public class UnixSocket {
|
|||
*/
|
||||
public void connect(UnixSocketAddress address) throws IOException {
|
||||
if (connected) close();
|
||||
this.channel = UnixSocketChannel.open(address);
|
||||
this.channel = UnixSocketChannel.open(address);
|
||||
this.sock = channel.getFD();
|
||||
this.os = new USOutputStream(channel, sock, this);
|
||||
this.is = new USInputStream(channel, this);
|
||||
this.sock = native_connect(address.path, address.abs);
|
||||
this.os = new USOutputStream(this.sock, this);
|
||||
this.is = new USInputStream(this.sock, this);
|
||||
this.address = address;
|
||||
this.connected = true;
|
||||
this.closed = false;
|
||||
|
@ -110,7 +123,7 @@ public class UnixSocket {
|
|||
* @param address The Unix Socket address to connect to
|
||||
*/
|
||||
public void connect(String address) throws IOException {
|
||||
connect(new UnixSocketAddress(new File(address)));
|
||||
connect(new UnixSocketAddress(address));
|
||||
}
|
||||
|
||||
public void finalize() {
|
||||
|
@ -125,7 +138,7 @@ public class UnixSocket {
|
|||
*/
|
||||
public synchronized void close() throws IOException {
|
||||
if (Debug.debug) Debug.print(Debug.INFO, "Closing socket");
|
||||
channel.close();
|
||||
native_close(sock);
|
||||
sock = 0;
|
||||
this.closed = true;
|
||||
this.connected = false;
|
||||
|
@ -169,7 +182,91 @@ public class UnixSocket {
|
|||
*/
|
||||
public void sendCredentialByte(byte data) throws IOException {
|
||||
if (!connected) throw new NotConnectedException();
|
||||
os.send(channel.getFD(), new byte[]{ data });
|
||||
native_send_creds(sock, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a single byte of data, with credentials.
|
||||
* (Works on BSDs)
|
||||
*
|
||||
* @param data The byte of data to send.
|
||||
* @see getPeerUID
|
||||
* @see getPeerPID
|
||||
* @see getPeerGID
|
||||
*/
|
||||
public byte recvCredentialByte() throws IOException {
|
||||
if (!connected) throw new NotConnectedException();
|
||||
int[] creds = new int[]{-1, -1, -1};
|
||||
byte data = native_recv_creds(sock, creds);
|
||||
pid = creds[0];
|
||||
uid = creds[1];
|
||||
gid = creds[2];
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the credential passing status.
|
||||
* (only effective on linux)
|
||||
*
|
||||
* @return The current status of credential passing.
|
||||
* @see setPassCred
|
||||
*/
|
||||
public boolean getPassCred() {
|
||||
return passcred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the uid of the remote process.
|
||||
* Some data must have been received on the socket to do this.
|
||||
* Either setPassCred must be called on Linux first, or recvCredentialByte
|
||||
* on BSD.
|
||||
*
|
||||
* @return the UID or -1 if it is not available
|
||||
*/
|
||||
public int getPeerUID() {
|
||||
if (-1 == uid)
|
||||
uid = native_getUID(sock);
|
||||
return uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the gid of the remote process.
|
||||
* Some data must have been received on the socket to do this.
|
||||
* Either setPassCred must be called on Linux first, or recvCredentialByte
|
||||
* on BSD.
|
||||
*
|
||||
* @return the GID or -1 if it is not available
|
||||
*/
|
||||
public int getPeerGID() {
|
||||
if (-1 == gid)
|
||||
gid = native_getGID(sock);
|
||||
return gid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the pid of the remote process.
|
||||
* Some data must have been received on the socket to do this.
|
||||
* Either setPassCred must be called on Linux first, or recvCredentialByte
|
||||
* on BSD.
|
||||
*
|
||||
* @return the PID or -1 if it is not available
|
||||
*/
|
||||
public int getPeerPID() {
|
||||
if (-1 == pid)
|
||||
pid = native_getPID(sock);
|
||||
return pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the credential passing status.
|
||||
* (Only does anything on linux, for other OS, you need
|
||||
* to use send/recv credentials)
|
||||
*
|
||||
* @param enable Set to true for credentials to be passed.
|
||||
*/
|
||||
public void setPassCred(boolean enable) throws IOException {
|
||||
native_set_pass_cred(sock, enable);
|
||||
passcred = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Java Unix Sockets Library
|
||||
*
|
||||
* Copyright (c) Matthew Johnson 2004
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* To Contact the author, please email src@matthew.ath.cx
|
||||
*
|
||||
*/
|
||||
package cx.ath.matthew.unix;
|
||||
|
||||
/**
|
||||
* Represents an address for a Unix Socket
|
||||
*/
|
||||
public class UnixSocketAddress {
|
||||
String path;
|
||||
boolean abs;
|
||||
|
||||
/**
|
||||
* Create the address.
|
||||
*
|
||||
* @param path The path to the Unix Socket.
|
||||
* @param abs True if this should be an abstract socket.
|
||||
*/
|
||||
public UnixSocketAddress(String path, boolean abs) {
|
||||
this.path = path;
|
||||
this.abs = abs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the address.
|
||||
*
|
||||
* @param path The path to the Unix Socket.
|
||||
*/
|
||||
public UnixSocketAddress(String path) {
|
||||
this.path = path;
|
||||
this.abs = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path.
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this an address for an abstract socket.
|
||||
*/
|
||||
public boolean isAbstract() {
|
||||
return abs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Address as a String.
|
||||
*/
|
||||
public String toString() {
|
||||
return "unix" + (abs ? ":abstract" : "") + ":path=" + path;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof UnixSocketAddress)) return false;
|
||||
return ((UnixSocketAddress) o).path.equals(this.path);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return path.hashCode();
|
||||
}
|
||||
}
|
|
@ -43,12 +43,20 @@ public class MessageWriter {
|
|||
if (Debug.debug) Debug.print(Debug.WARN, "Message " + m + " wire-data was null!");
|
||||
return;
|
||||
}
|
||||
for (byte[] buf : m.getWireData()) {
|
||||
if (Debug.debug)
|
||||
Debug.print(Debug.VERBOSE, "(" + buf + "):" + (null == buf ? "" : Hexdump.format(buf)));
|
||||
if (null == buf) break;
|
||||
out.write(buf);
|
||||
}
|
||||
if (isunix) {
|
||||
if (Debug.debug) {
|
||||
Debug.print(Debug.DEBUG, "Writing all " + m.getWireData().length + " buffers simultaneously to Unix Socket");
|
||||
for (byte[] buf : m.getWireData())
|
||||
Debug.print(Debug.VERBOSE, "(" + buf + "):" + (null == buf ? "" : Hexdump.format(buf)));
|
||||
}
|
||||
((USOutputStream) out).write(m.getWireData());
|
||||
} else
|
||||
for (byte[] buf : m.getWireData()) {
|
||||
if (Debug.debug)
|
||||
Debug.print(Debug.VERBOSE, "(" + buf + "):" + (null == buf ? "" : Hexdump.format(buf)));
|
||||
if (null == buf) break;
|
||||
out.write(buf);
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ package org.freedesktop.dbus;
|
|||
|
||||
import cx.ath.matthew.debug.Debug;
|
||||
import cx.ath.matthew.unix.UnixSocket;
|
||||
import cx.ath.matthew.unix.UnixSocketAddress;
|
||||
import cx.ath.matthew.utils.Hexdump;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
@ -25,6 +26,7 @@ import java.io.OutputStream;
|
|||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -255,8 +257,10 @@ public class Transport {
|
|||
return new String(res);
|
||||
}
|
||||
|
||||
public static final int MODE_SERVER = 1;
|
||||
public static final int MODE_CLIENT = 2;
|
||||
|
||||
public static final int AUTH_NONE = 0;
|
||||
public static final int AUTH_EXTERNAL = 1;
|
||||
public static final int AUTH_SHA = 2;
|
||||
public static final int AUTH_ANON = 4;
|
||||
|
@ -273,12 +277,15 @@ public class Transport {
|
|||
public static final int WAIT_DATA = 1;
|
||||
public static final int WAIT_OK = 2;
|
||||
public static final int WAIT_REJECT = 3;
|
||||
public static final int WAIT_AUTH = 4;
|
||||
public static final int WAIT_BEGIN = 5;
|
||||
public static final int AUTHENTICATED = 6;
|
||||
public static final int FAILED = 7;
|
||||
|
||||
public static final int OK = 1;
|
||||
public static final int CONTINUE = 2;
|
||||
public static final int ERROR = 3;
|
||||
public static final int REJECT = 4;
|
||||
|
||||
public Command receive(InputStream s) throws IOException {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
@ -388,8 +395,89 @@ public class Transport {
|
|||
}
|
||||
}
|
||||
|
||||
public String challenge = "";
|
||||
public String cookie = "";
|
||||
|
||||
public int do_response(int auth, String Uid, String kernelUid, Command c) {
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA");
|
||||
} catch (NoSuchAlgorithmException NSAe) {
|
||||
if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe);
|
||||
return ERROR;
|
||||
}
|
||||
switch (auth) {
|
||||
case AUTH_NONE:
|
||||
switch (c.getMechs()) {
|
||||
case AUTH_ANON:
|
||||
return OK;
|
||||
case AUTH_EXTERNAL:
|
||||
if (0 == col.compare(Uid, c.getData()) &&
|
||||
(null == kernelUid || 0 == col.compare(Uid, kernelUid)))
|
||||
return OK;
|
||||
else
|
||||
return ERROR;
|
||||
case AUTH_SHA:
|
||||
String context = COOKIE_CONTEXT;
|
||||
long id = System.currentTimeMillis();
|
||||
byte[] buf = new byte[8];
|
||||
Message.marshallintBig(id, buf, 0, 8);
|
||||
challenge = stupidlyEncode(md.digest(buf));
|
||||
Random r = new Random();
|
||||
r.nextBytes(buf);
|
||||
cookie = stupidlyEncode(md.digest(buf));
|
||||
try {
|
||||
addCookie(context, "" + id, id / 1000, cookie);
|
||||
} catch (IOException IOe) {
|
||||
if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe);
|
||||
}
|
||||
if (Debug.debug)
|
||||
Debug.print(Debug.DEBUG, "Sending challenge: " + context + ' ' + id + ' ' + challenge);
|
||||
c.setResponse(stupidlyEncode(context + ' ' + id + ' ' + challenge));
|
||||
return CONTINUE;
|
||||
default:
|
||||
return ERROR;
|
||||
}
|
||||
case AUTH_SHA:
|
||||
String[] response = stupidlyDecode(c.getData()).split(" ");
|
||||
if (response.length < 2) return ERROR;
|
||||
String cchal = response[0];
|
||||
String hash = response[1];
|
||||
String prehash = challenge + ":" + cchal + ":" + cookie;
|
||||
byte[] buf = md.digest(prehash.getBytes());
|
||||
String posthash = stupidlyEncode(buf);
|
||||
if (Debug.debug)
|
||||
Debug.print(Debug.DEBUG, "Authenticating Hash; data=" + prehash + " remote hash=" + hash + " local hash=" + posthash);
|
||||
if (0 == col.compare(posthash, hash))
|
||||
return OK;
|
||||
else
|
||||
return ERROR;
|
||||
default:
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getTypes(int types) {
|
||||
switch (types) {
|
||||
case AUTH_EXTERNAL:
|
||||
return new String[]{"EXTERNAL"};
|
||||
case AUTH_SHA:
|
||||
return new String[]{"DBUS_COOKIE_SHA1"};
|
||||
case AUTH_ANON:
|
||||
return new String[]{"ANONYMOUS"};
|
||||
case AUTH_SHA + AUTH_EXTERNAL:
|
||||
return new String[]{"EXTERNAL", "DBUS_COOKIE_SHA1"};
|
||||
case AUTH_SHA + AUTH_ANON:
|
||||
return new String[]{"ANONYMOUS", "DBUS_COOKIE_SHA1"};
|
||||
case AUTH_EXTERNAL + AUTH_ANON:
|
||||
return new String[]{"ANONYMOUS", "EXTERNAL"};
|
||||
case AUTH_EXTERNAL + AUTH_ANON + AUTH_SHA:
|
||||
return new String[]{"ANONYMOUS", "EXTERNAL", "DBUS_COOKIE_SHA1"};
|
||||
default:
|
||||
return new String[]{};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* performs SASL auth on the given streams.
|
||||
* Mode selects whether to run as a SASL server or client.
|
||||
|
@ -400,6 +488,7 @@ public class Transport {
|
|||
public boolean auth(int mode, int types, String guid, OutputStream out, InputStream in, UnixSocket us) throws IOException {
|
||||
String username = System.getProperty("user.name");
|
||||
String Uid = null;
|
||||
String kernelUid = null;
|
||||
try {
|
||||
Class c = Class.forName("com.sun.security.auth.module.UnixSystem");
|
||||
Method m = c.getMethod("getUid");
|
||||
|
@ -529,6 +618,110 @@ public class Transport {
|
|||
state = FAILED;
|
||||
}
|
||||
break;
|
||||
case MODE_SERVER:
|
||||
switch (state) {
|
||||
case INITIAL_STATE:
|
||||
byte[] buf = new byte[1];
|
||||
if (null == us) {
|
||||
in.read(buf);
|
||||
} else {
|
||||
buf[0] = us.recvCredentialByte();
|
||||
int kuid = us.getPeerUID();
|
||||
if (kuid >= 0)
|
||||
kernelUid = stupidlyEncode("" + kuid);
|
||||
}
|
||||
if (0 != buf[0]) state = FAILED;
|
||||
else state = WAIT_AUTH;
|
||||
break;
|
||||
case WAIT_AUTH:
|
||||
c = receive(in);
|
||||
switch (c.getCommand()) {
|
||||
case COMMAND_AUTH:
|
||||
if (null == c.getData()) {
|
||||
send(out, COMMAND_REJECTED, getTypes(types));
|
||||
} else {
|
||||
switch (do_response(current, Uid, kernelUid, c)) {
|
||||
case CONTINUE:
|
||||
send(out, COMMAND_DATA, c.getResponse());
|
||||
current = c.getMechs();
|
||||
state = WAIT_DATA;
|
||||
break;
|
||||
case OK:
|
||||
send(out, COMMAND_OK, guid);
|
||||
state = WAIT_BEGIN;
|
||||
current = 0;
|
||||
break;
|
||||
case REJECT:
|
||||
send(out, COMMAND_REJECTED, getTypes(types));
|
||||
current = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case COMMAND_ERROR:
|
||||
send(out, COMMAND_REJECTED, getTypes(types));
|
||||
break;
|
||||
case COMMAND_BEGIN:
|
||||
state = FAILED;
|
||||
break;
|
||||
default:
|
||||
send(out, COMMAND_ERROR, "Got invalid command");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case WAIT_DATA:
|
||||
c = receive(in);
|
||||
switch (c.getCommand()) {
|
||||
case COMMAND_DATA:
|
||||
switch (do_response(current, Uid, kernelUid, c)) {
|
||||
case CONTINUE:
|
||||
send(out, COMMAND_DATA, c.getResponse());
|
||||
state = WAIT_DATA;
|
||||
break;
|
||||
case OK:
|
||||
send(out, COMMAND_OK, guid);
|
||||
state = WAIT_BEGIN;
|
||||
current = 0;
|
||||
break;
|
||||
case REJECT:
|
||||
send(out, COMMAND_REJECTED, getTypes(types));
|
||||
current = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case COMMAND_ERROR:
|
||||
case COMMAND_CANCEL:
|
||||
send(out, COMMAND_REJECTED, getTypes(types));
|
||||
state = WAIT_AUTH;
|
||||
break;
|
||||
case COMMAND_BEGIN:
|
||||
state = FAILED;
|
||||
break;
|
||||
default:
|
||||
send(out, COMMAND_ERROR, "Got invalid command");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case WAIT_BEGIN:
|
||||
c = receive(in);
|
||||
switch (c.getCommand()) {
|
||||
case COMMAND_ERROR:
|
||||
case COMMAND_CANCEL:
|
||||
send(out, COMMAND_REJECTED, getTypes(types));
|
||||
state = WAIT_AUTH;
|
||||
break;
|
||||
case COMMAND_BEGIN:
|
||||
state = AUTHENTICATED;
|
||||
break;
|
||||
default:
|
||||
send(out, COMMAND_ERROR, "Got invalid command");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
state = FAILED;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -588,15 +781,25 @@ public class Transport {
|
|||
types = SASL.AUTH_EXTERNAL;
|
||||
mode = SASL.MODE_CLIENT;
|
||||
us = new UnixSocket();
|
||||
if (null != address.getParameter("path"))
|
||||
us.connect(new jnr.unixsocket.UnixSocketAddress(new File(address.getParameter("path"))));
|
||||
if (null != address.getParameter("abstract"))
|
||||
us.connect(new UnixSocketAddress(address.getParameter("abstract"), true));
|
||||
else if (null != address.getParameter("path"))
|
||||
us.connect(new UnixSocketAddress(address.getParameter("path"), false));
|
||||
us.setPassCred(true);
|
||||
in = us.getInputStream();
|
||||
out = us.getOutputStream();
|
||||
} else if ("tcp".equals(address.getType())) {
|
||||
types = SASL.AUTH_SHA;
|
||||
mode = SASL.MODE_CLIENT;
|
||||
s = new Socket();
|
||||
s.connect(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port"))));
|
||||
if (null != address.getParameter("listen")) {
|
||||
mode = SASL.MODE_SERVER;
|
||||
ServerSocket ss = new ServerSocket();
|
||||
ss.bind(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port"))));
|
||||
s = ss.accept();
|
||||
} else {
|
||||
mode = SASL.MODE_CLIENT;
|
||||
s = new Socket();
|
||||
s.connect(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port"))));
|
||||
}
|
||||
in = s.getInputStream();
|
||||
out = s.getOutputStream();
|
||||
} else {
|
||||
|
|
116
misc/CrossDataCenter.md
Normal file
116
misc/CrossDataCenter.md
Normal file
|
@ -0,0 +1,116 @@
|
|||
Test Cross-Data-Center scenario (test with external JDG server)
|
||||
===============================================================
|
||||
|
||||
These are temporary notes. This docs should be removed once we have cross-DC support finished and properly documented.
|
||||
|
||||
What is working right now is:
|
||||
- Propagating of invalidation messages for "realms" and "users" caches
|
||||
- All the other things provided by ClusterProvider, which is:
|
||||
-- ClusterStartupTime (used for offlineSessions and revokeRefreshToken) is shared for all clusters in all datacenters
|
||||
-- Periodic userStorage synchronization is always executed just on one node at a time. It won't be never executed concurrently on more nodes (Assuming "nodes" refer to all servers in all clusters in all datacenters)
|
||||
|
||||
What doesn't work right now:
|
||||
- UserSessionProvider and offline sessions
|
||||
|
||||
|
||||
Basic setup
|
||||
===========
|
||||
|
||||
This is setup with 2 keycloak nodes, which are NOT in cluster. They just share the same database and they will be configured with "work" infinispan cache with remoteStore, which will point
|
||||
to external JDG server.
|
||||
|
||||
JDG Server setup
|
||||
----------------
|
||||
- Download JDG 7.0 server and unzip to some folder
|
||||
|
||||
- Add this into JDG_HOME/standalone/configuration/standalone.xml under cache-container named "local" :
|
||||
|
||||
```
|
||||
<local-cache name="work" start="EAGER" batching="false" />
|
||||
```
|
||||
|
||||
- Start server:
|
||||
```
|
||||
cd JDG_HOME/bin
|
||||
./standalone.sh -Djboss.socket.binding.port-offset=100
|
||||
```
|
||||
|
||||
Keycloak servers setup
|
||||
----------------------
|
||||
You need to setup 2 Keycloak nodes in this way.
|
||||
|
||||
For now, it's recommended to test Keycloak overlay on EAP7 because of infinispan bug, which is fixed in EAP 7.0 (infinispan 8.1.2), but not
|
||||
yet on Wildfly 10 (infinispan 8.1.0). See below for details.
|
||||
|
||||
1) Configure shared database in KEYCLOAK_HOME/standalone/configuration/standalone.xml . For example MySQL
|
||||
|
||||
2) Add `module` attribute to the infinispan keycloak container:
|
||||
|
||||
```
|
||||
<cache-container name="keycloak" jndi-name="infinispan/Keycloak" module="org.keycloak.keycloak-model-infinispan">
|
||||
```
|
||||
|
||||
3) Configure `work` cache to use remoteStore. You should use this:
|
||||
|
||||
```
|
||||
<local-cache name="work">
|
||||
<remote-store passivation="false" fetch-state="false" purge="false" preload="false" shared="true" cache="work" remote-servers="remote-cache">
|
||||
<property name="rawValues">true</property>
|
||||
<property name="marshaller">org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory</property>
|
||||
</remote-store>
|
||||
</local-cache>
|
||||
```
|
||||
|
||||
4) Configure connection to the external JDG server. Because we used port offset 100 for JDG (see above), the HotRod endpoint is running on 11322 .
|
||||
So add the config like this to the bottom of standalone.xml under `socket-binding-group` element:
|
||||
|
||||
```
|
||||
<outbound-socket-binding name="remote-cache">
|
||||
<remote-destination host="localhost" port="11322"/>
|
||||
</outbound-socket-binding>
|
||||
```
|
||||
|
||||
5) Optional: Configure logging in standalone.xml to see what invalidation events were send:
|
||||
````
|
||||
<logger category="org.keycloak.cluster.infinispan">
|
||||
<level name="TRACE"/>
|
||||
</logger>
|
||||
<logger category="org.keycloak.models.cache.infinispan">
|
||||
<level name="DEBUG"/>
|
||||
</logger>
|
||||
````
|
||||
|
||||
6) Setup Keycloak node2 . Just copy Keycloak to another location on your laptop and repeat steps 1-5 above for second server too.
|
||||
|
||||
7) Run server 1 with parameters like (assuming you have virtual hosts "node1" and "node2" defined in your `/etc/hosts` ):
|
||||
```
|
||||
./standalone.sh -Djboss.node.name=node1 -b node1 -bmanagement node1
|
||||
```
|
||||
|
||||
and server2 with:
|
||||
```
|
||||
./standalone.sh -Djboss.node.name=node2 -b node2 -bmanagement node2
|
||||
```
|
||||
|
||||
8) Note something like this in both `KEYCLOAK_HOME/standalone/log/server.log` on both nodes. Note that cluster Startup Time will be same time on both nodes:
|
||||
```
|
||||
2016-11-16 22:12:52,080 DEBUG [org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory] (ServerService Thread Pool -- 62) My address: node1-1953169551
|
||||
2016-11-16 22:12:52,081 DEBUG [org.keycloak.cluster.infinispan.CrossDCAwareCacheFactory] (ServerService Thread Pool -- 62) RemoteStore is available. Cross-DC scenario will be used
|
||||
2016-11-16 22:12:52,119 DEBUG [org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory] (ServerService Thread Pool -- 62) Loaded cluster startup time: Wed Nov 16 22:09:48 CET 2016
|
||||
2016-11-16 22:12:52,128 DEBUG [org.keycloak.cluster.infinispan.InfinispanNotificationsManager] (ServerService Thread Pool -- 62) Added listener for HotRod remoteStore cache: work
|
||||
```
|
||||
|
||||
9) Login to node1. Then change any realm on node2. You will see in the node2 server.log that RealmUpdatedEvent was sent and on node1 that this event was received.
|
||||
|
||||
This is done even if node1 and node2 are NOT in cluster as it's the external JDG used for communication between 2 keycloak servers and sending/receiving cache invalidation events. But note that userSession
|
||||
doesn't yet work (eg. if you login to node1, you won't see the userSession on node2).
|
||||
|
||||
|
||||
WARNING: Previous steps works on Keycloak server overlay deployed on EAP 7.0 . With deploy on Wildfly 10.0.0.Final, you will see exception
|
||||
at startup caused by the bug https://issues.jboss.org/browse/ISPN-6203 .
|
||||
|
||||
There is a workaround to add this line into KEYCLOAK_HOME/modules/system/layers/base/org/wildfly/clustering/service/main/module.xml :
|
||||
|
||||
```
|
||||
<module name="org.infinispan.client.hotrod"/>
|
||||
```
|
|
@ -48,6 +48,10 @@
|
|||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-cachestore-remote</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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.cluster.infinispan;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.client.hotrod.Flag;
|
||||
import org.infinispan.client.hotrod.RemoteCache;
|
||||
import org.infinispan.commons.api.BasicCache;
|
||||
import org.infinispan.persistence.remote.RemoteStore;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
abstract class CrossDCAwareCacheFactory {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(CrossDCAwareCacheFactory.class);
|
||||
|
||||
|
||||
abstract BasicCache<String, Serializable> getCache();
|
||||
|
||||
|
||||
static CrossDCAwareCacheFactory getFactory(Cache<String, Serializable> workCache, Set<RemoteStore> remoteStores) {
|
||||
if (remoteStores.isEmpty()) {
|
||||
logger.debugf("No configured remoteStore available. Cross-DC scenario is not used");
|
||||
return new InfinispanCacheWrapperFactory(workCache);
|
||||
} else {
|
||||
logger.debugf("RemoteStore is available. Cross-DC scenario will be used");
|
||||
|
||||
if (remoteStores.size() > 1) {
|
||||
logger.warnf("More remoteStores configured for work cache. Will use just the first one");
|
||||
}
|
||||
|
||||
// For cross-DC scenario, we need to return underlying remoteCache for atomic operations to work properly
|
||||
RemoteStore remoteStore = remoteStores.iterator().next();
|
||||
RemoteCache remoteCache = remoteStore.getRemoteCache();
|
||||
return new RemoteCacheWrapperFactory(remoteCache);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We don't have external JDG configured. No cross-DC.
|
||||
private static class InfinispanCacheWrapperFactory extends CrossDCAwareCacheFactory {
|
||||
|
||||
private final Cache<String, Serializable> workCache;
|
||||
|
||||
InfinispanCacheWrapperFactory(Cache<String, Serializable> workCache) {
|
||||
this.workCache = workCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
BasicCache<String, Serializable> getCache() {
|
||||
return workCache;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// We have external JDG configured. Cross-DC should be enabled
|
||||
private static class RemoteCacheWrapperFactory extends CrossDCAwareCacheFactory {
|
||||
|
||||
private final RemoteCache<String, Serializable> remoteCache;
|
||||
|
||||
RemoteCacheWrapperFactory(RemoteCache<String, Serializable> remoteCache) {
|
||||
this.remoteCache = remoteCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
BasicCache<String, Serializable> getCache() {
|
||||
// Flags are per-invocation!
|
||||
return remoteCache.withFlags(Flag.FORCE_RETURN_VALUE);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -17,20 +17,15 @@
|
|||
|
||||
package org.keycloak.cluster.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.context.Flag;
|
||||
import org.infinispan.lifecycle.ComponentStatus;
|
||||
import org.infinispan.remoting.transport.Transport;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
import org.keycloak.cluster.ClusterListener;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.cluster.ExecutionResult;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -43,34 +38,22 @@ public class InfinispanClusterProvider implements ClusterProvider {
|
|||
public static final String CLUSTER_STARTUP_TIME_KEY = "cluster-start-time";
|
||||
private static final String TASK_KEY_PREFIX = "task::";
|
||||
|
||||
private final InfinispanClusterProviderFactory factory;
|
||||
private final KeycloakSession session;
|
||||
private final Cache<String, Serializable> cache;
|
||||
private final int clusterStartupTime;
|
||||
private final String myAddress;
|
||||
private final CrossDCAwareCacheFactory crossDCAwareCacheFactory;
|
||||
private final InfinispanNotificationsManager notificationsManager; // Just to extract notifications related stuff to separate class
|
||||
|
||||
public InfinispanClusterProvider(InfinispanClusterProviderFactory factory, KeycloakSession session, Cache<String, Serializable> cache) {
|
||||
this.factory = factory;
|
||||
this.session = session;
|
||||
this.cache = cache;
|
||||
public InfinispanClusterProvider(int clusterStartupTime, String myAddress, CrossDCAwareCacheFactory crossDCAwareCacheFactory, InfinispanNotificationsManager notificationsManager) {
|
||||
this.myAddress = myAddress;
|
||||
this.clusterStartupTime = clusterStartupTime;
|
||||
this.crossDCAwareCacheFactory = crossDCAwareCacheFactory;
|
||||
this.notificationsManager = notificationsManager;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getClusterStartupTime() {
|
||||
Integer existingClusterStartTime = (Integer) cache.get(InfinispanClusterProvider.CLUSTER_STARTUP_TIME_KEY);
|
||||
if (existingClusterStartTime != null) {
|
||||
return existingClusterStartTime;
|
||||
} else {
|
||||
// clusterStartTime not yet initialized. Let's try to put our startupTime
|
||||
int serverStartTime = (int) (session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
|
||||
|
||||
existingClusterStartTime = (Integer) cache.putIfAbsent(InfinispanClusterProvider.CLUSTER_STARTUP_TIME_KEY, serverStartTime);
|
||||
if (existingClusterStartTime == null) {
|
||||
logger.debugf("Initialized cluster startup time to %s", Time.toDate(serverStartTime).toString());
|
||||
return serverStartTime;
|
||||
} else {
|
||||
return existingClusterStartTime;
|
||||
}
|
||||
}
|
||||
return clusterStartupTime;
|
||||
}
|
||||
|
||||
|
||||
|
@ -104,56 +87,33 @@ public class InfinispanClusterProvider implements ClusterProvider {
|
|||
|
||||
@Override
|
||||
public void registerListener(String taskKey, ClusterListener task) {
|
||||
factory.registerListener(taskKey, task);
|
||||
this.notificationsManager.registerListener(taskKey, task);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void notify(String taskKey, ClusterEvent event) {
|
||||
// Put the value to the cache to notify listeners on all the nodes
|
||||
cache.put(taskKey, event);
|
||||
public void notify(String taskKey, ClusterEvent event, boolean ignoreSender) {
|
||||
this.notificationsManager.notify(taskKey, event, ignoreSender);
|
||||
}
|
||||
|
||||
|
||||
private String getCurrentNode(Cache<String, Serializable> cache) {
|
||||
Transport transport = cache.getCacheManager().getTransport();
|
||||
return transport==null ? "local" : transport.getAddress().toString();
|
||||
}
|
||||
|
||||
|
||||
private LockEntry createLockEntry(Cache<String, Serializable> cache) {
|
||||
private LockEntry createLockEntry() {
|
||||
LockEntry lock = new LockEntry();
|
||||
lock.setNode(getCurrentNode(cache));
|
||||
lock.setNode(myAddress);
|
||||
lock.setTimestamp(Time.currentTime());
|
||||
return lock;
|
||||
}
|
||||
|
||||
|
||||
private boolean tryLock(String cacheKey, int taskTimeoutInSeconds) {
|
||||
LockEntry myLock = createLockEntry(cache);
|
||||
LockEntry myLock = createLockEntry();
|
||||
|
||||
LockEntry existingLock = (LockEntry) cache.putIfAbsent(cacheKey, myLock);
|
||||
LockEntry existingLock = (LockEntry) crossDCAwareCacheFactory.getCache().putIfAbsent(cacheKey, myLock, taskTimeoutInSeconds, TimeUnit.SECONDS);
|
||||
if (existingLock != null) {
|
||||
// Task likely already in progress. Check if timestamp is not outdated
|
||||
int thatTime = existingLock.getTimestamp();
|
||||
int currentTime = Time.currentTime();
|
||||
if (thatTime + taskTimeoutInSeconds < currentTime) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Task %s outdated when in progress by node %s. Will try to replace task with our node %s", cacheKey, existingLock.getNode(), myLock.getNode());
|
||||
}
|
||||
boolean replaced = cache.replace(cacheKey, existingLock, myLock);
|
||||
if (!replaced) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Failed to replace the task %s. Other thread replaced in the meantime. Ignoring task.", cacheKey);
|
||||
}
|
||||
}
|
||||
return replaced;
|
||||
} else {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Task %s in progress already by node %s. Ignoring task.", cacheKey, existingLock.getNode());
|
||||
}
|
||||
return false;
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Task %s in progress already by node %s. Ignoring task.", cacheKey, existingLock.getNode());
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Successfully acquired lock for task %s. Our node is %s", cacheKey, myLock.getNode());
|
||||
|
@ -168,20 +128,12 @@ public class InfinispanClusterProvider implements ClusterProvider {
|
|||
int retry = 3;
|
||||
while (true) {
|
||||
try {
|
||||
cache.getAdvancedCache()
|
||||
.withFlags(Flag.IGNORE_RETURN_VALUES, Flag.FORCE_SYNCHRONOUS)
|
||||
.remove(cacheKey);
|
||||
crossDCAwareCacheFactory.getCache().remove(cacheKey);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Task %s removed from the cache", cacheKey);
|
||||
}
|
||||
return;
|
||||
} catch (RuntimeException e) {
|
||||
ComponentStatus status = cache.getStatus();
|
||||
if (status.isStopping() || status.isTerminated()) {
|
||||
logger.warnf("Failed to remove task %s from the cache. Cache is already terminating", cacheKey);
|
||||
logger.debug(e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
retry--;
|
||||
if (retry == 0) {
|
||||
throw e;
|
||||
|
|
|
@ -20,27 +20,24 @@ package org.keycloak.cluster.infinispan;
|
|||
import org.infinispan.Cache;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.infinispan.notifications.Listener;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent;
|
||||
import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged;
|
||||
import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent;
|
||||
import org.infinispan.persistence.manager.PersistenceManager;
|
||||
import org.infinispan.persistence.remote.RemoteStore;
|
||||
import org.infinispan.remoting.transport.Address;
|
||||
import org.infinispan.remoting.transport.Transport;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
import org.keycloak.cluster.ClusterListener;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.cluster.ClusterProviderFactory;
|
||||
import org.keycloak.common.util.HostUtils;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -49,6 +46,8 @@ import java.util.function.Predicate;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This impl is aware of Cross-Data-Center scenario too
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class InfinispanClusterProviderFactory implements ClusterProviderFactory {
|
||||
|
@ -57,28 +56,82 @@ public class InfinispanClusterProviderFactory implements ClusterProviderFactory
|
|||
|
||||
protected static final Logger logger = Logger.getLogger(InfinispanClusterProviderFactory.class);
|
||||
|
||||
// Infinispan cache
|
||||
private volatile Cache<String, Serializable> workCache;
|
||||
|
||||
private Map<String, ClusterListener> listeners = new HashMap<>();
|
||||
// Ensure that atomic operations (like putIfAbsent) must work correctly in any of: non-clustered, clustered or cross-Data-Center (cross-DC) setups
|
||||
private CrossDCAwareCacheFactory crossDCAwareCacheFactory;
|
||||
|
||||
private String myAddress;
|
||||
|
||||
private int clusterStartupTime;
|
||||
|
||||
// Just to extract notifications related stuff to separate class
|
||||
private InfinispanNotificationsManager notificationsManager;
|
||||
|
||||
@Override
|
||||
public ClusterProvider create(KeycloakSession session) {
|
||||
lazyInit(session);
|
||||
return new InfinispanClusterProvider(this, session, workCache);
|
||||
return new InfinispanClusterProvider(clusterStartupTime, myAddress, crossDCAwareCacheFactory, notificationsManager);
|
||||
}
|
||||
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
if (workCache == null) {
|
||||
synchronized (this) {
|
||||
if (workCache == null) {
|
||||
workCache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.WORK_CACHE_NAME);
|
||||
InfinispanConnectionProvider ispnConnections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
workCache = ispnConnections.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME);
|
||||
|
||||
workCache.getCacheManager().addListener(new ViewChangeListener());
|
||||
workCache.addListener(new CacheEntryListener());
|
||||
initMyAddress();
|
||||
|
||||
Set<RemoteStore> remoteStores = getRemoteStores();
|
||||
crossDCAwareCacheFactory = CrossDCAwareCacheFactory.getFactory(workCache, remoteStores);
|
||||
|
||||
clusterStartupTime = initClusterStartupTime(session);
|
||||
|
||||
notificationsManager = InfinispanNotificationsManager.create(workCache, myAddress, remoteStores);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// See if we have RemoteStore (external JDG) configured for cross-Data-Center scenario
|
||||
private Set<RemoteStore> getRemoteStores() {
|
||||
return workCache.getAdvancedCache().getComponentRegistry().getComponent(PersistenceManager.class).getStores(RemoteStore.class);
|
||||
}
|
||||
|
||||
|
||||
protected void initMyAddress() {
|
||||
Transport transport = workCache.getCacheManager().getTransport();
|
||||
this.myAddress = transport == null ? HostUtils.getHostName() + "-" + workCache.hashCode() : transport.getAddress().toString();
|
||||
logger.debugf("My address: %s", this.myAddress);
|
||||
}
|
||||
|
||||
|
||||
protected int initClusterStartupTime(KeycloakSession session) {
|
||||
Integer existingClusterStartTime = (Integer) crossDCAwareCacheFactory.getCache().get(InfinispanClusterProvider.CLUSTER_STARTUP_TIME_KEY);
|
||||
if (existingClusterStartTime != null) {
|
||||
logger.debugf("Loaded cluster startup time: %s", Time.toDate(existingClusterStartTime).toString());
|
||||
return existingClusterStartTime;
|
||||
} else {
|
||||
// clusterStartTime not yet initialized. Let's try to put our startupTime
|
||||
int serverStartTime = (int) (session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
|
||||
|
||||
existingClusterStartTime = (Integer) crossDCAwareCacheFactory.getCache().putIfAbsent(InfinispanClusterProvider.CLUSTER_STARTUP_TIME_KEY, serverStartTime);
|
||||
if (existingClusterStartTime == null) {
|
||||
logger.debugf("Initialized cluster startup time to %s", Time.toDate(serverStartTime).toString());
|
||||
return serverStartTime;
|
||||
} else {
|
||||
logger.debugf("Loaded cluster startup time: %s", Time.toDate(existingClusterStartTime).toString());
|
||||
return existingClusterStartTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
@ -167,34 +220,4 @@ public class InfinispanClusterProviderFactory implements ClusterProviderFactory
|
|||
}
|
||||
|
||||
|
||||
<T> void registerListener(String taskKey, ClusterListener task) {
|
||||
listeners.put(taskKey, task);
|
||||
}
|
||||
|
||||
@Listener
|
||||
public class CacheEntryListener {
|
||||
|
||||
@CacheEntryCreated
|
||||
public void cacheEntryCreated(CacheEntryCreatedEvent<String, Object> event) {
|
||||
if (!event.isPre()) {
|
||||
trigger(event.getKey(), event.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntryModified
|
||||
public void cacheEntryModified(CacheEntryModifiedEvent<String, Object> event) {
|
||||
if (!event.isPre()) {
|
||||
trigger(event.getKey(), event.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void trigger(String key, Object value) {
|
||||
ClusterListener task = listeners.get(key);
|
||||
if (task != null) {
|
||||
ClusterEvent event = (ClusterEvent) value;
|
||||
task.run(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* 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.cluster.infinispan;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.client.hotrod.RemoteCache;
|
||||
import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated;
|
||||
import org.infinispan.client.hotrod.annotation.ClientCacheEntryModified;
|
||||
import org.infinispan.client.hotrod.annotation.ClientListener;
|
||||
import org.infinispan.client.hotrod.event.ClientCacheEntryCreatedEvent;
|
||||
import org.infinispan.client.hotrod.event.ClientCacheEntryModifiedEvent;
|
||||
import org.infinispan.client.hotrod.event.ClientEvent;
|
||||
import org.infinispan.context.Flag;
|
||||
import org.infinispan.marshall.core.MarshalledEntry;
|
||||
import org.infinispan.notifications.Listener;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent;
|
||||
import org.infinispan.persistence.manager.PersistenceManager;
|
||||
import org.infinispan.persistence.remote.RemoteStore;
|
||||
import org.infinispan.remoting.transport.Transport;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
import org.keycloak.cluster.ClusterListener;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.common.util.HostUtils;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
|
||||
/**
|
||||
* Impl for sending infinispan messages across cluster and listening to them
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class InfinispanNotificationsManager {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(InfinispanNotificationsManager.class);
|
||||
|
||||
private final MultivaluedHashMap<String, ClusterListener> listeners = new MultivaluedHashMap<>();
|
||||
|
||||
private final Cache<String, Serializable> workCache;
|
||||
|
||||
private final String myAddress;
|
||||
|
||||
|
||||
protected InfinispanNotificationsManager(Cache<String, Serializable> workCache, String myAddress) {
|
||||
this.workCache = workCache;
|
||||
this.myAddress = myAddress;
|
||||
}
|
||||
|
||||
|
||||
// Create and init manager including all listeners etc
|
||||
public static InfinispanNotificationsManager create(Cache<String, Serializable> workCache, String myAddress, Set<RemoteStore> remoteStores) {
|
||||
InfinispanNotificationsManager manager = new InfinispanNotificationsManager(workCache, myAddress);
|
||||
|
||||
// We need CacheEntryListener just if we don't have remoteStore. With remoteStore will be all cluster nodes notified anyway from HotRod listener
|
||||
if (remoteStores.isEmpty()) {
|
||||
workCache.addListener(manager.new CacheEntryListener());
|
||||
|
||||
logger.debugf("Added listener for infinispan cache: %s", workCache.getName());
|
||||
} else {
|
||||
for (RemoteStore remoteStore : remoteStores) {
|
||||
RemoteCache<Object, Object> remoteCache = remoteStore.getRemoteCache();
|
||||
remoteCache.addClientListener(manager.new HotRodListener(remoteCache));
|
||||
|
||||
logger.debugf("Added listener for HotRod remoteStore cache: %s", remoteCache.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
|
||||
void registerListener(String taskKey, ClusterListener task) {
|
||||
listeners.add(taskKey, task);
|
||||
}
|
||||
|
||||
|
||||
void notify(String taskKey, ClusterEvent event, boolean ignoreSender) {
|
||||
WrapperClusterEvent wrappedEvent = new WrapperClusterEvent();
|
||||
wrappedEvent.setDelegateEvent(event);
|
||||
wrappedEvent.setIgnoreSender(ignoreSender);
|
||||
wrappedEvent.setSender(myAddress);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Sending event %s: %s", taskKey, event);
|
||||
}
|
||||
|
||||
// Put the value to the cache to notify listeners on all the nodes
|
||||
workCache.getAdvancedCache().withFlags(Flag.IGNORE_RETURN_VALUES)
|
||||
.put(taskKey, wrappedEvent, 120, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
||||
@Listener(observation = Listener.Observation.POST)
|
||||
public class CacheEntryListener {
|
||||
|
||||
@CacheEntryCreated
|
||||
public void cacheEntryCreated(CacheEntryCreatedEvent<String, Serializable> event) {
|
||||
eventReceived(event.getKey(), event.getValue());
|
||||
}
|
||||
|
||||
@CacheEntryModified
|
||||
public void cacheEntryModified(CacheEntryModifiedEvent<String, Serializable> event) {
|
||||
eventReceived(event.getKey(), event.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ClientListener
|
||||
public class HotRodListener {
|
||||
|
||||
private final RemoteCache<Object, Object> remoteCache;
|
||||
|
||||
public HotRodListener(RemoteCache<Object, Object> remoteCache) {
|
||||
this.remoteCache = remoteCache;
|
||||
}
|
||||
|
||||
|
||||
@ClientCacheEntryCreated
|
||||
public void created(ClientCacheEntryCreatedEvent event) {
|
||||
String key = event.getKey().toString();
|
||||
hotrodEventReceived(key);
|
||||
}
|
||||
|
||||
|
||||
@ClientCacheEntryModified
|
||||
public void updated(ClientCacheEntryModifiedEvent event) {
|
||||
String key = event.getKey().toString();
|
||||
hotrodEventReceived(key);
|
||||
}
|
||||
|
||||
private void hotrodEventReceived(String key) {
|
||||
// TODO: Look at CacheEventConverter stuff to possibly include value in the event and avoid additional remoteCache request
|
||||
Object value = remoteCache.get(key);
|
||||
|
||||
Serializable rawValue;
|
||||
if (value instanceof MarshalledEntry) {
|
||||
Object rw = ((MarshalledEntry)value).getValue();
|
||||
rawValue = (Serializable) rw;
|
||||
} else {
|
||||
rawValue = (Serializable) value;
|
||||
}
|
||||
|
||||
|
||||
eventReceived(key, rawValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void eventReceived(String key, Serializable obj) {
|
||||
if (!(obj instanceof WrapperClusterEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
WrapperClusterEvent event = (WrapperClusterEvent) obj;
|
||||
|
||||
if (event.isIgnoreSender()) {
|
||||
if (this.myAddress.equals(event.getSender())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Received event %s: %s", key, event);
|
||||
}
|
||||
|
||||
ClusterEvent wrappedEvent = event.getDelegateEvent();
|
||||
|
||||
List<ClusterListener> myListeners = listeners.get(key);
|
||||
if (myListeners != null) {
|
||||
for (ClusterListener listener : myListeners) {
|
||||
listener.eventReceived(wrappedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
myListeners = listeners.get(ClusterProvider.ALL);
|
||||
if (myListeners != null) {
|
||||
for (ClusterListener listener : myListeners) {
|
||||
listener.eventReceived(wrappedEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.cluster.infinispan;
|
||||
|
||||
import org.infinispan.commons.marshall.jboss.GenericJBossMarshaller;
|
||||
|
||||
/**
|
||||
* Needed on Wildfly, so that remoteStore (hotRod client) can find our classes
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class KeycloakHotRodMarshallerFactory {
|
||||
|
||||
public static GenericJBossMarshaller getInstance() {
|
||||
return new GenericJBossMarshaller(KeycloakHotRodMarshallerFactory.class.getClassLoader());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.cluster.infinispan;
|
||||
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class WrapperClusterEvent implements ClusterEvent {
|
||||
|
||||
private String sender; // will be null in non-clustered environment
|
||||
private boolean ignoreSender;
|
||||
private ClusterEvent delegateEvent;
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public void setSender(String sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
public boolean isIgnoreSender() {
|
||||
return ignoreSender;
|
||||
}
|
||||
|
||||
public void setIgnoreSender(boolean ignoreSender) {
|
||||
this.ignoreSender = ignoreSender;
|
||||
}
|
||||
|
||||
public ClusterEvent getDelegateEvent() {
|
||||
return delegateEvent;
|
||||
}
|
||||
|
||||
public void setDelegateEvent(ClusterEvent delegateEvent) {
|
||||
this.delegateEvent = delegateEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("WrapperClusterEvent [ sender=%s, delegateEvent=%s ]", sender, delegateEvent.toString());
|
||||
}
|
||||
}
|
|
@ -27,11 +27,14 @@ import org.infinispan.eviction.EvictionStrategy;
|
|||
import org.infinispan.eviction.EvictionType;
|
||||
import org.infinispan.manager.DefaultCacheManager;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.infinispan.persistence.remote.configuration.ExhaustedAction;
|
||||
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
|
||||
import org.infinispan.transaction.LockingMode;
|
||||
import org.infinispan.transaction.TransactionMode;
|
||||
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
|
@ -126,7 +129,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||
|
||||
boolean clustered = config.getBoolean("clustered", false);
|
||||
boolean async = config.getBoolean("async", true);
|
||||
boolean async = config.getBoolean("async", false);
|
||||
boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
|
||||
|
||||
if (clustered) {
|
||||
|
@ -139,14 +142,11 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
|
||||
logger.debug("Started embedded Infinispan cache container");
|
||||
|
||||
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
|
||||
if (clustered) {
|
||||
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
|
||||
}
|
||||
Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
|
||||
ConfigurationBuilder modelCacheConfigBuilder = new ConfigurationBuilder();
|
||||
Configuration modelCacheConfiguration = modelCacheConfigBuilder.build();
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, invalidationCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, modelCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, modelCacheConfiguration);
|
||||
|
||||
ConfigurationBuilder sessionConfigBuilder = new ConfigurationBuilder();
|
||||
if (clustered) {
|
||||
|
@ -174,8 +174,14 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
if (clustered) {
|
||||
replicationConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
|
||||
}
|
||||
Configuration replicationCacheConfiguration = replicationConfigBuilder.build();
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.WORK_CACHE_NAME, replicationCacheConfiguration);
|
||||
|
||||
boolean jdgEnabled = config.getBoolean("remoteStoreEnabled", false);
|
||||
if (jdgEnabled) {
|
||||
configureRemoteCacheStore(replicationConfigBuilder, async);
|
||||
}
|
||||
|
||||
Configuration replicationEvictionCacheConfiguration = replicationConfigBuilder.build();
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.WORK_CACHE_NAME, replicationEvictionCacheConfiguration);
|
||||
|
||||
ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
|
||||
counterConfigBuilder.invocationBatching().enable()
|
||||
|
@ -211,6 +217,34 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
return cb.build();
|
||||
}
|
||||
|
||||
// Used for cross-data centers scenario. Usually integration with external JDG server, which itself handles communication between DCs.
|
||||
private void configureRemoteCacheStore(ConfigurationBuilder builder, boolean async) {
|
||||
String jdgServer = config.get("remoteStoreServer", "localhost");
|
||||
Integer jdgPort = config.getInt("remoteStorePort", 11222);
|
||||
|
||||
builder.persistence()
|
||||
.passivation(false)
|
||||
.addStore(RemoteStoreConfigurationBuilder.class)
|
||||
.fetchPersistentState(false)
|
||||
.ignoreModifications(false)
|
||||
.purgeOnStartup(false)
|
||||
.preload(false)
|
||||
.shared(true)
|
||||
.remoteCacheName(InfinispanConnectionProvider.WORK_CACHE_NAME)
|
||||
.rawValues(true)
|
||||
.forceReturnValues(false)
|
||||
.marshaller(KeycloakHotRodMarshallerFactory.class.getName())
|
||||
.addServer()
|
||||
.host(jdgServer)
|
||||
.port(jdgPort)
|
||||
// .connectionPool()
|
||||
// .maxActive(100)
|
||||
// .exhaustedAction(ExhaustedAction.CREATE_NEW)
|
||||
.async()
|
||||
.enabled(async);
|
||||
|
||||
}
|
||||
|
||||
protected Configuration getKeysCacheConfig() {
|
||||
ConfigurationBuilder cb = new ConfigurationBuilder();
|
||||
cb.eviction().strategy(EvictionStrategy.LRU).type(EvictionType.COUNT).size(InfinispanConnectionProvider.KEYS_CACHE_DEFAULT_MAX);
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
@ -55,7 +54,7 @@ import java.util.function.Predicate;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class CacheManager {
|
||||
protected static final Logger logger = Logger.getLogger(CacheManager.class);
|
||||
|
||||
protected final Cache<String, Long> revisions;
|
||||
protected final Cache<String, Revisioned> cache;
|
||||
protected final UpdateCounter counter = new UpdateCounter();
|
||||
|
@ -63,9 +62,10 @@ public abstract class CacheManager {
|
|||
public CacheManager(Cache<String, Revisioned> cache, Cache<String, Long> revisions) {
|
||||
this.cache = cache;
|
||||
this.revisions = revisions;
|
||||
this.cache.addListener(this);
|
||||
}
|
||||
|
||||
protected abstract Logger getLogger();
|
||||
|
||||
public Cache<String, Revisioned> getCache() {
|
||||
return cache;
|
||||
}
|
||||
|
@ -79,10 +79,7 @@ public abstract class CacheManager {
|
|||
if (revision == null) {
|
||||
revision = counter.current();
|
||||
}
|
||||
// if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
|
||||
// so, we do this to force this.
|
||||
String invalidationKey = "invalidation.key" + id;
|
||||
cache.putForExternalRead(invalidationKey, new AbstractRevisioned(-1L, invalidationKey));
|
||||
|
||||
return revision;
|
||||
}
|
||||
|
||||
|
@ -101,12 +98,16 @@ public abstract class CacheManager {
|
|||
}
|
||||
Long rev = revisions.get(id);
|
||||
if (rev == null) {
|
||||
RealmCacheManager.logger.tracev("get() missing rev");
|
||||
if (getLogger().isTraceEnabled()) {
|
||||
getLogger().tracev("get() missing rev {0}", id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
|
||||
if (rev > oRev) {
|
||||
RealmCacheManager.logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
|
||||
if (getLogger().isTraceEnabled()) {
|
||||
getLogger().tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return o != null && type.isInstance(o) ? type.cast(o) : null;
|
||||
|
@ -114,9 +115,11 @@ public abstract class CacheManager {
|
|||
|
||||
public Object invalidateObject(String id) {
|
||||
Revisioned removed = (Revisioned)cache.remove(id);
|
||||
// if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
|
||||
// so, we do this to force the event.
|
||||
cache.remove("invalidation.key" + id);
|
||||
|
||||
if (getLogger().isTraceEnabled()) {
|
||||
getLogger().tracef("Removed key='%s', value='%s' from cache", id, removed);
|
||||
}
|
||||
|
||||
bumpVersion(id);
|
||||
return removed;
|
||||
}
|
||||
|
@ -137,37 +140,35 @@ public abstract class CacheManager {
|
|||
//revisions.getAdvancedCache().lock(id);
|
||||
Long rev = revisions.get(id);
|
||||
if (rev == null) {
|
||||
if (id.endsWith("realm.clients")) RealmCacheManager.logger.trace("addRevisioned rev == null realm.clients");
|
||||
rev = counter.current();
|
||||
revisions.put(id, rev);
|
||||
}
|
||||
revisions.startBatch();
|
||||
if (!revisions.getAdvancedCache().lock(id)) {
|
||||
RealmCacheManager.logger.trace("Could not obtain version lock");
|
||||
if (getLogger().isTraceEnabled()) {
|
||||
getLogger().tracev("Could not obtain version lock: {0}", id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
rev = revisions.get(id);
|
||||
if (rev == null) {
|
||||
if (id.endsWith("realm.clients")) RealmCacheManager.logger.trace("addRevisioned rev2 == null realm.clients");
|
||||
return;
|
||||
}
|
||||
if (rev > startupRevision) { // revision is ahead transaction start. Other transaction updated in the meantime. Don't cache
|
||||
if (RealmCacheManager.logger.isTraceEnabled()) {
|
||||
RealmCacheManager.logger.tracev("Skipped cache. Current revision {0}, Transaction start revision {1}", object.getRevision(), startupRevision);
|
||||
if (getLogger().isTraceEnabled()) {
|
||||
getLogger().tracev("Skipped cache. Current revision {0}, Transaction start revision {1}", object.getRevision(), startupRevision);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (rev.equals(object.getRevision())) {
|
||||
if (id.endsWith("realm.clients")) RealmCacheManager.logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
|
||||
cache.putForExternalRead(id, object);
|
||||
return;
|
||||
}
|
||||
if (rev > object.getRevision()) { // revision is ahead, don't cache
|
||||
if (id.endsWith("realm.clients")) RealmCacheManager.logger.trace("addRevisioned revision is ahead realm.clients");
|
||||
if (getLogger().isTraceEnabled()) getLogger().tracev("Skipped cache. Object revision {0}, Cache revision {1}", object.getRevision(), rev);
|
||||
return;
|
||||
}
|
||||
// revisions cache has a lower value than the object.revision, so update revision and add it to cache
|
||||
if (id.endsWith("realm.clients")) RealmCacheManager.logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
|
||||
revisions.put(id, object.getRevision());
|
||||
if (lifespan < 0) cache.putForExternalRead(id, object);
|
||||
else cache.putForExternalRead(id, object, lifespan, TimeUnit.MILLISECONDS);
|
||||
|
@ -196,63 +197,36 @@ public abstract class CacheManager {
|
|||
.filter(predicate).iterator();
|
||||
}
|
||||
|
||||
@CacheEntryInvalidated
|
||||
public void cacheInvalidated(CacheEntryInvalidatedEvent<String, Object> event) {
|
||||
if (event.isPre()) {
|
||||
String key = event.getKey();
|
||||
if (key.startsWith("invalidation.key")) {
|
||||
// if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
|
||||
// so, we do this to force this.
|
||||
String bump = key.substring("invalidation.key".length());
|
||||
RealmCacheManager.logger.tracev("bumping invalidation key {0}", bump);
|
||||
bumpVersion(bump);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
//if (!event.isPre()) {
|
||||
String key = event.getKey();
|
||||
if (key.startsWith("invalidation.key")) {
|
||||
// if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
|
||||
// so, we do this to force this.
|
||||
String bump = key.substring("invalidation.key".length());
|
||||
bumpVersion(bump);
|
||||
RealmCacheManager.logger.tracev("bumping invalidation key {0}", bump);
|
||||
return;
|
||||
}
|
||||
bumpVersion(key);
|
||||
Object object = event.getValue();
|
||||
if (object != null) {
|
||||
bumpVersion(key);
|
||||
Predicate<Map.Entry<String, Revisioned>> predicate = getInvalidationPredicate(object);
|
||||
if (predicate != null) runEvictions(predicate);
|
||||
RealmCacheManager.logger.tracev("invalidating: {0}" + object.getClass().getName());
|
||||
}
|
||||
public void sendInvalidationEvents(KeycloakSession session, Collection<InvalidationEvent> invalidationEvents) {
|
||||
ClusterProvider clusterProvider = session.getProvider(ClusterProvider.class);
|
||||
|
||||
// Maybe add InvalidationEvent, which will be collection of all invalidationEvents? That will reduce cluster traffic even more.
|
||||
for (InvalidationEvent event : invalidationEvents) {
|
||||
clusterProvider.notify(generateEventId(event), event, true);
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntriesEvicted
|
||||
public void cacheEvicted(CacheEntriesEvictedEvent<String, Object> event) {
|
||||
if (!event.isPre())
|
||||
for (Map.Entry<String, Object> entry : event.getEntries().entrySet()) {
|
||||
Object object = entry.getValue();
|
||||
bumpVersion(entry.getKey());
|
||||
if (object == null) continue;
|
||||
RealmCacheManager.logger.tracev("evicting: {0}" + object.getClass().getName());
|
||||
Predicate<Map.Entry<String, Revisioned>> predicate = getInvalidationPredicate(object);
|
||||
if (predicate != null) runEvictions(predicate);
|
||||
protected String generateEventId(InvalidationEvent event) {
|
||||
return new StringBuilder(event.getId())
|
||||
.append("_")
|
||||
.append(event.hashCode())
|
||||
.toString();
|
||||
}
|
||||
|
||||
|
||||
protected void invalidationEventReceived(InvalidationEvent event) {
|
||||
Set<String> invalidations = new HashSet<>();
|
||||
|
||||
addInvalidationsFromEvent(event, invalidations);
|
||||
|
||||
getLogger().debugf("Invalidating %d cache items after received event %s", invalidations.size(), event);
|
||||
|
||||
for (String invalidation : invalidations) {
|
||||
invalidateObject(invalidation);
|
||||
}
|
||||
}
|
||||
|
||||
public void runEvictions(Predicate<Map.Entry<String, Revisioned>> current) {
|
||||
Set<String> evictions = new HashSet<>();
|
||||
addInvalidations(current, evictions);
|
||||
RealmCacheManager.logger.tracev("running evictions size: {0}", evictions.size());
|
||||
for (String key : evictions) {
|
||||
cache.evict(key);
|
||||
bumpVersion(key);
|
||||
}
|
||||
}
|
||||
protected abstract void addInvalidationsFromEvent(InvalidationEvent event, Set<String> invalidations);
|
||||
|
||||
protected abstract Predicate<Map.Entry<String, Revisioned>> getInvalidationPredicate(Object object);
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ public class ClientAdapter implements ClientModel {
|
|||
|
||||
private void getDelegateForUpdate() {
|
||||
if (updated == null) {
|
||||
cacheSession.registerClientInvalidation(cached.getId());
|
||||
cacheSession.registerClientInvalidation(cached.getId(), cached.getClientId(), cachedRealm.getId());
|
||||
updated = cacheSession.getDelegate().getClientById(cached.getId(), cachedRealm);
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
}
|
||||
|
@ -577,18 +577,12 @@ public class ClientAdapter implements ClientModel {
|
|||
|
||||
@Override
|
||||
public RoleModel addRole(String name) {
|
||||
getDelegateForUpdate();
|
||||
RoleModel role = updated.addRole(name);
|
||||
cacheSession.registerRoleInvalidation(role.getId());
|
||||
return role;
|
||||
return cacheSession.addClientRole(getRealm(), this, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel addRole(String id, String name) {
|
||||
getDelegateForUpdate();
|
||||
RoleModel role = updated.addRole(id, name);
|
||||
cacheSession.registerRoleInvalidation(role.getId());
|
||||
return role;
|
||||
return cacheSession.addClientRole(getRealm(), this, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,7 +21,6 @@ import org.infinispan.Cache;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
import org.keycloak.cluster.ClusterListener;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -29,6 +28,7 @@ import org.keycloak.models.KeycloakSessionFactory;
|
|||
import org.keycloak.models.cache.CacheRealmProvider;
|
||||
import org.keycloak.models.cache.CacheRealmProviderFactory;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -54,14 +54,23 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
|
|||
Cache<String, Revisioned> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||
Cache<String, Long> revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME);
|
||||
realmCache = new RealmCacheManager(cache, revisions);
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
cluster.registerListener(REALM_CLEAR_CACHE_EVENTS, new ClusterListener() {
|
||||
@Override
|
||||
public void run(ClusterEvent event) {
|
||||
realmCache.clear();
|
||||
cluster.registerListener(ClusterProvider.ALL, (ClusterEvent event) -> {
|
||||
|
||||
if (event instanceof InvalidationEvent) {
|
||||
InvalidationEvent invalidationEvent = (InvalidationEvent) event;
|
||||
realmCache.invalidationEventReceived(invalidationEvent);
|
||||
}
|
||||
});
|
||||
|
||||
cluster.registerListener(REALM_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
|
||||
|
||||
realmCache.clear();
|
||||
|
||||
});
|
||||
|
||||
log.debug("Registered cluster listeners");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import org.infinispan.Cache;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
import org.keycloak.cluster.ClusterListener;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -29,6 +28,7 @@ import org.keycloak.models.KeycloakSessionFactory;
|
|||
import org.keycloak.models.cache.UserCache;
|
||||
import org.keycloak.models.cache.UserCacheProviderFactory;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -55,13 +55,25 @@ public class InfinispanUserCacheProviderFactory implements UserCacheProviderFact
|
|||
Cache<String, Revisioned> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.USER_CACHE_NAME);
|
||||
Cache<String, Long> revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME);
|
||||
userCache = new UserCacheManager(cache, revisions);
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
cluster.registerListener(USER_CLEAR_CACHE_EVENTS, new ClusterListener() {
|
||||
@Override
|
||||
public void run(ClusterEvent event) {
|
||||
userCache.clear();
|
||||
|
||||
cluster.registerListener(ClusterProvider.ALL, (ClusterEvent event) -> {
|
||||
|
||||
if (event instanceof InvalidationEvent) {
|
||||
InvalidationEvent invalidationEvent = (InvalidationEvent) event;
|
||||
userCache.invalidationEventReceived(invalidationEvent);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
cluster.registerListener(USER_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
|
||||
|
||||
userCache.clear();
|
||||
|
||||
});
|
||||
|
||||
log.debug("Registered cluster listeners");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,13 +39,8 @@ import org.keycloak.models.UserFederationMapperModel;
|
|||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.cache.CachedRealmModel;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedRealm;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
@ -75,7 +70,7 @@ public class RealmAdapter implements CachedRealmModel {
|
|||
@Override
|
||||
public RealmModel getDelegateForUpdate() {
|
||||
if (updated == null) {
|
||||
cacheSession.registerRealmInvalidation(cached.getId());
|
||||
cacheSession.registerRealmInvalidation(cached.getId(), cached.getName());
|
||||
updated = cacheSession.getDelegate().getRealm(cached.getId());
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
}
|
||||
|
@ -731,13 +726,6 @@ public class RealmAdapter implements CachedRealmModel {
|
|||
updated.setNotBefore(notBefore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRoleById(String id) {
|
||||
cacheSession.registerRoleInvalidation(id);
|
||||
getDelegateForUpdate();
|
||||
return updated.removeRoleById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEventsEnabled() {
|
||||
if (isUpdated()) return updated.isEventsEnabled();
|
||||
|
@ -837,18 +825,12 @@ public class RealmAdapter implements CachedRealmModel {
|
|||
|
||||
@Override
|
||||
public RoleModel addRole(String name) {
|
||||
getDelegateForUpdate();
|
||||
RoleModel role = updated.addRole(name);
|
||||
cacheSession.registerRoleInvalidation(role.getId());
|
||||
return role;
|
||||
return cacheSession.addRealmRole(this, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel addRole(String id, String name) {
|
||||
getDelegateForUpdate();
|
||||
RoleModel role = updated.addRole(id, name);
|
||||
cacheSession.registerRoleInvalidation(role.getId());
|
||||
return role;
|
||||
return cacheSession.addRealmRole(this, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1257,12 +1239,6 @@ public class RealmAdapter implements CachedRealmModel {
|
|||
return cacheSession.createGroup(this, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTopLevelGroup(GroupModel subGroup) {
|
||||
cacheSession.addTopLevelGroup(this, subGroup);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveGroup(GroupModel group, GroupModel toParent) {
|
||||
cacheSession.moveGroup(this, group, toParent);
|
||||
|
|
|
@ -18,145 +18,88 @@
|
|||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.notifications.Listener;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedClient;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedClientTemplate;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedRole;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
import org.keycloak.models.cache.infinispan.stream.ClientQueryPredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.ClientTemplateQueryPredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.GroupQueryPredicate;
|
||||
import org.keycloak.models.cache.infinispan.events.RealmCacheInvalidationEvent;
|
||||
import org.keycloak.models.cache.infinispan.stream.HasRolePredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.InClientPredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.InRealmPredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.RealmQueryPredicate;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Listener
|
||||
public class RealmCacheManager extends CacheManager {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(RealmCacheManager.class);
|
||||
private static final Logger logger = Logger.getLogger(RealmCacheManager.class);
|
||||
|
||||
@Override
|
||||
protected Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public RealmCacheManager(Cache<String, Revisioned> cache, Cache<String, Long> revisions) {
|
||||
super(cache, revisions);
|
||||
}
|
||||
|
||||
|
||||
public void realmInvalidation(String id, Set<String> invalidations) {
|
||||
Predicate<Map.Entry<String, Revisioned>> predicate = getRealmInvalidationPredicate(id);
|
||||
addInvalidations(predicate, invalidations);
|
||||
public void realmUpdated(String id, String name, Set<String> invalidations) {
|
||||
invalidations.add(id);
|
||||
invalidations.add(RealmCacheSession.getRealmByNameCacheKey(name));
|
||||
}
|
||||
|
||||
public Predicate<Map.Entry<String, Revisioned>> getRealmInvalidationPredicate(String id) {
|
||||
return RealmQueryPredicate.create().realm(id);
|
||||
public void realmRemoval(String id, String name, Set<String> invalidations) {
|
||||
realmUpdated(id, name, invalidations);
|
||||
|
||||
addInvalidations(InRealmPredicate.create().realm(id), invalidations);
|
||||
}
|
||||
|
||||
public void clientInvalidation(String id, Set<String> invalidations) {
|
||||
addInvalidations(getClientInvalidationPredicate(id), invalidations);
|
||||
public void roleAdded(String roleContainerId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getRolesCacheKey(roleContainerId));
|
||||
}
|
||||
|
||||
public Predicate<Map.Entry<String, Revisioned>> getClientInvalidationPredicate(String id) {
|
||||
return ClientQueryPredicate.create().client(id);
|
||||
public void roleUpdated(String roleContainerId, String roleName, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getRoleByNameCacheKey(roleContainerId, roleName));
|
||||
}
|
||||
|
||||
public void roleInvalidation(String id, Set<String> invalidations) {
|
||||
addInvalidations(getRoleInvalidationPredicate(id), invalidations);
|
||||
public void roleRemoval(String id, String roleName, String roleContainerId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getRolesCacheKey(roleContainerId));
|
||||
invalidations.add(RealmCacheSession.getRoleByNameCacheKey(roleContainerId, roleName));
|
||||
|
||||
addInvalidations(HasRolePredicate.create().role(id), invalidations);
|
||||
}
|
||||
|
||||
public Predicate<Map.Entry<String, Revisioned>> getRoleInvalidationPredicate(String id) {
|
||||
return HasRolePredicate.create().role(id);
|
||||
public void groupQueriesInvalidations(String realmId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getGroupsQueryCacheKey(realmId));
|
||||
invalidations.add(RealmCacheSession.getTopGroupsQueryCacheKey(realmId)); // Just easier to always invalidate top-level too. It's not big performance penalty
|
||||
}
|
||||
|
||||
public void groupInvalidation(String id, Set<String> invalidations) {
|
||||
addInvalidations(getGroupInvalidationPredicate(id), invalidations);
|
||||
|
||||
public void clientAdded(String realmId, String clientUUID, String clientId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getRealmClientsQueryCacheKey(realmId));
|
||||
}
|
||||
|
||||
public Predicate<Map.Entry<String, Revisioned>> getGroupInvalidationPredicate(String id) {
|
||||
return GroupQueryPredicate.create().group(id);
|
||||
public void clientUpdated(String realmId, String clientUuid, String clientId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getClientByClientIdCacheKey(clientId, realmId));
|
||||
}
|
||||
|
||||
public void clientTemplateInvalidation(String id, Set<String> invalidations) {
|
||||
addInvalidations(getClientTemplateInvalidationPredicate(id), invalidations);
|
||||
// Client roles invalidated separately
|
||||
public void clientRemoval(String realmId, String clientUUID, String clientId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getRealmClientsQueryCacheKey(realmId));
|
||||
invalidations.add(RealmCacheSession.getClientByClientIdCacheKey(clientId, realmId));
|
||||
|
||||
addInvalidations(InClientPredicate.create().client(clientUUID), invalidations);
|
||||
}
|
||||
|
||||
public Predicate<Map.Entry<String, Revisioned>> getClientTemplateInvalidationPredicate(String id) {
|
||||
return ClientTemplateQueryPredicate.create().template(id);
|
||||
}
|
||||
|
||||
public void realmRemoval(String id, Set<String> invalidations) {
|
||||
Predicate<Map.Entry<String, Revisioned>> predicate = getRealmRemovalPredicate(id);
|
||||
addInvalidations(predicate, invalidations);
|
||||
}
|
||||
|
||||
public Predicate<Map.Entry<String, Revisioned>> getRealmRemovalPredicate(String id) {
|
||||
Predicate<Map.Entry<String, Revisioned>> predicate = null;
|
||||
predicate = RealmQueryPredicate.create().realm(id)
|
||||
.or(InRealmPredicate.create().realm(id));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
public void clientAdded(String realmId, String id, Set<String> invalidations) {
|
||||
addInvalidations(getClientAddedPredicate(realmId), invalidations);
|
||||
}
|
||||
|
||||
public Predicate<Map.Entry<String, Revisioned>> getClientAddedPredicate(String realmId) {
|
||||
return ClientQueryPredicate.create().inRealm(realmId);
|
||||
}
|
||||
|
||||
public void clientRemoval(String realmId, String id, Set<String> invalidations) {
|
||||
Predicate<Map.Entry<String, Revisioned>> predicate = null;
|
||||
predicate = getClientRemovalPredicate(realmId, id);
|
||||
addInvalidations(predicate, invalidations);
|
||||
}
|
||||
|
||||
public Predicate<Map.Entry<String, Revisioned>> getClientRemovalPredicate(String realmId, String id) {
|
||||
Predicate<Map.Entry<String, Revisioned>> predicate;
|
||||
predicate = ClientQueryPredicate.create().inRealm(realmId)
|
||||
.or(ClientQueryPredicate.create().client(id))
|
||||
.or(InClientPredicate.create().client(id));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
public void roleRemoval(String id, Set<String> invalidations) {
|
||||
addInvalidations(getRoleRemovalPredicate(id), invalidations);
|
||||
|
||||
}
|
||||
|
||||
public Predicate<Map.Entry<String, Revisioned>> getRoleRemovalPredicate(String id) {
|
||||
return getRoleInvalidationPredicate(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<Map.Entry<String, Revisioned>> getInvalidationPredicate(Object object) {
|
||||
if (object instanceof CachedRealm) {
|
||||
CachedRealm cached = (CachedRealm)object;
|
||||
return getRealmRemovalPredicate(cached.getId());
|
||||
} else if (object instanceof CachedClient) {
|
||||
CachedClient cached = (CachedClient)object;
|
||||
Predicate<Map.Entry<String, Revisioned>> predicate = getClientRemovalPredicate(cached.getRealm(), cached.getId());
|
||||
return predicate;
|
||||
} else if (object instanceof CachedRole) {
|
||||
CachedRole cached = (CachedRole)object;
|
||||
return getRoleRemovalPredicate(cached.getId());
|
||||
} else if (object instanceof CachedGroup) {
|
||||
CachedGroup cached = (CachedGroup)object;
|
||||
return getGroupInvalidationPredicate(cached.getId());
|
||||
} else if (object instanceof CachedClientTemplate) {
|
||||
CachedClientTemplate cached = (CachedClientTemplate)object;
|
||||
return getClientTemplateInvalidationPredicate(cached.getId());
|
||||
protected void addInvalidationsFromEvent(InvalidationEvent event, Set<String> invalidations) {
|
||||
if (event instanceof RealmCacheInvalidationEvent) {
|
||||
invalidations.add(event.getId());
|
||||
|
||||
((RealmCacheInvalidationEvent) event).addInvalidations(this, invalidations);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.models.cache.infinispan;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientTemplateModel;
|
||||
|
@ -38,8 +39,22 @@ import org.keycloak.models.cache.infinispan.entities.CachedRealm;
|
|||
import org.keycloak.models.cache.infinispan.entities.CachedRealmRole;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedRole;
|
||||
import org.keycloak.models.cache.infinispan.entities.ClientListQuery;
|
||||
import org.keycloak.models.cache.infinispan.entities.GroupListQuery;
|
||||
import org.keycloak.models.cache.infinispan.entities.RealmListQuery;
|
||||
import org.keycloak.models.cache.infinispan.entities.RoleListQuery;
|
||||
import org.keycloak.models.cache.infinispan.events.ClientAddedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.ClientRemovedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.ClientTemplateEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.ClientUpdatedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.GroupAddedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.GroupMovedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.GroupRemovedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.GroupUpdatedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RealmRemovedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RealmUpdatedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RoleAddedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RoleRemovedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RoleUpdatedEvent;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -126,6 +141,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
protected Map<String, GroupAdapter> managedGroups = new HashMap<>();
|
||||
protected Set<String> listInvalidations = new HashSet<>();
|
||||
protected Set<String> invalidations = new HashSet<>();
|
||||
protected Set<InvalidationEvent> invalidationEvents = new HashSet<>(); // Events to be sent across cluster
|
||||
|
||||
protected boolean clearAll;
|
||||
protected final long startupRevision;
|
||||
|
@ -150,7 +166,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
public void clear() {
|
||||
cache.clear();
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
cluster.notify(InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS, new ClearCacheEvent());
|
||||
cluster.notify(InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -167,21 +183,19 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void registerRealmInvalidation(String id) {
|
||||
invalidateRealm(id);
|
||||
cache.realmInvalidation(id, invalidations);
|
||||
}
|
||||
|
||||
private void invalidateRealm(String id) {
|
||||
invalidations.add(id);
|
||||
public void registerRealmInvalidation(String id, String name) {
|
||||
cache.realmUpdated(id, name, invalidations);
|
||||
RealmAdapter adapter = managedRealms.get(id);
|
||||
if (adapter != null) adapter.invalidateFlag();
|
||||
|
||||
invalidationEvents.add(RealmUpdatedEvent.create(id, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerClientInvalidation(String id) {
|
||||
public void registerClientInvalidation(String id, String clientId, String realmId) {
|
||||
invalidateClient(id);
|
||||
cache.clientInvalidation(id, invalidations);
|
||||
invalidationEvents.add(ClientUpdatedEvent.create(id, clientId, realmId));
|
||||
cache.clientUpdated(realmId, id, clientId, invalidations);
|
||||
}
|
||||
|
||||
private void invalidateClient(String id) {
|
||||
|
@ -193,7 +207,9 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
@Override
|
||||
public void registerClientTemplateInvalidation(String id) {
|
||||
invalidateClientTemplate(id);
|
||||
cache.clientTemplateInvalidation(id, invalidations);
|
||||
// Note: Adding/Removing client template is supposed to invalidate CachedRealm as well, so the list of clientTemplates is invalidated.
|
||||
// But separate RealmUpdatedEvent will be sent for it. So ClientTemplateEvent don't need to take care of it.
|
||||
invalidationEvents.add(ClientTemplateEvent.create(id));
|
||||
}
|
||||
|
||||
private void invalidateClientTemplate(String id) {
|
||||
|
@ -203,14 +219,15 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void registerRoleInvalidation(String id) {
|
||||
public void registerRoleInvalidation(String id, String roleName, String roleContainerId) {
|
||||
invalidateRole(id);
|
||||
roleInvalidations(id);
|
||||
cache.roleUpdated(roleContainerId, roleName, invalidations);
|
||||
invalidationEvents.add(RoleUpdatedEvent.create(id, roleName, roleContainerId));
|
||||
}
|
||||
|
||||
private void roleInvalidations(String roleId) {
|
||||
private void roleRemovalInvalidations(String roleId, String roleName, String roleContainerId) {
|
||||
Set<String> newInvalidations = new HashSet<>();
|
||||
cache.roleInvalidation(roleId, newInvalidations);
|
||||
cache.roleRemoval(roleId, roleName, roleContainerId, newInvalidations);
|
||||
invalidations.addAll(newInvalidations);
|
||||
// need to make sure that scope and group mapping clients and groups are invalidated
|
||||
for (String id : newInvalidations) {
|
||||
|
@ -229,6 +246,11 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
clientTemplate.invalidate();
|
||||
continue;
|
||||
}
|
||||
RoleAdapter role = managedRoles.get(id);
|
||||
if (role != null) {
|
||||
role.invalidate();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -243,10 +265,26 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
if (adapter != null) adapter.invalidate();
|
||||
}
|
||||
|
||||
private void addedRole(String roleId, String roleContainerId) {
|
||||
// this is needed so that a new role that hasn't been committed isn't cached in a query
|
||||
listInvalidations.add(roleContainerId);
|
||||
|
||||
invalidateRole(roleId);
|
||||
cache.roleAdded(roleContainerId, invalidations);
|
||||
invalidationEvents.add(RoleAddedEvent.create(roleId, roleContainerId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerGroupInvalidation(String id) {
|
||||
invalidateGroup(id, null, false);
|
||||
addGroupEventIfAbsent(GroupUpdatedEvent.create(id));
|
||||
}
|
||||
|
||||
private void invalidateGroup(String id, String realmId, boolean invalidateQueries) {
|
||||
invalidateGroup(id);
|
||||
cache.groupInvalidation(id, invalidations);
|
||||
if (invalidateQueries) {
|
||||
cache.groupQueriesInvalidations(realmId, invalidations);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateGroup(String id) {
|
||||
|
@ -259,6 +297,8 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
for (String id : invalidations) {
|
||||
cache.invalidateObject(id);
|
||||
}
|
||||
|
||||
cache.sendInvalidationEvents(session, invalidationEvents);
|
||||
}
|
||||
|
||||
private KeycloakTransaction getPrepareTransaction() {
|
||||
|
@ -358,14 +398,14 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
@Override
|
||||
public RealmModel createRealm(String name) {
|
||||
RealmModel realm = getDelegate().createRealm(name);
|
||||
registerRealmInvalidation(realm.getId());
|
||||
registerRealmInvalidation(realm.getId(), realm.getName());
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel createRealm(String id, String name) {
|
||||
RealmModel realm = getDelegate().createRealm(id, name);
|
||||
registerRealmInvalidation(realm.getId());
|
||||
registerRealmInvalidation(realm.getId(), realm.getName());
|
||||
return realm;
|
||||
}
|
||||
|
||||
|
@ -434,7 +474,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
}
|
||||
}
|
||||
|
||||
public String getRealmByNameCacheKey(String name) {
|
||||
static String getRealmByNameCacheKey(String name) {
|
||||
return "realm.query.by.name." + name;
|
||||
}
|
||||
|
||||
|
@ -457,20 +497,12 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
RealmModel realm = getRealm(id);
|
||||
if (realm == null) return false;
|
||||
|
||||
invalidations.add(getRealmClientsQueryCacheKey(id));
|
||||
invalidations.add(getRealmByNameCacheKey(realm.getName()));
|
||||
cache.invalidateObject(id);
|
||||
cache.realmRemoval(id, invalidations);
|
||||
invalidationEvents.add(RealmRemovedEvent.create(id, realm.getName()));
|
||||
cache.realmRemoval(id, realm.getName(), invalidations);
|
||||
return getDelegate().removeRealm(id);
|
||||
}
|
||||
|
||||
protected void invalidateClient(RealmModel realm, ClientModel client) {
|
||||
invalidateClient(client.getId());
|
||||
invalidations.add(getRealmClientsQueryCacheKey(realm.getId()));
|
||||
invalidations.add(getClientByClientIdCacheKey(client.getClientId(), realm));
|
||||
listInvalidations.add(realm.getId());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ClientModel addClient(RealmModel realm, String clientId) {
|
||||
|
@ -486,30 +518,32 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
private ClientModel addedClient(RealmModel realm, ClientModel client) {
|
||||
logger.trace("added Client.....");
|
||||
// need to invalidate realm client query cache every time as it may not be loaded on this node, but loaded on another
|
||||
invalidateClient(realm, client);
|
||||
cache.clientAdded(realm.getId(), client.getId(), invalidations);
|
||||
// this is needed so that a new client that hasn't been committed isn't cached in a query
|
||||
|
||||
invalidateClient(client.getId());
|
||||
// this is needed so that a client that hasn't been committed isn't cached in a query
|
||||
listInvalidations.add(realm.getId());
|
||||
|
||||
invalidationEvents.add(ClientAddedEvent.create(client.getId(), client.getClientId(), realm.getId()));
|
||||
cache.clientAdded(realm.getId(), client.getId(), client.getClientId(), invalidations);
|
||||
return client;
|
||||
}
|
||||
|
||||
private String getRealmClientsQueryCacheKey(String realm) {
|
||||
static String getRealmClientsQueryCacheKey(String realm) {
|
||||
return realm + REALM_CLIENTS_QUERY_SUFFIX;
|
||||
}
|
||||
|
||||
private String getGroupsQueryCacheKey(String realm) {
|
||||
static String getGroupsQueryCacheKey(String realm) {
|
||||
return realm + ".groups";
|
||||
}
|
||||
|
||||
private String getTopGroupsQueryCacheKey(String realm) {
|
||||
static String getTopGroupsQueryCacheKey(String realm) {
|
||||
return realm + ".top.groups";
|
||||
}
|
||||
|
||||
private String getRolesCacheKey(String container) {
|
||||
static String getRolesCacheKey(String container) {
|
||||
return container + ROLES_QUERY_SUFFIX;
|
||||
}
|
||||
private String getRoleByNameCacheKey(String container, String name) {
|
||||
static String getRoleByNameCacheKey(String container, String name) {
|
||||
return container + "." + name + ROLES_QUERY_SUFFIX;
|
||||
}
|
||||
|
||||
|
@ -541,6 +575,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
for (String id : query.getClients()) {
|
||||
ClientModel client = session.realms().getClientById(id, realm);
|
||||
if (client == null) {
|
||||
// TODO: Handle with cluster invalidations too
|
||||
invalidations.add(cacheKey);
|
||||
return getDelegate().getClients(realm);
|
||||
}
|
||||
|
@ -554,12 +589,16 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
public boolean removeClient(String id, RealmModel realm) {
|
||||
ClientModel client = getClientById(id, realm);
|
||||
if (client == null) return false;
|
||||
// need to invalidate realm client query cache every time client list is changed
|
||||
invalidateClient(realm, client);
|
||||
cache.clientRemoval(realm.getId(), id, invalidations);
|
||||
|
||||
invalidateClient(client.getId());
|
||||
// this is needed so that a client that hasn't been committed isn't cached in a query
|
||||
listInvalidations.add(realm.getId());
|
||||
|
||||
invalidationEvents.add(ClientRemovedEvent.create(client));
|
||||
cache.clientRemoval(realm.getId(), id, client.getClientId(), invalidations);
|
||||
|
||||
for (RoleModel role : client.getRoles()) {
|
||||
String roleId = role.getId();
|
||||
roleInvalidations(roleId);
|
||||
roleRemovalInvalidations(role.getId(), role.getName(), client.getId());
|
||||
}
|
||||
return getDelegate().removeClient(id, realm);
|
||||
}
|
||||
|
@ -577,11 +616,8 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
@Override
|
||||
public RoleModel addRealmRole(RealmModel realm, String id, String name) {
|
||||
invalidations.add(getRolesCacheKey(realm.getId()));
|
||||
// this is needed so that a new role that hasn't been committed isn't cached in a query
|
||||
listInvalidations.add(realm.getId());
|
||||
RoleModel role = getDelegate().addRealmRole(realm, name);
|
||||
invalidations.add(role.getId());
|
||||
addedRole(role.getId(), realm.getId());
|
||||
return role;
|
||||
}
|
||||
|
||||
|
@ -664,11 +700,8 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
@Override
|
||||
public RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name) {
|
||||
invalidations.add(getRolesCacheKey(client.getId()));
|
||||
// this is needed so that a new role that hasn't been committed isn't cached in a query
|
||||
listInvalidations.add(client.getId());
|
||||
RoleModel role = getDelegate().addClientRole(realm, client, id, name);
|
||||
invalidateRole(role.getId());
|
||||
addedRole(role.getId(), client.getId());
|
||||
return role;
|
||||
}
|
||||
|
||||
|
@ -734,10 +767,12 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
@Override
|
||||
public boolean removeRole(RealmModel realm, RoleModel role) {
|
||||
invalidations.add(getRolesCacheKey(role.getContainer().getId()));
|
||||
invalidations.add(getRoleByNameCacheKey(role.getContainer().getId(), role.getName()));
|
||||
listInvalidations.add(role.getContainer().getId());
|
||||
registerRoleInvalidation(role.getId());
|
||||
|
||||
invalidateRole(role.getId());
|
||||
invalidationEvents.add(RoleRemovedEvent.create(role.getId(), role.getName(), role.getContainer().getId()));
|
||||
roleRemovalInvalidations(role.getId(), role.getName(), role.getContainer().getId());
|
||||
|
||||
return getDelegate().removeRole(realm, role);
|
||||
}
|
||||
|
||||
|
@ -797,8 +832,11 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
@Override
|
||||
public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) {
|
||||
registerGroupInvalidation(group.getId());
|
||||
if (toParent != null) registerGroupInvalidation(toParent.getId());
|
||||
invalidateGroup(group.getId(), realm.getId(), true);
|
||||
if (toParent != null) invalidateGroup(group.getId(), realm.getId(), false); // Queries already invalidated
|
||||
listInvalidations.add(realm.getId());
|
||||
|
||||
invalidationEvents.add(GroupMovedEvent.create(group, toParent, realm.getId()));
|
||||
getDelegate().moveGroup(realm, group, toParent);
|
||||
}
|
||||
|
||||
|
@ -876,14 +914,15 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
@Override
|
||||
public boolean removeGroup(RealmModel realm, GroupModel group) {
|
||||
registerGroupInvalidation(group.getId());
|
||||
invalidateGroup(group.getId(), realm.getId(), true);
|
||||
listInvalidations.add(realm.getId());
|
||||
invalidations.add(getGroupsQueryCacheKey(realm.getId()));
|
||||
if (group.getParentId() == null) {
|
||||
invalidations.add(getTopGroupsQueryCacheKey(realm.getId()));
|
||||
} else {
|
||||
registerGroupInvalidation(group.getParentId());
|
||||
cache.groupQueriesInvalidations(realm.getId(), invalidations);
|
||||
if (group.getParentId() != null) {
|
||||
invalidateGroup(group.getParentId(), realm.getId(), false); // Queries already invalidated
|
||||
}
|
||||
|
||||
invalidationEvents.add(GroupRemovedEvent.create(group, realm.getId()));
|
||||
|
||||
return getDelegate().removeGroup(realm, group);
|
||||
}
|
||||
|
||||
|
@ -893,11 +932,11 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
return groupAdded(realm, group);
|
||||
}
|
||||
|
||||
public GroupModel groupAdded(RealmModel realm, GroupModel group) {
|
||||
private GroupModel groupAdded(RealmModel realm, GroupModel group) {
|
||||
listInvalidations.add(realm.getId());
|
||||
invalidations.add(getGroupsQueryCacheKey(realm.getId()));
|
||||
invalidations.add(getTopGroupsQueryCacheKey(realm.getId()));
|
||||
cache.groupQueriesInvalidations(realm.getId(), invalidations);
|
||||
invalidations.add(group.getId());
|
||||
invalidationEvents.add(GroupAddedEvent.create(group.getId(), realm.getId()));
|
||||
return group;
|
||||
}
|
||||
|
||||
|
@ -909,15 +948,32 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
@Override
|
||||
public void addTopLevelGroup(RealmModel realm, GroupModel subGroup) {
|
||||
invalidations.add(getTopGroupsQueryCacheKey(realm.getId()));
|
||||
invalidations.add(subGroup.getId());
|
||||
invalidateGroup(subGroup.getId(), realm.getId(), true);
|
||||
if (subGroup.getParentId() != null) {
|
||||
registerGroupInvalidation(subGroup.getParentId());
|
||||
invalidateGroup(subGroup.getParentId(), realm.getId(), false); // Queries already invalidated
|
||||
}
|
||||
|
||||
addGroupEventIfAbsent(GroupMovedEvent.create(subGroup, null, realm.getId()));
|
||||
|
||||
getDelegate().addTopLevelGroup(realm, subGroup);
|
||||
|
||||
}
|
||||
|
||||
private void addGroupEventIfAbsent(InvalidationEvent eventToAdd) {
|
||||
String groupId = eventToAdd.getId();
|
||||
|
||||
// Check if we have existing event with bigger priority
|
||||
boolean eventAlreadyExists = invalidationEvents.stream().filter((InvalidationEvent event) -> {
|
||||
|
||||
return (event.getId().equals(groupId)) && (event instanceof GroupAddedEvent || event instanceof GroupMovedEvent || event instanceof GroupRemovedEvent);
|
||||
|
||||
}).findFirst().isPresent();
|
||||
|
||||
if (!eventAlreadyExists) {
|
||||
invalidationEvents.add(eventToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClientById(String id, RealmModel realm) {
|
||||
CachedClient cached = cache.get(id, CachedClient.class);
|
||||
|
@ -948,7 +1004,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
@Override
|
||||
public ClientModel getClientByClientId(String clientId, RealmModel realm) {
|
||||
String cacheKey = getClientByClientIdCacheKey(clientId, realm);
|
||||
String cacheKey = getClientByClientIdCacheKey(clientId, realm.getId());
|
||||
ClientListQuery query = cache.get(cacheKey, ClientListQuery.class);
|
||||
String id = null;
|
||||
|
||||
|
@ -976,8 +1032,8 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
return getClientById(id, realm);
|
||||
}
|
||||
|
||||
public String getClientByClientIdCacheKey(String clientId, RealmModel realm) {
|
||||
return realm.getId() + ".client.query.by.clientId." + clientId;
|
||||
static String getClientByClientIdCacheKey(String clientId, String realmId) {
|
||||
return realmId + ".client.query.by.clientId." + clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -47,7 +47,7 @@ public class RoleAdapter implements RoleModel {
|
|||
|
||||
protected void getDelegateForUpdate() {
|
||||
if (updated == null) {
|
||||
cacheSession.registerRoleInvalidation(cached.getId());
|
||||
cacheSession.registerRoleInvalidation(cached.getId(), cached.getName(), getContainerId());
|
||||
updated = cacheSession.getDelegate().getRoleById(cached.getId(), realm);
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
}
|
||||
|
|
|
@ -18,40 +18,94 @@
|
|||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.notifications.Listener;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
import org.keycloak.models.cache.infinispan.events.UserCacheInvalidationEvent;
|
||||
import org.keycloak.models.cache.infinispan.stream.InRealmPredicate;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Listener
|
||||
public class UserCacheManager extends CacheManager {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(UserCacheManager.class);
|
||||
private static final Logger logger = Logger.getLogger(UserCacheManager.class);
|
||||
|
||||
protected volatile boolean enabled = true;
|
||||
|
||||
public UserCacheManager(Cache<String, Revisioned> cache, Cache<String, Long> revisions) {
|
||||
super(cache, revisions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
revisions.clear();
|
||||
}
|
||||
|
||||
|
||||
public void userUpdatedInvalidations(String userId, String username, String email, String realmId, Set<String> invalidations) {
|
||||
invalidations.add(userId);
|
||||
if (email != null) invalidations.add(UserCacheSession.getUserByEmailCacheKey(realmId, email));
|
||||
invalidations.add(UserCacheSession.getUserByUsernameCacheKey(realmId, username));
|
||||
}
|
||||
|
||||
// Fully invalidate user including consents and federatedIdentity links.
|
||||
public void fullUserInvalidation(String userId, String username, String email, String realmId, boolean identityFederationEnabled, Map<String, String> federatedIdentities, Set<String> invalidations) {
|
||||
userUpdatedInvalidations(userId, username, email, realmId, invalidations);
|
||||
|
||||
if (identityFederationEnabled) {
|
||||
// Invalidate all keys for lookup this user by any identityProvider link
|
||||
for (Map.Entry<String, String> socialLink : federatedIdentities.entrySet()) {
|
||||
String fedIdentityCacheKey = UserCacheSession.getUserByFederatedIdentityCacheKey(realmId, socialLink.getKey(), socialLink.getValue());
|
||||
invalidations.add(fedIdentityCacheKey);
|
||||
}
|
||||
|
||||
// Invalidate federationLinks of user
|
||||
invalidations.add(UserCacheSession.getFederatedIdentityLinksCacheKey(userId));
|
||||
}
|
||||
|
||||
// Consents
|
||||
invalidations.add(UserCacheSession.getConsentCacheKey(userId));
|
||||
}
|
||||
|
||||
public void federatedIdentityLinkUpdatedInvalidation(String userId, Set<String> invalidations) {
|
||||
invalidations.add(UserCacheSession.getFederatedIdentityLinksCacheKey(userId));
|
||||
}
|
||||
|
||||
public void federatedIdentityLinkRemovedInvalidation(String userId, String realmId, String identityProviderId, String socialUserId, Set<String> invalidations) {
|
||||
invalidations.add(UserCacheSession.getFederatedIdentityLinksCacheKey(userId));
|
||||
if (identityProviderId != null) {
|
||||
invalidations.add(UserCacheSession.getUserByFederatedIdentityCacheKey(realmId, identityProviderId, socialUserId));
|
||||
}
|
||||
}
|
||||
|
||||
public void consentInvalidation(String userId, Set<String> invalidations) {
|
||||
invalidations.add(UserCacheSession.getConsentCacheKey(userId));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Predicate<Map.Entry<String, Revisioned>> getInvalidationPredicate(Object object) {
|
||||
return null;
|
||||
protected void addInvalidationsFromEvent(InvalidationEvent event, Set<String> invalidations) {
|
||||
if (event instanceof UserCacheInvalidationEvent) {
|
||||
((UserCacheInvalidationEvent) event).addInvalidations(this, invalidations);
|
||||
}
|
||||
}
|
||||
|
||||
public void invalidateRealmUsers(String realm, Set<String> invalidations) {
|
||||
addInvalidations(InRealmPredicate.create().realm(realm), invalidations);
|
||||
InRealmPredicate inRealmPredicate = getInRealmPredicate(realm);
|
||||
addInvalidations(inRealmPredicate, invalidations);
|
||||
}
|
||||
|
||||
private InRealmPredicate getInRealmPredicate(String realmId) {
|
||||
return InRealmPredicate.create().realm(realmId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.models.cache.infinispan;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
import org.keycloak.common.constants.ServiceAccountConstants;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
|
@ -42,6 +43,12 @@ import org.keycloak.models.cache.infinispan.entities.CachedUser;
|
|||
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedUserConsents;
|
||||
import org.keycloak.models.cache.infinispan.entities.UserListQuery;
|
||||
import org.keycloak.models.cache.infinispan.events.UserCacheRealmInvalidationEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.UserConsentsUpdatedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.UserFederationLinkRemovedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.UserFederationLinkUpdatedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.UserFullInvalidationEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.UserUpdatedEvent;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
@ -72,6 +79,7 @@ public class UserCacheSession implements UserCache {
|
|||
|
||||
protected Set<String> invalidations = new HashSet<>();
|
||||
protected Set<String> realmInvalidations = new HashSet<>();
|
||||
protected Set<InvalidationEvent> invalidationEvents = new HashSet<>(); // Events to be sent across cluster
|
||||
protected Map<String, UserModel> managedUsers = new HashMap<>();
|
||||
|
||||
public UserCacheSession(UserCacheManager cache, KeycloakSession session) {
|
||||
|
@ -85,7 +93,7 @@ public class UserCacheSession implements UserCache {
|
|||
public void clear() {
|
||||
cache.clear();
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
cluster.notify(InfinispanUserCacheProviderFactory.USER_CLEAR_CACHE_EVENTS, new ClearCacheEvent());
|
||||
cluster.notify(InfinispanUserCacheProviderFactory.USER_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), true);
|
||||
}
|
||||
|
||||
public UserProvider getDelegate() {
|
||||
|
@ -97,10 +105,8 @@ public class UserCacheSession implements UserCache {
|
|||
}
|
||||
|
||||
public void registerUserInvalidation(RealmModel realm,CachedUser user) {
|
||||
invalidations.add(user.getId());
|
||||
if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
|
||||
invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
|
||||
if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
|
||||
cache.userUpdatedInvalidations(user.getId(), user.getUsername(), user.getEmail(), user.getRealm(), invalidations);
|
||||
invalidationEvents.add(UserUpdatedEvent.create(user.getId(), user.getUsername(), user.getEmail(), user.getRealm()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -108,16 +114,14 @@ public class UserCacheSession implements UserCache {
|
|||
if (user instanceof CachedUserModel) {
|
||||
((CachedUserModel)user).invalidate();
|
||||
} else {
|
||||
invalidations.add(user.getId());
|
||||
if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
|
||||
invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
|
||||
if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
|
||||
cache.userUpdatedInvalidations(user.getId(), user.getUsername(), user.getEmail(), realm.getId(), invalidations);
|
||||
invalidationEvents.add(UserUpdatedEvent.create(user.getId(), user.getUsername(), user.getEmail(), realm.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(RealmModel realm) {
|
||||
realmInvalidations.add(realm.getId());
|
||||
addRealmInvalidation(realm.getId());
|
||||
}
|
||||
|
||||
protected void runInvalidations() {
|
||||
|
@ -127,6 +131,8 @@ public class UserCacheSession implements UserCache {
|
|||
for (String invalidation : invalidations) {
|
||||
cache.invalidateObject(invalidation);
|
||||
}
|
||||
|
||||
cache.sendInvalidationEvents(session, invalidationEvents);
|
||||
}
|
||||
|
||||
private KeycloakTransaction getTransaction() {
|
||||
|
@ -201,19 +207,23 @@ public class UserCacheSession implements UserCache {
|
|||
return adapter;
|
||||
}
|
||||
|
||||
public String getUserByUsernameCacheKey(String realmId, String username) {
|
||||
static String getUserByUsernameCacheKey(String realmId, String username) {
|
||||
return realmId + ".username." + username;
|
||||
}
|
||||
|
||||
public String getUserByEmailCacheKey(String realmId, String email) {
|
||||
static String getUserByEmailCacheKey(String realmId, String email) {
|
||||
return realmId + ".email." + email;
|
||||
}
|
||||
|
||||
public String getUserByFederatedIdentityCacheKey(String realmId, FederatedIdentityModel socialLink) {
|
||||
return realmId + ".idp." + socialLink.getIdentityProvider() + "." + socialLink.getUserId();
|
||||
private static String getUserByFederatedIdentityCacheKey(String realmId, FederatedIdentityModel socialLink) {
|
||||
return getUserByFederatedIdentityCacheKey(realmId, socialLink.getIdentityProvider(), socialLink.getUserId());
|
||||
}
|
||||
|
||||
public String getFederatedIdentityLinksCacheKey(String userId) {
|
||||
static String getUserByFederatedIdentityCacheKey(String realmId, String identityProvider, String socialUserId) {
|
||||
return realmId + ".idp." + identityProvider + "." + socialUserId;
|
||||
}
|
||||
|
||||
static String getFederatedIdentityLinksCacheKey(String userId) {
|
||||
return userId + ".idplinks";
|
||||
}
|
||||
|
||||
|
@ -655,27 +665,32 @@ public class UserCacheSession implements UserCache {
|
|||
|
||||
@Override
|
||||
public void updateConsent(RealmModel realm, String userId, UserConsentModel consent) {
|
||||
invalidations.add(getConsentCacheKey(userId));
|
||||
invalidateConsent(userId);
|
||||
getDelegate().updateConsent(realm, userId, consent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId) {
|
||||
invalidations.add(getConsentCacheKey(userId));
|
||||
invalidateConsent(userId);
|
||||
return getDelegate().revokeConsentForClient(realm, userId, clientInternalId);
|
||||
}
|
||||
|
||||
public String getConsentCacheKey(String userId) {
|
||||
static String getConsentCacheKey(String userId) {
|
||||
return userId + ".consents";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addConsent(RealmModel realm, String userId, UserConsentModel consent) {
|
||||
invalidations.add(getConsentCacheKey(userId));
|
||||
invalidateConsent(userId);
|
||||
getDelegate().addConsent(realm, userId, consent);
|
||||
}
|
||||
|
||||
private void invalidateConsent(String userId) {
|
||||
cache.consentInvalidation(userId, invalidations);
|
||||
invalidationEvents.add(UserConsentsUpdatedEvent.create(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserConsentModel getConsentByClient(RealmModel realm, String userId, String clientId) {
|
||||
logger.tracev("getConsentByClient: {0}", userId);
|
||||
|
@ -754,7 +769,7 @@ public class UserCacheSession implements UserCache {
|
|||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||
UserModel user = getDelegate().addUser(realm, id, username, addDefaultRoles, addDefaultRoles);
|
||||
// just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
|
||||
invalidateUser(realm, user);
|
||||
fullyInvalidateUser(realm, user);
|
||||
managedUsers.put(user.getId(), user);
|
||||
return user;
|
||||
}
|
||||
|
@ -763,94 +778,89 @@ public class UserCacheSession implements UserCache {
|
|||
public UserModel addUser(RealmModel realm, String username) {
|
||||
UserModel user = getDelegate().addUser(realm, username);
|
||||
// just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
|
||||
invalidateUser(realm, user);
|
||||
fullyInvalidateUser(realm, user);
|
||||
managedUsers.put(user.getId(), user);
|
||||
return user;
|
||||
}
|
||||
|
||||
protected void invalidateUser(RealmModel realm, UserModel user) {
|
||||
// just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
|
||||
// just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
|
||||
protected void fullyInvalidateUser(RealmModel realm, UserModel user) {
|
||||
Set<FederatedIdentityModel> federatedIdentities = realm.isIdentityFederationEnabled() ? getFederatedIdentities(user, realm) : null;
|
||||
|
||||
if (realm.isIdentityFederationEnabled()) {
|
||||
// Invalidate all keys for lookup this user by any identityProvider link
|
||||
Set<FederatedIdentityModel> federatedIdentities = getFederatedIdentities(user, realm);
|
||||
for (FederatedIdentityModel socialLink : federatedIdentities) {
|
||||
String fedIdentityCacheKey = getUserByFederatedIdentityCacheKey(realm.getId(), socialLink);
|
||||
invalidations.add(fedIdentityCacheKey);
|
||||
}
|
||||
UserFullInvalidationEvent event = UserFullInvalidationEvent.create(user.getId(), user.getUsername(), user.getEmail(), realm.getId(), realm.isIdentityFederationEnabled(), federatedIdentities);
|
||||
|
||||
// Invalidate federationLinks of user
|
||||
invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
|
||||
}
|
||||
|
||||
invalidations.add(user.getId());
|
||||
if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
|
||||
invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
|
||||
cache.fullUserInvalidation(user.getId(), user.getUsername(), user.getEmail(), realm.getId(), realm.isIdentityFederationEnabled(), event.getFederatedIdentities(), invalidations);
|
||||
invalidationEvents.add(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeUser(RealmModel realm, UserModel user) {
|
||||
invalidateUser(realm, user);
|
||||
fullyInvalidateUser(realm, user);
|
||||
return getDelegate().removeUser(realm, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
|
||||
invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
|
||||
invalidateFederationLink(user.getId());
|
||||
getDelegate().addFederatedIdentity(realm, user, socialLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
|
||||
invalidations.add(getFederatedIdentityLinksCacheKey(federatedUser.getId()));
|
||||
invalidateFederationLink(federatedUser.getId());
|
||||
getDelegate().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
|
||||
}
|
||||
|
||||
private void invalidateFederationLink(String userId) {
|
||||
cache.federatedIdentityLinkUpdatedInvalidation(userId, invalidations);
|
||||
invalidationEvents.add(UserFederationLinkUpdatedEvent.create(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
|
||||
// Needs to invalidate both directions
|
||||
FederatedIdentityModel socialLink = getFederatedIdentity(user, socialProvider, realm);
|
||||
invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
|
||||
if (socialLink != null) {
|
||||
invalidations.add(getUserByFederatedIdentityCacheKey(realm.getId(), socialLink));
|
||||
}
|
||||
|
||||
UserFederationLinkRemovedEvent event = UserFederationLinkRemovedEvent.create(user.getId(), realm.getId(), socialLink);
|
||||
cache.federatedIdentityLinkRemovedInvalidation(user.getId(), realm.getId(), event.getIdentityProviderId(), event.getSocialUserId(), invalidations);
|
||||
invalidationEvents.add(event);
|
||||
|
||||
return getDelegate().removeFederatedIdentity(realm, user, socialProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
||||
realmInvalidations.add(realm.getId()); // easier to just invalidate whole realm
|
||||
addRealmInvalidation(realm.getId()); // easier to just invalidate whole realm
|
||||
getDelegate().grantToAllUsers(realm, role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm) {
|
||||
realmInvalidations.add(realm.getId());
|
||||
addRealmInvalidation(realm.getId());
|
||||
getDelegate().preRemove(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, RoleModel role) {
|
||||
realmInvalidations.add(realm.getId()); // easier to just invalidate whole realm
|
||||
addRealmInvalidation(realm.getId()); // easier to just invalidate whole realm
|
||||
getDelegate().preRemove(realm, role);
|
||||
}
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
realmInvalidations.add(realm.getId()); // easier to just invalidate whole realm
|
||||
addRealmInvalidation(realm.getId()); // easier to just invalidate whole realm
|
||||
getDelegate().preRemove(realm, group);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, UserFederationProviderModel link) {
|
||||
realmInvalidations.add(realm.getId()); // easier to just invalidate whole realm
|
||||
addRealmInvalidation(realm.getId()); // easier to just invalidate whole realm
|
||||
getDelegate().preRemove(realm, link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, ClientModel client) {
|
||||
realmInvalidations.add(realm.getId()); // easier to just invalidate whole realm
|
||||
addRealmInvalidation(realm.getId()); // easier to just invalidate whole realm
|
||||
getDelegate().preRemove(realm, client);
|
||||
}
|
||||
|
||||
|
@ -862,9 +872,14 @@ public class UserCacheSession implements UserCache {
|
|||
@Override
|
||||
public void preRemove(RealmModel realm, ComponentModel component) {
|
||||
if (!component.getProviderType().equals(UserStorageProvider.class.getName())) return;
|
||||
realmInvalidations.add(realm.getId()); // easier to just invalidate whole realm
|
||||
addRealmInvalidation(realm.getId()); // easier to just invalidate whole realm
|
||||
getDelegate().preRemove(realm, component);
|
||||
|
||||
}
|
||||
|
||||
private void addRealmInvalidation(String realmId) {
|
||||
realmInvalidations.add(realmId);
|
||||
invalidationEvents.add(UserCacheRealmInvalidationEvent.create(realmId));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -135,7 +135,6 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
|||
}
|
||||
|
||||
protected List<String> defaultGroups = new LinkedList<String>();
|
||||
protected Set<String> groups = new HashSet<String>();
|
||||
protected List<String> clientTemplates= new LinkedList<>();
|
||||
protected boolean internationalizationEnabled;
|
||||
protected Set<String> supportedLocales;
|
||||
|
@ -237,9 +236,7 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
|||
executionsById.put(execution.getId(), execution);
|
||||
}
|
||||
}
|
||||
for (GroupModel group : model.getGroups()) {
|
||||
groups.add(group.getId());
|
||||
}
|
||||
|
||||
for (AuthenticatorConfigModel authenticator : model.getAuthenticatorConfigs()) {
|
||||
authenticatorConfigs.put(authenticator.getId(), authenticator);
|
||||
}
|
||||
|
@ -541,10 +538,6 @@ public class CachedRealm extends AbstractExtendableRevisioned {
|
|||
return clientAuthenticationFlow;
|
||||
}
|
||||
|
||||
public Set<String> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public List<String> getDefaultGroups() {
|
||||
return defaultGroups;
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
package org.keycloak.models.cache.infinispan.entities;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface ClientTemplateQuery extends InRealm {
|
||||
Set<String> getTemplates();
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
package org.keycloak.models.cache.infinispan;
|
||||
package org.keycloak.models.cache.infinispan.entities;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
||||
import org.keycloak.models.cache.infinispan.entities.GroupQuery;
|
||||
|
||||
import java.util.Set;
|
||||
|
|
@ -59,7 +59,8 @@ public class RoleListQuery extends AbstractRevisioned implements RoleQuery, InCl
|
|||
public String toString() {
|
||||
return "RoleListQuery{" +
|
||||
"id='" + getId() + "'" +
|
||||
"realmName='" + realmName + '\'' +
|
||||
", realmName='" + realmName + '\'' +
|
||||
", clientUuid='" + client + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
55
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/ClientAddedEvent.java
vendored
Normal file
55
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/ClientAddedEvent.java
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientAddedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String clientUuid;
|
||||
private String clientId;
|
||||
private String realmId;
|
||||
|
||||
public static ClientAddedEvent create(String clientUuid, String clientId, String realmId) {
|
||||
ClientAddedEvent event = new ClientAddedEvent();
|
||||
event.clientUuid = clientUuid;
|
||||
event.clientId = clientId;
|
||||
event.realmId = realmId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return clientUuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ClientAddedEvent [ realmId=%s, clientUuid=%s, clientId=%s ]", realmId, clientUuid, clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.clientAdded(realmId, clientUuid, clientId, invalidations);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientRemovedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String clientUuid;
|
||||
private String clientId;
|
||||
private String realmId;
|
||||
// roleId -> roleName
|
||||
private Map<String, String> clientRoles;
|
||||
|
||||
public static ClientRemovedEvent create(ClientModel client) {
|
||||
ClientRemovedEvent event = new ClientRemovedEvent();
|
||||
|
||||
event.realmId = client.getRealm().getId();
|
||||
event.clientUuid = client.getId();
|
||||
event.clientId = client.getClientId();
|
||||
event.clientRoles = new HashMap<>();
|
||||
for (RoleModel clientRole : client.getRoles()) {
|
||||
event.clientRoles.put(clientRole.getId(), clientRole.getName());
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return clientUuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ClientRemovedEvent [ realmId=%s, clientUuid=%s, clientId=%s, clientRoleIds=%s ]", realmId, clientUuid, clientId, clientRoles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.clientRemoval(realmId, clientUuid, clientId, invalidations);
|
||||
|
||||
// Separate iteration for all client roles to invalidate records dependent on them
|
||||
for (Map.Entry<String, String> clientRole : clientRoles.entrySet()) {
|
||||
String roleId = clientRole.getKey();
|
||||
String roleName = clientRole.getValue();
|
||||
realmCache.roleRemoval(roleId, roleName, clientUuid, invalidations);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientTemplateEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String clientTemplateId;
|
||||
|
||||
public static ClientTemplateEvent create(String clientTemplateId) {
|
||||
ClientTemplateEvent event = new ClientTemplateEvent();
|
||||
event.clientTemplateId = clientTemplateId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return clientTemplateId;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClientTemplateEvent [ " + clientTemplateId + " ]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
// Nothing. ID was already invalidated
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientUpdatedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String clientUuid;
|
||||
private String clientId;
|
||||
private String realmId;
|
||||
|
||||
public static ClientUpdatedEvent create(String clientUuid, String clientId, String realmId) {
|
||||
ClientUpdatedEvent event = new ClientUpdatedEvent();
|
||||
event.clientUuid = clientUuid;
|
||||
event.clientId = clientId;
|
||||
event.realmId = realmId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return clientUuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ClientUpdatedEvent [ realmId=%s, clientUuid=%s, clientId=%s ]", realmId, clientUuid, clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.clientUpdated(realmId, clientUuid, clientId, invalidations);
|
||||
}
|
||||
}
|
54
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/GroupAddedEvent.java
vendored
Normal file
54
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/GroupAddedEvent.java
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class GroupAddedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String groupId;
|
||||
private String realmId;
|
||||
|
||||
public static GroupAddedEvent create(String groupId, String realmId) {
|
||||
GroupAddedEvent event = new GroupAddedEvent();
|
||||
event.realmId = realmId;
|
||||
event.groupId = groupId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GroupAddedEvent [ realmId=%s, groupId=%s ]", realmId, groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.groupQueriesInvalidations(realmId, invalidations);
|
||||
}
|
||||
}
|
64
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/GroupMovedEvent.java
vendored
Normal file
64
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/GroupMovedEvent.java
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class GroupMovedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String groupId;
|
||||
private String newParentId; // null if moving to top-level
|
||||
private String oldParentId; // null if moving from top-level
|
||||
private String realmId;
|
||||
|
||||
public static GroupMovedEvent create(GroupModel group, GroupModel toParent, String realmId) {
|
||||
GroupMovedEvent event = new GroupMovedEvent();
|
||||
event.realmId = realmId;
|
||||
event.groupId = group.getId();
|
||||
event.oldParentId = group.getParentId();
|
||||
event.newParentId = toParent==null ? null : toParent.getId();
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GroupMovedEvent [ realmId=%s, groupId=%s, newParentId=%s, oldParentId=%s ]", realmId, groupId, newParentId, oldParentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.groupQueriesInvalidations(realmId, invalidations);
|
||||
if (newParentId != null) {
|
||||
invalidations.add(newParentId);
|
||||
}
|
||||
if (oldParentId != null) {
|
||||
invalidations.add(oldParentId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class GroupRemovedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String groupId;
|
||||
private String parentId;
|
||||
private String realmId;
|
||||
|
||||
public static GroupRemovedEvent create(GroupModel group, String realmId) {
|
||||
GroupRemovedEvent event = new GroupRemovedEvent();
|
||||
event.realmId = realmId;
|
||||
event.groupId = group.getId();
|
||||
event.parentId = group.getParentId();
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GroupRemovedEvent [ realmId=%s, groupId=%s, parentId=%s ]", realmId, groupId, parentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.groupQueriesInvalidations(realmId, invalidations);
|
||||
if (parentId != null) {
|
||||
invalidations.add(parentId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class GroupUpdatedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String groupId;
|
||||
|
||||
public static GroupUpdatedEvent create(String groupId) {
|
||||
GroupUpdatedEvent event = new GroupUpdatedEvent();
|
||||
event.groupId = groupId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GroupUpdatedEvent [ " + groupId + " ]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
// Nothing. ID already invalidated
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class InvalidationEvent implements ClusterEvent {
|
||||
|
||||
public abstract String getId();
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClass().hashCode() * 13 + getId().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (!obj.getClass().equals(this.getClass())) return false;
|
||||
|
||||
InvalidationEvent that = (InvalidationEvent) obj;
|
||||
if (!that.getId().equals(getId())) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface RealmCacheInvalidationEvent {
|
||||
|
||||
void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations);
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class RealmRemovedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String realmId;
|
||||
private String realmName;
|
||||
|
||||
public static RealmRemovedEvent create(String realmId, String realmName) {
|
||||
RealmRemovedEvent event = new RealmRemovedEvent();
|
||||
event.realmId = realmId;
|
||||
event.realmName = realmName;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("RealmRemovedEvent [ realmId=%s, realmName=%s ]", realmId, realmName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.realmRemoval(realmId, realmName, invalidations);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class RealmUpdatedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String realmId;
|
||||
private String realmName;
|
||||
|
||||
public static RealmUpdatedEvent create(String realmId, String realmName) {
|
||||
RealmUpdatedEvent event = new RealmUpdatedEvent();
|
||||
event.realmId = realmId;
|
||||
event.realmName = realmName;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("RealmUpdatedEvent [ realmId=%s, realmName=%s ]", realmId, realmName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.realmUpdated(realmId, realmName, invalidations);
|
||||
}
|
||||
}
|
53
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RoleAddedEvent.java
vendored
Normal file
53
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RoleAddedEvent.java
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class RoleAddedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String roleId;
|
||||
private String containerId;
|
||||
|
||||
public static RoleAddedEvent create(String roleId, String containerId) {
|
||||
RoleAddedEvent event = new RoleAddedEvent();
|
||||
event.roleId = roleId;
|
||||
event.containerId = containerId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("RoleAddedEvent [ roleId=%s, containerId=%s ]", roleId, containerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.roleAdded(containerId, invalidations);
|
||||
}
|
||||
}
|
55
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RoleRemovedEvent.java
vendored
Normal file
55
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RoleRemovedEvent.java
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class RoleRemovedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String roleId;
|
||||
private String roleName;
|
||||
private String containerId;
|
||||
|
||||
public static RoleRemovedEvent create(String roleId, String roleName, String containerId) {
|
||||
RoleRemovedEvent event = new RoleRemovedEvent();
|
||||
event.roleId = roleId;
|
||||
event.roleName = roleName;
|
||||
event.containerId = containerId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("RoleRemovedEvent [ roleId=%s, containerId=%s ]", roleId, containerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.roleRemoval(roleId, roleName, containerId, invalidations);
|
||||
}
|
||||
}
|
55
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RoleUpdatedEvent.java
vendored
Normal file
55
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RoleUpdatedEvent.java
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class RoleUpdatedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String roleId;
|
||||
private String roleName;
|
||||
private String containerId;
|
||||
|
||||
public static RoleUpdatedEvent create(String roleId, String roleName, String containerId) {
|
||||
RoleUpdatedEvent event = new RoleUpdatedEvent();
|
||||
event.roleId = roleId;
|
||||
event.roleName = roleName;
|
||||
event.containerId = containerId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("RoleUpdatedEvent [ roleId=%s, roleName=%s, containerId=%s ]", roleId, roleName, containerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.roleUpdated(containerId, roleName, invalidations);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.UserCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface UserCacheInvalidationEvent {
|
||||
|
||||
void addInvalidations(UserCacheManager userCache, Set<String> invalidations);
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.UserCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class UserCacheRealmInvalidationEvent extends InvalidationEvent implements UserCacheInvalidationEvent {
|
||||
|
||||
private String realmId;
|
||||
|
||||
public static UserCacheRealmInvalidationEvent create(String realmId) {
|
||||
UserCacheRealmInvalidationEvent event = new UserCacheRealmInvalidationEvent();
|
||||
event.realmId = realmId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return realmId; // Just a placeholder
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("UserCacheRealmInvalidationEvent [ realmId=%s ]", realmId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(UserCacheManager userCache, Set<String> invalidations) {
|
||||
userCache.invalidateRealmUsers(realmId, invalidations);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.UserCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class UserConsentsUpdatedEvent extends InvalidationEvent implements UserCacheInvalidationEvent {
|
||||
|
||||
private String userId;
|
||||
|
||||
public static UserConsentsUpdatedEvent create(String userId) {
|
||||
UserConsentsUpdatedEvent event = new UserConsentsUpdatedEvent();
|
||||
event.userId = userId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("UserConsentsUpdatedEvent [ userId=%s ]", userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(UserCacheManager userCache, Set<String> invalidations) {
|
||||
userCache.consentInvalidation(userId, invalidations);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.cache.infinispan.UserCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class UserFederationLinkRemovedEvent extends InvalidationEvent implements UserCacheInvalidationEvent {
|
||||
|
||||
private String userId;
|
||||
private String realmId;
|
||||
private String identityProviderId;
|
||||
private String socialUserId;
|
||||
|
||||
public static UserFederationLinkRemovedEvent create(String userId, String realmId, FederatedIdentityModel socialLink) {
|
||||
UserFederationLinkRemovedEvent event = new UserFederationLinkRemovedEvent();
|
||||
event.userId = userId;
|
||||
event.realmId = realmId;
|
||||
if (socialLink != null) {
|
||||
event.identityProviderId = socialLink.getIdentityProvider();
|
||||
event.socialUserId = socialLink.getUserId();
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public String getIdentityProviderId() {
|
||||
return identityProviderId;
|
||||
}
|
||||
|
||||
public String getSocialUserId() {
|
||||
return socialUserId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("UserFederationLinkRemovedEvent [ userId=%s, identityProviderId=%s, socialUserId=%s ]", userId, identityProviderId, socialUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(UserCacheManager userCache, Set<String> invalidations) {
|
||||
userCache.federatedIdentityLinkRemovedInvalidation(userId, realmId, identityProviderId, socialUserId, invalidations);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.UserCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class UserFederationLinkUpdatedEvent extends InvalidationEvent implements UserCacheInvalidationEvent {
|
||||
|
||||
private String userId;
|
||||
|
||||
public static UserFederationLinkUpdatedEvent create(String userId) {
|
||||
UserFederationLinkUpdatedEvent event = new UserFederationLinkUpdatedEvent();
|
||||
event.userId = userId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("UserFederationLinkUpdatedEvent [ userId=%s ]", userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(UserCacheManager userCache, Set<String> invalidations) {
|
||||
userCache.federatedIdentityLinkUpdatedInvalidation(userId, invalidations);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.cache.infinispan.UserCacheManager;
|
||||
|
||||
/**
|
||||
* Used when user added/removed
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class UserFullInvalidationEvent extends InvalidationEvent implements UserCacheInvalidationEvent {
|
||||
|
||||
private String userId;
|
||||
private String username;
|
||||
private String email;
|
||||
private String realmId;
|
||||
private boolean identityFederationEnabled;
|
||||
private Map<String, String> federatedIdentities;
|
||||
|
||||
public static UserFullInvalidationEvent create(String userId, String username, String email, String realmId, boolean identityFederationEnabled, Collection<FederatedIdentityModel> federatedIdentities) {
|
||||
UserFullInvalidationEvent event = new UserFullInvalidationEvent();
|
||||
event.userId = userId;
|
||||
event.username = username;
|
||||
event.email = email;
|
||||
event.realmId = realmId;
|
||||
|
||||
event.identityFederationEnabled = identityFederationEnabled;
|
||||
if (identityFederationEnabled) {
|
||||
event.federatedIdentities = new HashMap<>();
|
||||
for (FederatedIdentityModel socialLink : federatedIdentities) {
|
||||
event.federatedIdentities.put(socialLink.getIdentityProvider(), socialLink.getUserId());
|
||||
}
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public Map<String, String> getFederatedIdentities() {
|
||||
return federatedIdentities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("UserFullInvalidationEvent [ userId=%s, username=%s, email=%s ]", userId, username, email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(UserCacheManager userCache, Set<String> invalidations) {
|
||||
userCache.fullUserInvalidation(userId, username, email, realmId, identityFederationEnabled, federatedIdentities, invalidations);
|
||||
}
|
||||
}
|
57
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/UserUpdatedEvent.java
vendored
Normal file
57
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/UserUpdatedEvent.java
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.models.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.UserCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class UserUpdatedEvent extends InvalidationEvent implements UserCacheInvalidationEvent {
|
||||
|
||||
private String userId;
|
||||
private String username;
|
||||
private String email;
|
||||
private String realmId;
|
||||
|
||||
public static UserUpdatedEvent create(String userId, String username, String email, String realmId) {
|
||||
UserUpdatedEvent event = new UserUpdatedEvent();
|
||||
event.userId = userId;
|
||||
event.username = username;
|
||||
event.email = email;
|
||||
event.realmId = realmId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("UserUpdatedEvent [ userId=%s, username=%s, email=%s ]", userId, username, email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(UserCacheManager userCache, Set<String> invalidations) {
|
||||
userCache.userUpdatedInvalidations(userId, username, email, realmId, invalidations);
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package org.keycloak.models.cache.infinispan.stream;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.cache.infinispan.entities.ClientQuery;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ClientQueryPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
|
||||
protected static final Logger logger = Logger.getLogger(ClientQueryPredicate.class);
|
||||
private String client;
|
||||
private String inRealm;
|
||||
|
||||
public static ClientQueryPredicate create() {
|
||||
return new ClientQueryPredicate();
|
||||
}
|
||||
|
||||
public ClientQueryPredicate client(String client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientQueryPredicate inRealm(String inRealm) {
|
||||
this.inRealm = inRealm;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean test(Map.Entry<String, Revisioned> entry) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) return false;
|
||||
if (!(value instanceof ClientQuery)) return false;
|
||||
ClientQuery query = (ClientQuery)value;
|
||||
if (client != null && !query.getClients().contains(client)) return false;
|
||||
if (inRealm != null && !query.getRealm().equals(inRealm)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package org.keycloak.models.cache.infinispan.stream;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.entities.ClientTemplateQuery;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ClientTemplateQueryPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
|
||||
private String template;
|
||||
|
||||
public static ClientTemplateQueryPredicate create() {
|
||||
return new ClientTemplateQueryPredicate();
|
||||
}
|
||||
|
||||
public ClientTemplateQueryPredicate template(String template) {
|
||||
this.template = template;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean test(Map.Entry<String, Revisioned> entry) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) return false;
|
||||
if (!(value instanceof ClientTemplateQuery)) return false;
|
||||
ClientTemplateQuery query = (ClientTemplateQuery)value;
|
||||
|
||||
|
||||
return query.getTemplates().contains(template);
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package org.keycloak.models.cache.infinispan.stream;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.entities.GroupQuery;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class GroupQueryPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
|
||||
private String group;
|
||||
|
||||
public static GroupQueryPredicate create() {
|
||||
return new GroupQueryPredicate();
|
||||
}
|
||||
|
||||
public GroupQueryPredicate group(String group) {
|
||||
this.group = group;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean test(Map.Entry<String, Revisioned> entry) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) return false;
|
||||
if (!(value instanceof GroupQuery)) return false;
|
||||
GroupQuery query = (GroupQuery)value;
|
||||
|
||||
|
||||
return query.getGroups().contains(group);
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package org.keycloak.models.cache.infinispan.stream;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.entities.RealmQuery;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RealmQueryPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
|
||||
private String realm;
|
||||
|
||||
public static RealmQueryPredicate create() {
|
||||
return new RealmQueryPredicate();
|
||||
}
|
||||
|
||||
public RealmQueryPredicate realm(String realm) {
|
||||
this.realm = realm;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean test(Map.Entry<String, Revisioned> entry) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) return false;
|
||||
if (!(value instanceof RealmQuery)) return false;
|
||||
RealmQuery query = (RealmQuery)value;
|
||||
|
||||
|
||||
return query.getRealms().contains(realm);
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package org.keycloak.models.cache.infinispan.stream;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
import org.keycloak.models.cache.infinispan.entities.RoleQuery;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RoleQueryPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
|
||||
private String role;
|
||||
|
||||
public static RoleQueryPredicate create() {
|
||||
return new RoleQueryPredicate();
|
||||
}
|
||||
|
||||
public RoleQueryPredicate role(String role) {
|
||||
this.role = role;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean test(Map.Entry<String, Revisioned> entry) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) return false;
|
||||
if (!(value instanceof RoleQuery)) return false;
|
||||
RoleQuery query = (RoleQuery)value;
|
||||
|
||||
|
||||
return query.getRoles().contains(role);
|
||||
}
|
||||
}
|
|
@ -431,8 +431,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserRemoved(RealmModel realm, UserModel user) {
|
||||
|
||||
protected void onUserRemoved(RealmModel realm, UserModel user) {
|
||||
removeUserSessions(realm, user, true);
|
||||
removeUserSessions(realm, user, false);
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.UserSessionProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||
|
@ -45,7 +46,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||
private Config.Scope config;
|
||||
|
||||
@Override
|
||||
public UserSessionProvider create(KeycloakSession session) {
|
||||
public InfinispanUserSessionProvider create(KeycloakSession session) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||
Cache<String, SessionEntity> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
|
||||
|
@ -73,6 +74,11 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||
public void onEvent(ProviderEvent event) {
|
||||
if (event instanceof PostMigrationEvent) {
|
||||
loadPersistentSessions(factory, maxErrors, sessionsPerSegment);
|
||||
} else if (event instanceof UserModel.UserRemovedEvent) {
|
||||
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
|
||||
|
||||
InfinispanUserSessionProvider provider = (InfinispanUserSessionProvider) userRemovedEvent.getKeycloakSession().getProvider(UserSessionProvider.class, getId());
|
||||
provider.onUserRemoved(userRemovedEvent.getRealm(), userRemovedEvent.getUser());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -92,13 +92,13 @@ public class InfinispanUserSessionInitializer {
|
|||
|
||||
|
||||
private boolean isFinished() {
|
||||
InitializerState state = (InitializerState) workCache.get(stateKey);
|
||||
InitializerState state = getStateFromCache();
|
||||
return state != null && state.isFinished();
|
||||
}
|
||||
|
||||
|
||||
private InitializerState getOrCreateInitializerState() {
|
||||
InitializerState state = (InitializerState) workCache.get(stateKey);
|
||||
InitializerState state = getStateFromCache();
|
||||
if (state == null) {
|
||||
final int[] count = new int[1];
|
||||
|
||||
|
@ -128,6 +128,12 @@ public class InfinispanUserSessionInitializer {
|
|||
|
||||
}
|
||||
|
||||
private InitializerState getStateFromCache() {
|
||||
// TODO: We ignore cacheStore for now, so that in Cross-DC scenario (with RemoteStore enabled) is the remoteStore ignored. This means that every DC needs to load offline sessions separately.
|
||||
return (InitializerState) workCache.getAdvancedCache()
|
||||
.withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD)
|
||||
.get(stateKey);
|
||||
}
|
||||
|
||||
private void saveStateToCache(final InitializerState state) {
|
||||
|
||||
|
@ -138,8 +144,9 @@ public class InfinispanUserSessionInitializer {
|
|||
public void run() {
|
||||
|
||||
// Save this synchronously to ensure all nodes read correct state
|
||||
// TODO: We ignore cacheStore for now, so that in Cross-DC scenario (with RemoteStore enabled) is the remoteStore ignored. This means that every DC needs to load offline sessions separately.
|
||||
InfinispanUserSessionInitializer.this.workCache.getAdvancedCache().
|
||||
withFlags(Flag.IGNORE_RETURN_VALUES, Flag.FORCE_SYNCHRONOUS)
|
||||
withFlags(Flag.IGNORE_RETURN_VALUES, Flag.FORCE_SYNCHRONOUS, Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD)
|
||||
.put(stateKey, state);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* 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.cluster.infinispan;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.client.hotrod.Flag;
|
||||
import org.infinispan.client.hotrod.RemoteCache;
|
||||
import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated;
|
||||
import org.infinispan.client.hotrod.annotation.ClientCacheEntryModified;
|
||||
import org.infinispan.client.hotrod.annotation.ClientListener;
|
||||
import org.infinispan.client.hotrod.event.ClientCacheEntryCreatedEvent;
|
||||
import org.infinispan.client.hotrod.event.ClientCacheEntryModifiedEvent;
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||
import org.infinispan.manager.DefaultCacheManager;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.infinispan.persistence.manager.PersistenceManager;
|
||||
import org.infinispan.persistence.remote.RemoteStore;
|
||||
import org.infinispan.persistence.remote.configuration.ExhaustedAction;
|
||||
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
|
||||
import org.junit.Ignore;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
|
||||
/**
|
||||
* Test concurrency for remoteStore (backed by HotRod RemoteCaches) against external JDG
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@Ignore
|
||||
public class ConcurrencyJDGRemoteCacheTest {
|
||||
|
||||
private static Map<String, EntryInfo> state = new HashMap<>();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Init map somehow
|
||||
for (int i=0 ; i<100 ; i++) {
|
||||
String key = "key-" + i;
|
||||
state.put(key, new EntryInfo());
|
||||
}
|
||||
|
||||
// Create caches, listeners and finally worker threads
|
||||
Worker worker1 = createWorker(1);
|
||||
Worker worker2 = createWorker(2);
|
||||
|
||||
// Start and join workers
|
||||
worker1.start();
|
||||
worker2.start();
|
||||
|
||||
worker1.join();
|
||||
worker2.join();
|
||||
|
||||
// Output
|
||||
for (Map.Entry<String, EntryInfo> entry : state.entrySet()) {
|
||||
System.out.println(entry.getKey() + ":::" + entry.getValue());
|
||||
worker1.cache.remove(entry.getKey());
|
||||
}
|
||||
|
||||
// Finish JVM
|
||||
worker1.cache.getCacheManager().stop();
|
||||
worker2.cache.getCacheManager().stop();
|
||||
}
|
||||
|
||||
private static Worker createWorker(int threadId) {
|
||||
EmbeddedCacheManager manager = createManager(threadId);
|
||||
Cache<String, Integer> cache = manager.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME);
|
||||
|
||||
System.out.println("Retrieved cache: " + threadId);
|
||||
|
||||
RemoteStore remoteStore = cache.getAdvancedCache().getComponentRegistry().getComponent(PersistenceManager.class).getStores(RemoteStore.class).iterator().next();
|
||||
HotRodListener listener = new HotRodListener();
|
||||
remoteStore.getRemoteCache().addClientListener(listener);
|
||||
|
||||
return new Worker(cache, threadId);
|
||||
}
|
||||
|
||||
private static EmbeddedCacheManager createManager(int threadId) {
|
||||
System.setProperty("java.net.preferIPv4Stack", "true");
|
||||
System.setProperty("jgroups.tcp.port", "53715");
|
||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||
|
||||
boolean clustered = false;
|
||||
boolean async = false;
|
||||
boolean allowDuplicateJMXDomains = true;
|
||||
|
||||
if (clustered) {
|
||||
gcb = gcb.clusteredDefault();
|
||||
gcb.transport().clusterName("test-clustering");
|
||||
}
|
||||
|
||||
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
|
||||
|
||||
EmbeddedCacheManager cacheManager = new DefaultCacheManager(gcb.build());
|
||||
|
||||
Configuration invalidationCacheConfiguration = getCacheBackedByRemoteStore(threadId);
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.WORK_CACHE_NAME, invalidationCacheConfiguration);
|
||||
return cacheManager;
|
||||
|
||||
}
|
||||
|
||||
private static Configuration getCacheBackedByRemoteStore(int threadId) {
|
||||
ConfigurationBuilder cacheConfigBuilder = new ConfigurationBuilder();
|
||||
|
||||
// int port = threadId==1 ? 11222 : 11322;
|
||||
int port = 11222;
|
||||
|
||||
return cacheConfigBuilder.persistence().addStore(RemoteStoreConfigurationBuilder.class)
|
||||
.fetchPersistentState(false)
|
||||
.ignoreModifications(false)
|
||||
.purgeOnStartup(false)
|
||||
.preload(false)
|
||||
.shared(true)
|
||||
.remoteCacheName(InfinispanConnectionProvider.WORK_CACHE_NAME)
|
||||
.rawValues(true)
|
||||
.forceReturnValues(false)
|
||||
.addServer()
|
||||
.host("localhost")
|
||||
.port(port)
|
||||
.connectionPool()
|
||||
.maxActive(20)
|
||||
.exhaustedAction(ExhaustedAction.CREATE_NEW)
|
||||
.async()
|
||||
. enabled(false).build();
|
||||
}
|
||||
|
||||
|
||||
@ClientListener
|
||||
public static class HotRodListener {
|
||||
|
||||
//private AtomicInteger listenerCount = new AtomicInteger(0);
|
||||
|
||||
@ClientCacheEntryCreated
|
||||
public void created(ClientCacheEntryCreatedEvent event) {
|
||||
String cacheKey = (String) event.getKey();
|
||||
state.get(cacheKey).successfulListenerWrites.incrementAndGet();
|
||||
}
|
||||
|
||||
@ClientCacheEntryModified
|
||||
public void updated(ClientCacheEntryModifiedEvent event) {
|
||||
String cacheKey = (String) event.getKey();
|
||||
state.get(cacheKey).successfulListenerWrites.incrementAndGet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class Worker extends Thread {
|
||||
|
||||
private final Cache<String, Integer> cache;
|
||||
|
||||
private final int myThreadId;
|
||||
|
||||
private Worker(Cache<String, Integer> cache, int myThreadId) {
|
||||
this.cache = cache;
|
||||
this.myThreadId = myThreadId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (Map.Entry<String, EntryInfo> entry : state.entrySet()) {
|
||||
String cacheKey = entry.getKey();
|
||||
EntryInfo wrapper = state.get(cacheKey);
|
||||
|
||||
int val = getClusterStartupTime(this.cache, cacheKey, wrapper);
|
||||
if (myThreadId == 1) {
|
||||
wrapper.th1.set(val);
|
||||
} else {
|
||||
wrapper.th2.set(val);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
System.out.println("Worker finished: " + myThreadId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static int getClusterStartupTime(Cache<String, Integer> cache, String cacheKey, EntryInfo wrapper) {
|
||||
int startupTime = new Random().nextInt(1024);
|
||||
|
||||
// Concurrency doesn't work correctly with this
|
||||
//Integer existingClusterStartTime = (Integer) cache.putIfAbsent(cacheKey, startupTime);
|
||||
|
||||
// Concurrency works fine with this
|
||||
RemoteCache remoteCache = cache.getAdvancedCache().getComponentRegistry().getComponent(PersistenceManager.class).getStores(RemoteStore.class).iterator().next().getRemoteCache();
|
||||
Integer existingClusterStartTime = (Integer) remoteCache.withFlags(Flag.FORCE_RETURN_VALUE).putIfAbsent(cacheKey, startupTime);
|
||||
|
||||
if (existingClusterStartTime == null) {
|
||||
wrapper.successfulInitializations.incrementAndGet();
|
||||
return startupTime;
|
||||
} else {
|
||||
return existingClusterStartTime;
|
||||
}
|
||||
}
|
||||
|
||||
private static class EntryInfo {
|
||||
AtomicInteger successfulInitializations = new AtomicInteger(0);
|
||||
AtomicInteger successfulListenerWrites = new AtomicInteger(0);
|
||||
AtomicInteger th1 = new AtomicInteger();
|
||||
AtomicInteger th2 = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Inits: %d, listeners: %d, th1: %d, th2: %d", successfulInitializations.get(), successfulListenerWrites.get(), th1.get(), th2.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -146,7 +146,8 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
query.setParameter("realm", realm.getId());
|
||||
List<String> clients = query.getResultList();
|
||||
for (String client : clients) {
|
||||
session.realms().removeClient(client, adapter);
|
||||
// No need to go through cache. Clients were already invalidated
|
||||
removeClient(client, adapter);
|
||||
}
|
||||
|
||||
for (ClientTemplateEntity a : new LinkedList<>(realm.getClientTemplates())) {
|
||||
|
@ -154,7 +155,8 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
}
|
||||
|
||||
for (RoleModel role : adapter.getRoles()) {
|
||||
session.realms().removeRole(adapter, role);
|
||||
// No need to go through cache. Roles were already invalidated
|
||||
removeRole(adapter, role);
|
||||
}
|
||||
|
||||
|
||||
|
@ -486,7 +488,8 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
session.users().preRemove(realm, client);
|
||||
|
||||
for (RoleModel role : client.getRoles()) {
|
||||
client.removeRole(role);
|
||||
// No need to go through cache. Roles were already invalidated
|
||||
removeRole(realm, role);
|
||||
}
|
||||
|
||||
ClientEntity clientEntity = ((ClientAdapter)client).getEntity();
|
||||
|
|
|
@ -124,17 +124,6 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
|||
UserEntity userEntity = em.find(UserEntity.class, user.getId());
|
||||
if (userEntity == null) return false;
|
||||
removeUser(userEntity);
|
||||
session.getKeycloakSessionFactory().publish(new UserModel.UserRemovedEvent() {
|
||||
@Override
|
||||
public UserModel getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSession getKeycloakSession() {
|
||||
return session;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -952,13 +952,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
return session.realms().getRoleById(id, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRoleById(String id) {
|
||||
RoleModel role = getRoleById(id);
|
||||
if (role == null) return false;
|
||||
return role.getContainer().removeRole(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PasswordPolicy getPasswordPolicy() {
|
||||
if (passwordPolicy == null) {
|
||||
|
@ -1932,12 +1925,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
return session.realms().createGroup(this, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTopLevelGroup(GroupModel subGroup) {
|
||||
session.realms().addTopLevelGroup(this, subGroup);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveGroup(GroupModel group, GroupModel toParent) {
|
||||
session.realms().moveGroup(this, group, toParent);
|
||||
|
|
|
@ -44,11 +44,6 @@ public class JpaUserSessionPersisterProviderFactory implements UserSessionPersis
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserManager;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.cache.CachedUserModel;
|
||||
|
@ -50,6 +51,8 @@ import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
|
|||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.UserConsentEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.UserModelDelegate;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -630,7 +633,19 @@ public class MongoUserProvider implements UserProvider, UserCredentialStore {
|
|||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, ComponentModel component) {
|
||||
if (!component.getProviderType().equals(UserStorageProvider.class.getName())) return;
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("federationLink").is(component.getId())
|
||||
.get();
|
||||
|
||||
List<MongoUserEntity> mongoUsers = getMongoStore().loadEntities(MongoUserEntity.class, query, invocationContext);
|
||||
UserManager userManager = new UserManager(session);
|
||||
|
||||
for (MongoUserEntity userEntity : mongoUsers) {
|
||||
// Doing this way to ensure UserRemovedEvent triggered with proper callbacks.
|
||||
UserAdapter user = new UserAdapter(session, realm, userEntity, invocationContext);
|
||||
userManager.removeUser(realm, user, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -661,16 +676,18 @@ public class MongoUserProvider implements UserProvider, UserCredentialStore {
|
|||
}
|
||||
|
||||
public MongoUserEntity getMongoUserEntity(UserModel user) {
|
||||
UserAdapter adapter = null;
|
||||
if (user instanceof CachedUserModel) {
|
||||
adapter = (UserAdapter)((CachedUserModel)user).getDelegateForUpdate();
|
||||
} else if (user instanceof UserAdapter ){
|
||||
adapter = (UserAdapter)user;
|
||||
if (user instanceof UserAdapter) {
|
||||
UserAdapter adapter = (UserAdapter)user;
|
||||
return adapter.getMongoEntity();
|
||||
} else if (user instanceof CachedUserModel) {
|
||||
UserModel delegate = ((CachedUserModel)user).getDelegateForUpdate();
|
||||
return getMongoUserEntity(delegate);
|
||||
} else if (user instanceof UserModelDelegate){
|
||||
UserModel delegate = ((UserModelDelegate) user).getDelegate();
|
||||
return getMongoUserEntity(delegate);
|
||||
} else {
|
||||
return getMongoStore().loadEntity(MongoUserEntity.class, user.getId(), invocationContext);
|
||||
|
||||
}
|
||||
return adapter.getMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -42,11 +42,6 @@ public class MongoUserSessionPersisterProviderFactory implements UserSessionPers
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.keycloak.common.enums.SslRequired;
|
|||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.jose.jwk.JWKBuilder;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
|
@ -62,10 +61,6 @@ import org.keycloak.models.mongo.keycloak.entities.UserFederationProviderEntity;
|
|||
import org.keycloak.models.utils.ComponentUtil;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -515,13 +510,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
return session.realms().removeRole(this, role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRoleById(String id) {
|
||||
RoleModel role = getRoleById(id);
|
||||
if (role == null) return false;
|
||||
return removeRole(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRoles() {
|
||||
DBObject query = new QueryBuilder()
|
||||
|
@ -554,12 +542,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
return session.realms().createGroup(this, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTopLevelGroup(GroupModel subGroup) {
|
||||
session.realms().addTopLevelGroup(this, subGroup);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveGroup(GroupModel group, GroupModel toParent) {
|
||||
session.realms().moveGroup(this, group, toParent);
|
||||
|
@ -2006,28 +1988,39 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
@Override
|
||||
public void removeComponent(ComponentModel component) {
|
||||
Iterator<ComponentEntity> it = realm.getComponentEntities().iterator();
|
||||
ComponentEntity found = null;
|
||||
while(it.hasNext()) {
|
||||
if (it.next().getId().equals(component.getId())) {
|
||||
session.users().preRemove(this, component);
|
||||
removeComponents(component.getId());
|
||||
it.remove();
|
||||
ComponentEntity next = it.next();
|
||||
if (next.getId().equals(component.getId())) {
|
||||
found = next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateRealm();
|
||||
|
||||
if (found != null) {
|
||||
session.users().preRemove(this, component);
|
||||
removeComponents(component.getId());
|
||||
realm.getComponentEntities().remove(found);
|
||||
updateRealm();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeComponents(String parentId) {
|
||||
Iterator<ComponentEntity> it = realm.getComponentEntities().iterator();
|
||||
Set<ComponentEntity> toRemove = new HashSet<>();
|
||||
while(it.hasNext()) {
|
||||
ComponentEntity next = it.next();
|
||||
if (next.getParentId().equals(parentId)) {
|
||||
session.users().preRemove(this, entityToModel(next));
|
||||
it.remove();
|
||||
toRemove.add(next);
|
||||
}
|
||||
}
|
||||
|
||||
for (ComponentEntity toRem : toRemove) {
|
||||
session.users().preRemove(this, entityToModel(toRem));
|
||||
realm.getComponentEntities().remove(toRem);
|
||||
}
|
||||
|
||||
updateRealm();
|
||||
|
||||
}
|
||||
|
|
|
@ -261,6 +261,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
if (user.getGroupIds() == null) return false;
|
||||
if (user.getGroupIds().contains(group.getId())) return true;
|
||||
Set<GroupModel> groups = getGroups();
|
||||
return RoleUtils.isMember(groups, group);
|
||||
|
|
13
pom.xml
13
pom.xml
|
@ -80,7 +80,7 @@
|
|||
|
||||
<!-- Authorization Drools Policy Provider -->
|
||||
<version.org.drools>6.4.0.Final</version.org.drools>
|
||||
<version.jboss-integration-platform>6.0.6.Final</version.jboss-integration-platform>
|
||||
<version.jboss-integration-platform>6.0.10.Final</version.jboss-integration-platform>
|
||||
|
||||
<!-- Others -->
|
||||
<apacheds.version>2.0.0-M21</apacheds.version>
|
||||
|
@ -98,7 +98,6 @@
|
|||
<servlet.api.30.version>1.0.2.Final</servlet.api.30.version>
|
||||
<twitter4j.version>4.0.4</twitter4j.version>
|
||||
<jna.version>4.1.0</jna.version>
|
||||
<jnr.version>0.14</jnr.version>
|
||||
|
||||
<!-- Test -->
|
||||
<greenmail.version>1.3.1b</greenmail.version>
|
||||
|
@ -634,6 +633,11 @@
|
|||
<artifactId>infinispan-core</artifactId>
|
||||
<version>${infinispan.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-cachestore-remote</artifactId>
|
||||
<version>${infinispan.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
|
@ -702,11 +706,6 @@
|
|||
<artifactId>jna</artifactId>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.jnr</groupId>
|
||||
<artifactId>jnr-unixsocket</artifactId>
|
||||
<version>${jnr.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-ldap-federation</artifactId>
|
||||
|
|
|
@ -79,8 +79,9 @@ public enum JBossSAMLURIConstants {
|
|||
"http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
|
||||
|
||||
SAML_HTTP_POST_BINDING("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"),
|
||||
SAML_HTTP_SOAP_BINDING("urn:oasis:names:tc:SAML:2.0:bindings:SOAP"),
|
||||
SAML_HTTP_REDIRECT_BINDING("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"),
|
||||
SAML_SOAP_BINDING("urn:oasis:names:tc:SAML:2.0:bindings:SOAP"),
|
||||
SAML_PAOS_BINDING("urn:oasis:names:tc:SAML:2.0:bindings:PAOS"),
|
||||
|
||||
SAML_11_NS("urn:oasis:names:tc:SAML:1.0:assertion"),
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue