Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
474043b688
171 changed files with 6294 additions and 1096 deletions
|
@ -41,7 +41,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -49,6 +49,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||
String deploymentName = deploymentUnit.getName();
|
||||
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return;
|
||||
}
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return;
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -236,7 +236,7 @@
|
|||
kc.createLogoutUrl = function(options) {
|
||||
var url = getRealmUrl()
|
||||
+ '/protocol/openid-connect/logout'
|
||||
+ '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options));
|
||||
+ '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false));
|
||||
|
||||
return url;
|
||||
}
|
||||
|
@ -842,14 +842,18 @@
|
|||
return createPromise().promise;
|
||||
},
|
||||
|
||||
redirectUri: function(options) {
|
||||
redirectUri: function(options, encodeHash) {
|
||||
if (arguments.length == 1) {
|
||||
encodeHash = true;
|
||||
}
|
||||
|
||||
if (options && options.redirectUri) {
|
||||
return options.redirectUri;
|
||||
} else if (kc.redirectUri) {
|
||||
return kc.redirectUri;
|
||||
} else {
|
||||
var redirectUri = location.href;
|
||||
if (location.hash) {
|
||||
if (location.hash && encodeHash) {
|
||||
redirectUri = redirectUri.substring(0, location.href.indexOf('#'));
|
||||
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
|
|
@ -49,6 +49,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||
String deploymentName = deploymentUnit.getName();
|
||||
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return;
|
||||
}
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return;
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
|
|
@ -49,6 +49,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||
String deploymentName = deploymentUnit.getName();
|
||||
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return;
|
||||
}
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return;
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -48,6 +48,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||
String deploymentName = deploymentUnit.getName();
|
||||
if (Configuration.INSTANCE.getSecureDeployment(deploymentName) == null) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return;
|
||||
}
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return;
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
|
|
@ -47,6 +47,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||
String deploymentName = deploymentUnit.getName();
|
||||
if (Configuration.INSTANCE.getSecureDeployment(deploymentName) == null) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return;
|
||||
}
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return;
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.keycloak.adapters.spi.KeycloakAccount;
|
|||
import org.keycloak.common.util.Encode;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -174,54 +173,11 @@ public class FilterSessionStore implements AdapterSessionStore {
|
|||
public ServletInputStream getInputStream() throws IOException {
|
||||
|
||||
if (needRequestRestore && body != null) {
|
||||
final ByteArrayInputStream is = new ByteArrayInputStream(body);
|
||||
return new ServletInputStream() {
|
||||
private int lastIndex = 0;
|
||||
private ReadListener readListener = null;
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return lastIndex == body.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
this.readListener = readListener;
|
||||
if (!isFinished()) {
|
||||
try {
|
||||
readListener.onDataAvailable();
|
||||
} catch (IOException e) {
|
||||
readListener.onError(e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
readListener.onAllDataRead();
|
||||
} catch (IOException e) {
|
||||
readListener.onError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int i = -1;
|
||||
if (!isFinished()) {
|
||||
i = body[lastIndex];
|
||||
lastIndex++;
|
||||
if (isFinished() && readListener != null) {
|
||||
try {
|
||||
readListener.onAllDataRead();
|
||||
} catch (IOException e) {
|
||||
readListener.onError(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return i;
|
||||
return is.read();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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.representations.idm;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class AbstractAuthenticationExecutionRepresentation implements Serializable {
|
||||
|
||||
private String authenticatorConfig;
|
||||
private String authenticator;
|
||||
private boolean authenticatorFlow;
|
||||
private String requirement;
|
||||
private int priority;
|
||||
|
||||
public String getAuthenticatorConfig() {
|
||||
return authenticatorConfig;
|
||||
}
|
||||
|
||||
public void setAuthenticatorConfig(String authenticatorConfig) {
|
||||
this.authenticatorConfig = authenticatorConfig;
|
||||
}
|
||||
|
||||
public String getAuthenticator() {
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
public void setAuthenticator(String authenticator) {
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
public String getRequirement() {
|
||||
return requirement;
|
||||
}
|
||||
|
||||
public void setRequirement(String requirement) {
|
||||
this.requirement = requirement;
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the referenced authenticator a flow?
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isAutheticatorFlow() {
|
||||
return authenticatorFlow;
|
||||
}
|
||||
|
||||
public void setAutheticatorFlow(boolean autheticatorFlow) {
|
||||
this.authenticatorFlow = autheticatorFlow;
|
||||
}
|
||||
}
|
|
@ -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.representations.idm;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AuthenticationExecutionExportRepresentation extends AbstractAuthenticationExecutionRepresentation {
|
||||
|
||||
private String flowAlias;
|
||||
private boolean userSetupAllowed;
|
||||
|
||||
|
||||
public boolean isUserSetupAllowed() {
|
||||
return userSetupAllowed;
|
||||
}
|
||||
|
||||
public void setUserSetupAllowed(boolean userSetupAllowed) {
|
||||
this.userSetupAllowed = userSetupAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this execution is a flow, this is the flowId pointing to an AuthenticationFlowModel
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getFlowAlias() {
|
||||
return flowAlias;
|
||||
}
|
||||
|
||||
public void setFlowAlias(String flowId) {
|
||||
this.flowAlias = flowId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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.representations.idm;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AuthenticationExecutionInfoRepresentation implements Serializable {
|
||||
|
||||
protected String id;
|
||||
protected String requirement;
|
||||
protected String displayName;
|
||||
protected List<String> requirementChoices;
|
||||
protected Boolean configurable;
|
||||
protected Boolean authenticationFlow;
|
||||
protected String providerId;
|
||||
protected String authenticationConfig;
|
||||
protected String flowId;
|
||||
protected int level;
|
||||
protected int index;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String execution) {
|
||||
this.id = execution;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getRequirement() {
|
||||
return requirement;
|
||||
}
|
||||
|
||||
public void setRequirement(String requirement) {
|
||||
this.requirement = requirement;
|
||||
}
|
||||
|
||||
public List<String> getRequirementChoices() {
|
||||
return requirementChoices;
|
||||
}
|
||||
|
||||
public void setRequirementChoices(List<String> requirementChoices) {
|
||||
this.requirementChoices = requirementChoices;
|
||||
}
|
||||
|
||||
public Boolean getConfigurable() {
|
||||
return configurable;
|
||||
}
|
||||
|
||||
public void setConfigurable(Boolean configurable) {
|
||||
this.configurable = configurable;
|
||||
}
|
||||
|
||||
public String getProviderId() {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
public void setProviderId(String providerId) {
|
||||
this.providerId = providerId;
|
||||
}
|
||||
|
||||
public String getAuthenticationConfig() {
|
||||
return authenticationConfig;
|
||||
}
|
||||
|
||||
public void setAuthenticationConfig(String authenticationConfig) {
|
||||
this.authenticationConfig = authenticationConfig;
|
||||
}
|
||||
|
||||
public Boolean getAuthenticationFlow() {
|
||||
return authenticationFlow;
|
||||
}
|
||||
|
||||
public void setAuthenticationFlow(Boolean authenticationFlow) {
|
||||
this.authenticationFlow = authenticationFlow;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setLevel(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public String getFlowId() {
|
||||
return flowId;
|
||||
}
|
||||
|
||||
public void setFlowId(String flowId) {
|
||||
this.flowId = flowId;
|
||||
}
|
||||
}
|
85
core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionRepresentation.java
Executable file → Normal file
85
core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionRepresentation.java
Executable file → Normal file
|
@ -17,87 +17,36 @@
|
|||
|
||||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class AuthenticationExecutionRepresentation implements Serializable {
|
||||
public class AuthenticationExecutionRepresentation extends AbstractAuthenticationExecutionRepresentation {
|
||||
|
||||
private String authenticatorConfig;
|
||||
private String authenticator;
|
||||
private String flowAlias;
|
||||
private boolean autheticatorFlow;
|
||||
private String requirement;
|
||||
private boolean userSetupAllowed;
|
||||
private int priority;
|
||||
private String id;
|
||||
private String flowId;
|
||||
private String parentFlow;
|
||||
|
||||
public String getAuthenticatorConfig() {
|
||||
return authenticatorConfig;
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setAuthenticatorConfig(String authenticatorConfig) {
|
||||
this.authenticatorConfig = authenticatorConfig;
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAuthenticator() {
|
||||
return authenticator;
|
||||
public String getFlowId() {
|
||||
return flowId;
|
||||
}
|
||||
|
||||
public void setAuthenticator(String authenticator) {
|
||||
this.authenticator = authenticator;
|
||||
public void setFlowId(String flowId) {
|
||||
this.flowId = flowId;
|
||||
}
|
||||
|
||||
public String getRequirement() {
|
||||
return requirement;
|
||||
public String getParentFlow() {
|
||||
return parentFlow;
|
||||
}
|
||||
|
||||
public void setRequirement(String requirement) {
|
||||
this.requirement = requirement;
|
||||
public void setParentFlow(String parentFlow) {
|
||||
this.parentFlow = parentFlow;
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public boolean isUserSetupAllowed() {
|
||||
return userSetupAllowed;
|
||||
}
|
||||
|
||||
public void setUserSetupAllowed(boolean userSetupAllowed) {
|
||||
this.userSetupAllowed = userSetupAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this execution is a flow, this is the flowId pointing to an AuthenticationFlowModel
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getFlowAlias() {
|
||||
return flowAlias;
|
||||
}
|
||||
|
||||
public void setFlowAlias(String flowId) {
|
||||
this.flowAlias = flowId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the referenced authenticator a flow?
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isAutheticatorFlow() {
|
||||
return autheticatorFlow;
|
||||
}
|
||||
|
||||
public void setAutheticatorFlow(boolean autheticatorFlow) {
|
||||
this.autheticatorFlow = autheticatorFlow;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,12 +26,21 @@ import java.util.List;
|
|||
*/
|
||||
public class AuthenticationFlowRepresentation implements Serializable {
|
||||
|
||||
private String id;
|
||||
private String alias;
|
||||
private String description;
|
||||
private String providerId;
|
||||
private boolean topLevel;
|
||||
private boolean builtIn;
|
||||
protected List<AuthenticationExecutionRepresentation> authenticationExecutions;
|
||||
protected List<AuthenticationExecutionExportRepresentation> authenticationExecutions;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
|
@ -73,11 +82,11 @@ public class AuthenticationFlowRepresentation implements Serializable {
|
|||
this.builtIn = builtIn;
|
||||
}
|
||||
|
||||
public List<AuthenticationExecutionRepresentation> getAuthenticationExecutions() {
|
||||
public List<AuthenticationExecutionExportRepresentation> getAuthenticationExecutions() {
|
||||
return authenticationExecutions;
|
||||
}
|
||||
|
||||
public void setAuthenticationExecutions(List<AuthenticationExecutionRepresentation> authenticationExecutions) {
|
||||
public void setAuthenticationExecutions(List<AuthenticationExecutionExportRepresentation> authenticationExecutions) {
|
||||
this.authenticationExecutions = authenticationExecutions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class AuthenticatorConfigInfoRepresentation {
|
||||
|
||||
protected String name;
|
||||
protected String providerId;
|
||||
protected String helpText;
|
||||
|
||||
protected List<ConfigPropertyRepresentation> properties;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getHelpText() {
|
||||
return helpText;
|
||||
}
|
||||
|
||||
public String getProviderId() {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
public void setProviderId(String providerId) {
|
||||
this.providerId = providerId;
|
||||
}
|
||||
|
||||
public void setHelpText(String helpText) {
|
||||
this.helpText = helpText;
|
||||
}
|
||||
|
||||
public List<ConfigPropertyRepresentation> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setProperties(List<ConfigPropertyRepresentation> properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
}
|
||||
|
|
@ -89,6 +89,9 @@
|
|||
<local-cache name="sessions"/>
|
||||
<local-cache name="offlineSessions"/>
|
||||
<local-cache name="loginFailures"/>
|
||||
<local-cache name="realmVersions">
|
||||
<transaction mode="BATCH" locking="PESSIMISTIC"/>
|
||||
</local-cache>
|
||||
</cache-container>
|
||||
<xsl:apply-templates select="node()|@*"/>
|
||||
</xsl:copy>
|
||||
|
|
16
distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
Normal file → Executable file
16
distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
Normal file → Executable file
|
@ -28,12 +28,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
"realmCache": {
|
||||
"infinispan" : {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
|
||||
"userSessionPersister": {
|
||||
"provider": "jpa"
|
||||
},
|
||||
|
@ -67,8 +61,16 @@
|
|||
}
|
||||
},
|
||||
|
||||
"realmCache": {
|
||||
"provider": "infinispan-locking",
|
||||
"infinispan-locking" : {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
|
||||
"connectionsInfinispan": {
|
||||
"default" : {
|
||||
"provider": "locking",
|
||||
"locking": {
|
||||
"cacheContainer" : "java:comp/env/infinispan/Keycloak"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,12 @@
|
|||
(via the subsystem, or auth-method in web.xml
|
||||
</para>
|
||||
</simplesect>
|
||||
<simplesect>
|
||||
<title>Client Registration service endpoints moved</title>
|
||||
<para>
|
||||
The Client Registration service endpoints have been moved from <literal>{realm}/clients</literal> to <literal>{realm}/clients-registrations</literal>.
|
||||
</para>
|
||||
</simplesect>
|
||||
<simplesect>
|
||||
<title>Deprecated OpenID Connect endpoints</title>
|
||||
<para>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<para>
|
||||
The Client Registration Service provides built-in support for Keycloak Client Representations, OpenID Connect
|
||||
Client Meta Data and SAML Entity Descriptors. It's also possible to plugin custom client registration providers
|
||||
if required. The Client Registration Service endpoint is <literal><KEYCLOAK URL>/realms/<realm>/clients/<provider></literal>.
|
||||
if required. The Client Registration Service endpoint is <literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/<provider></literal>.
|
||||
</para>
|
||||
<para>
|
||||
The built-in supported <literal>providers</literal> are:
|
||||
|
@ -123,23 +123,23 @@ Authorization: bearer eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJmMjJmNzQyYy04ZjNlLTQ2M....
|
|||
</para>
|
||||
<para>
|
||||
To create a client create a Client Representation (JSON) then do a HTTP POST to:
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients/default</literal>. It will return a Client Representation
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/default</literal>. It will return a Client Representation
|
||||
that also includes the registration access token. You should save the registration access token somewhere
|
||||
if you want to retrieve the config, update or delete the client later.
|
||||
</para>
|
||||
<para>
|
||||
To retrieve the Client Representation then do a HTTP GET to:
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients/default/<client id></literal>. It will also
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/default/<client id></literal>. It will also
|
||||
return a new registration access token.
|
||||
</para>
|
||||
<para>
|
||||
To update the Client Representation then do a HTTP PUT to with the updated Client Representation to:
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients/default/<client id></literal>. It will also
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/default/<client id></literal>. It will also
|
||||
return a new registration access token.
|
||||
</para>
|
||||
<para>
|
||||
To delete the Client Representation then do a HTTP DELETE to:
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients/default/<client id></literal>
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/default/<client id></literal>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
|
@ -155,7 +155,7 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
|
|||
</para>
|
||||
<para>
|
||||
To retrieve the Adapter Configuration then do a HTTP GET to:
|
||||
<literal><KEYCLOAK URL>//realms/<realm>/clients/installation/<client id></literal>
|
||||
<literal><KEYCLOAK URL>//realms/<realm>/clients-registrations/installation/<client id></literal>
|
||||
</para>
|
||||
<para>
|
||||
No authentication is required for public clients. This means that for the JavaScript adapter you can
|
||||
|
@ -172,7 +172,7 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
|
|||
</para>
|
||||
<para>
|
||||
The endpoint to use these specifications to register clients in Keycloak is:
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients/oidc[/<client id>]</literal>.
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/oidc[/<client id>]</literal>.
|
||||
</para>
|
||||
<para>
|
||||
This endpoints can also be found in the OpenID Connect Discovery endpoint for the realm:
|
||||
|
@ -190,7 +190,7 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
|
|||
</para>
|
||||
<para>
|
||||
To create a client do a HTTP POST with the SAML Entity Descriptor to:
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients/saml2-entity-descriptor</literal>.
|
||||
<literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/saml2-entity-descriptor</literal>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -396,7 +396,7 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
|
|||
<term>connection-pool-size</term>
|
||||
<listitem>
|
||||
<para>
|
||||
How many connections can be in the pool.
|
||||
How many connections can be in the pool (128 by default).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
@ -404,7 +404,24 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
|
|||
<term>max-pooled-per-route</term>
|
||||
<listitem>
|
||||
<para>
|
||||
How many connections can be pooled per host.
|
||||
How many connections can be pooled per host (64 by default).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>connection-ttl-millis</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Maximum connection time to live in milliseconds. Not set by default.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>max-connection-idle-time-millis</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Maximum time the connection might stay idle in the connection pool (900 seconds by default). Will start background cleaner thread of Apache HTTP client.
|
||||
Set to -1 to disable this checking and the background thread.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -202,7 +202,28 @@ An Angular JS example using Keycloak to secure it.
|
|||
If you are already logged in, you will not be asked for a username and password, but you will be redirected to
|
||||
an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
|
||||
|
||||
Step 9: Pure HTML5/Javascript Example
|
||||
Step 10: Angular2 JS Example
|
||||
----------------------------------
|
||||
An Angular2 JS example using Keycloak to secure it. Angular2 is in beta version yet.
|
||||
|
||||
To install angular2
|
||||
```
|
||||
$ cd keycloak/examples/demo-template/angular2-product-app/src/main/webapp/
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Transpile TypeScript to JavaScript before running the application.
|
||||
```
|
||||
$ npm run tsc
|
||||
```
|
||||
|
||||
[http://localhost:8080/angular2-product](http://localhost:8080/angular2-product)
|
||||
|
||||
If you are already logged in, you will not be asked for a username and password, but you will be redirected to
|
||||
an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
|
||||
|
||||
|
||||
Step 11: Pure HTML5/Javascript Example
|
||||
----------------------------------
|
||||
An pure HTML5/Javascript example using Keycloak to secure it.
|
||||
|
||||
|
@ -211,7 +232,7 @@ An pure HTML5/Javascript example using Keycloak to secure it.
|
|||
If you are already logged in, you will not be asked for a username and password, but you will be redirected to
|
||||
an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
|
||||
|
||||
Step 10: Service Account Example
|
||||
Step 12: Service Account Example
|
||||
================================
|
||||
An example for retrieve service account dedicated to the Client Application itself (not to any user).
|
||||
|
||||
|
@ -222,7 +243,7 @@ Client authentication is done with OAuth2 Client Credentials Grant in out-of-bou
|
|||
The example also shows different methods of client authentication. There is ProductSAClientSecretServlet using traditional authentication with clientId and client_secret,
|
||||
but there is also ProductSAClientSignedJWTServlet using client authentication with JWT signed by client private key.
|
||||
|
||||
Step 11: Offline Access Example
|
||||
Step 13: Offline Access Example
|
||||
===============================
|
||||
An example for retrieve offline token, which is then saved to the database and can be used by application anytime later. Offline token
|
||||
is valid even if user is already logged out from SSO. Server restart also won't invalidate offline token. Offline token can be revoked by the user in
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
53
examples/demo-template/angular2-product-app/pom.xml
Normal file
53
examples/demo-template/angular2-product-app/pom.xml
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-examples-demo-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.9.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.keycloak.example.demo</groupId>
|
||||
<artifactId>angular2-product-example</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<name>Angular2 Product Portal JS</name>
|
||||
<description/>
|
||||
|
||||
<build>
|
||||
<finalName>angular2-product</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jboss.as.plugins</groupId>
|
||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.wildfly.plugins</groupId>
|
||||
<artifactId>wildfly-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,78 @@
|
|||
import {Http, Headers,
|
||||
RequestOptions, Response} from 'angular2/http';
|
||||
import {Component} from 'angular2/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {KeycloakService} from './keycloak';
|
||||
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template:
|
||||
`
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
<div id="content">
|
||||
<h1>Angular2 Product (Beta)</h1>
|
||||
<h2><span>Products</span></h2>
|
||||
|
||||
<button type="button" (click)="logout()">Sign Out</button>
|
||||
<button type="button" (click)="reloadData()">Reload</button>
|
||||
<table class="table" [hidden]="!products.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product Listing</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="#p of products">
|
||||
<td>{{p}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
constructor(private _kc:KeycloakService, private http:Http){ }
|
||||
|
||||
products : string[] = [];
|
||||
|
||||
logout(){
|
||||
this._kc.logout();
|
||||
}
|
||||
|
||||
reloadData() {
|
||||
//angular dont have http interceptor yet
|
||||
|
||||
this._kc.getToken().then(
|
||||
token=>{
|
||||
let headers = new Headers({
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer ' + token
|
||||
});
|
||||
|
||||
let options = new RequestOptions({ headers: headers });
|
||||
|
||||
this.http.get('/database/products', options)
|
||||
.map(res => <string[]> res.json())
|
||||
.subscribe(
|
||||
prods => this.products = prods,
|
||||
error => console.log(error));
|
||||
|
||||
},
|
||||
error=>{
|
||||
console.log(error);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private handleError (error: Response) {
|
||||
console.error(error);
|
||||
return Observable.throw(error.json().error || 'Server error');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import {Injectable} from 'angular2/core';
|
||||
|
||||
|
||||
declare var Keycloak: any;
|
||||
|
||||
@Injectable()
|
||||
export class KeycloakService {
|
||||
|
||||
static auth : any = {};
|
||||
|
||||
static init() : Promise<any>{
|
||||
let keycloakAuth : any = new Keycloak('keycloak.json');
|
||||
KeycloakService.auth.loggedIn = false;
|
||||
|
||||
return new Promise((resolve,reject)=>{
|
||||
keycloakAuth.init({ onLoad: 'login-required' })
|
||||
.success( () => {
|
||||
KeycloakService.auth.loggedIn = true;
|
||||
KeycloakService.auth.authz = keycloakAuth;
|
||||
KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl + "/realms/demo/tokens/logout?redirect_uri=/angular2-product/index.html";
|
||||
resolve(null);
|
||||
})
|
||||
.error(()=> {
|
||||
reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logout(){
|
||||
console.log('*** LOGOUT');
|
||||
KeycloakService.auth.loggedIn = false;
|
||||
KeycloakService.auth.authz = null;
|
||||
|
||||
window.location.href = KeycloakService.auth.logoutUrl;
|
||||
}
|
||||
|
||||
getToken(): Promise<string>{
|
||||
return new Promise<string>((resolve,reject)=>{
|
||||
if (KeycloakService.auth.authz.token) {
|
||||
KeycloakService.auth.authz.updateToken(5).success(function() {
|
||||
resolve(<string>KeycloakService.auth.authz.token);
|
||||
})
|
||||
.error(function() {
|
||||
reject('Failed to refresh token');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import 'rxjs/Rx';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {HTTP_BINDINGS} from 'angular2/http';
|
||||
import {KeycloakService} from './keycloak';
|
||||
import {AppComponent} from './app';
|
||||
|
||||
KeycloakService.init().then(
|
||||
o=>{
|
||||
bootstrap(AppComponent,[HTTP_BINDINGS, KeycloakService]);
|
||||
},
|
||||
x=>{
|
||||
window.location.reload();
|
||||
}
|
||||
);
|
|
@ -0,0 +1,49 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular 2 QuickStart</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<!-- 3. Display the application -->
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
|
||||
|
||||
|
||||
<!-- 1. Load libraries -->
|
||||
<!-- IE required polyfills, in this exact order -->
|
||||
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
|
||||
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<script src="node_modules/angular2/bundles/http.js"></script>
|
||||
|
||||
|
||||
<script src="/auth/js/keycloak.js"></script>
|
||||
|
||||
<!-- 2. Configure SystemJS -->
|
||||
<script>
|
||||
System.config({
|
||||
packages: {
|
||||
app: {
|
||||
format: 'register',
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
}
|
||||
});
|
||||
System.import('app/main')
|
||||
.then(null, console.error.bind(console));
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"realm": "demo",
|
||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url": "/auth",
|
||||
"ssl-required": "external",
|
||||
"resource": "angular2-product",
|
||||
"public-client": true
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "angular2-product-app",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"tsc": "tsc",
|
||||
"tsc:w": "tsc -w",
|
||||
"lite": "lite-server",
|
||||
"start": "concurrent \"npm run tsc:w\" \"npm run lite\" "
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"angular2": "2.0.0-beta.3",
|
||||
"systemjs": "0.19.6",
|
||||
"es6-promise": "^3.0.2",
|
||||
"es6-shim": "^0.33.3",
|
||||
"reflect-metadata": "0.1.2",
|
||||
"rxjs": "5.0.0-beta.0",
|
||||
"zone.js": "0.5.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^1.0.0",
|
||||
"lite-server": "^2.0.1",
|
||||
"typescript": "^1.7.5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "system",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"removeComments": false,
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -42,7 +42,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -147,6 +147,15 @@
|
|||
"redirectUris": [
|
||||
"/angular-product/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"clientId": "angular2-product",
|
||||
"enabled": true,
|
||||
"publicClient": true,
|
||||
"baseUrl": "/angular2-product/index.html",
|
||||
"redirectUris": [
|
||||
"/angular2-product/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"clientId": "customer-portal-cli",
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
2
examples/demo-template/third-party/pom.xml
vendored
2
examples/demo-template/third-party/pom.xml
vendored
|
@ -34,7 +34,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* 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.admin.client.resource;
|
||||
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
|
||||
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public interface AuthenticationManagementResource {
|
||||
|
||||
@GET
|
||||
@Path("/form-providers")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
List<Map<String, Object>> getFormProviders();
|
||||
|
||||
@Path("/authenticator-providers")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
List<Map<String, Object>> getAuthenticatorProviders();
|
||||
|
||||
@Path("/client-authenticator-providers")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
List<Map<String, Object>> getClientAuthenticatorProviders();
|
||||
|
||||
@Path("/form-action-providers")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
List<Map<String, Object>> getFormActionProviders();
|
||||
|
||||
@Path("/flows")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
List<AuthenticationFlowRepresentation> getFlows();
|
||||
|
||||
@Path("/flows")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
Response createFlow(AuthenticationFlowRepresentation model);
|
||||
|
||||
@Path("/flows/{id}")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
AuthenticationFlowRepresentation getFlow(@PathParam("id") String id);
|
||||
|
||||
@Path("/flows/{id}")
|
||||
@DELETE
|
||||
void deleteFlow(@PathParam("id") String id);
|
||||
|
||||
@Path("/flows/{flowAlias}/copy")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
Response copy(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
|
||||
|
||||
@Path("/flows/{flowAlias}/executions/flow")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
void addExecutionFlow(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
|
||||
|
||||
@Path("/flows/{flowAlias}/executions/execution")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
void addExecution(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
|
||||
|
||||
@Path("/flows/{flowAlias}/executions")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Response getExecutions(@PathParam("flowAlias") String flowAlias);
|
||||
|
||||
@Path("/flows/{flowAlias}/executions")
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionInfoRepresentation rep);
|
||||
|
||||
@Path("/executions")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
Response addExecution(AuthenticationExecutionRepresentation model);
|
||||
|
||||
@Path("/executions/{executionId}/raise-priority")
|
||||
@POST
|
||||
void raisePriority(@PathParam("executionId") String execution);
|
||||
|
||||
@Path("/executions/{executionId}/lower-priority")
|
||||
@POST
|
||||
void lowerPriority(@PathParam("executionId") String execution);
|
||||
|
||||
@Path("/executions/{executionId}")
|
||||
@DELETE
|
||||
void removeExecution(@PathParam("executionId") String execution);
|
||||
|
||||
@Path("/executions/{executionId}/config")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigRepresentation config);
|
||||
|
||||
@Path("/executions/{executionId}/config/{id}")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id);
|
||||
|
||||
@Path("unregistered-required-actions")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
List<Map<String, String>> getUnregisteredRequiredActions();
|
||||
|
||||
@Path("register-required-action")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
void registereRequiredAction(Map<String, String> data);
|
||||
|
||||
@Path("required-actions")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
List<RequiredActionProviderRepresentation> getRequiredActions();
|
||||
|
||||
@Path("required-actions/{alias}")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
RequiredActionProviderRepresentation getRequiredAction(@PathParam("alias") String alias);
|
||||
|
||||
@Path("required-actions/{alias}")
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
void updateRequiredAction(@PathParam("alias") String alias, RequiredActionProviderRepresentation rep);
|
||||
|
||||
@Path("required-actions/{alias}")
|
||||
@DELETE
|
||||
void updateRequiredAction(@PathParam("alias") String alias);
|
||||
|
||||
@Path("config-description/{providerId}")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
AuthenticatorConfigInfoRepresentation getAuthenticatorConfigDescription(@PathParam("providerId") String providerId);
|
||||
|
||||
@Path("per-client-config-description")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Map<String, List<ConfigPropertyRepresentation>> getPerClientConfigDescription();
|
||||
|
||||
@Path("config")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
Response createAuthenticatorConfig(AuthenticatorConfigRepresentation config);
|
||||
|
||||
@Path("config/{id}")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("id") String id);
|
||||
|
||||
@Path("config/{id}")
|
||||
@DELETE
|
||||
void removeAuthenticatorConfig(@PathParam("id") String id);
|
||||
|
||||
@Path("config/{id}")
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigRepresentation config);
|
||||
}
|
|
@ -142,4 +142,8 @@ public interface RealmResource {
|
|||
@Path("clients-initial-access")
|
||||
ClientInitialAccessResource clientInitialAccess();
|
||||
|
||||
@Path("authentication")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
AuthenticationManagementResource flows();
|
||||
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ public class ClientRegistration {
|
|||
}
|
||||
|
||||
public ClientRegistrationBuilder url(String authUrl, String realm) {
|
||||
url = HttpUtil.getUrl(authUrl, "realms", realm, "clients");
|
||||
url = HttpUtil.getUrl(authUrl, "realms", realm, "clients-registrations");
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,11 +37,11 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
|
||||
protected static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.class);
|
||||
|
||||
private Config.Scope config;
|
||||
protected Config.Scope config;
|
||||
|
||||
private EmbeddedCacheManager cacheManager;
|
||||
protected EmbeddedCacheManager cacheManager;
|
||||
|
||||
private boolean containerManaged;
|
||||
protected boolean containerManaged;
|
||||
|
||||
@Override
|
||||
public InfinispanConnectionProvider create(KeycloakSession session) {
|
||||
|
@ -73,7 +73,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
|
||||
}
|
||||
|
||||
private void lazyInit() {
|
||||
protected void lazyInit() {
|
||||
if (cacheManager == null) {
|
||||
synchronized (this) {
|
||||
if (cacheManager == null) {
|
||||
|
@ -88,7 +88,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
}
|
||||
}
|
||||
|
||||
private void initContainerManaged(String cacheContainerLookup) {
|
||||
protected void initContainerManaged(String cacheContainerLookup) {
|
||||
try {
|
||||
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
||||
containerManaged = true;
|
||||
|
@ -99,7 +99,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
}
|
||||
}
|
||||
|
||||
private void initEmbedded() {
|
||||
protected void initEmbedded() {
|
||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||
|
||||
boolean clustered = config.getBoolean("clustered", false);
|
||||
|
|
|
@ -546,7 +546,9 @@ public class ClientAdapter implements ClientModel {
|
|||
public RoleModel getRole(String name) {
|
||||
if (updated != null) return updated.getRole(name);
|
||||
String id = cached.getRoles().get(name);
|
||||
if (id == null) return null;
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
return cacheSession.getRoleById(id, cachedRealm);
|
||||
}
|
||||
|
||||
|
|
10
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java
vendored
Executable file
10
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java
vendored
Executable file
|
@ -0,0 +1,10 @@
|
|||
package org.keycloak.models.cache.infinispan.counter;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface Revisioned {
|
||||
Long getRevision();
|
||||
void setRevision(Long revision);
|
||||
}
|
401
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
vendored
Executable file
401
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
vendored
Executable file
|
@ -0,0 +1,401 @@
|
|||
/*
|
||||
* 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.counter;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientTemplateModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.cache.CacheRealmProvider;
|
||||
import org.keycloak.models.cache.RealmCache;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedClientRole;
|
||||
import org.keycloak.models.cache.entities.CachedClientTemplate;
|
||||
import org.keycloak.models.cache.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.entities.CachedRealmRole;
|
||||
import org.keycloak.models.cache.entities.CachedRole;
|
||||
import org.keycloak.models.cache.infinispan.ClientAdapter;
|
||||
import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
|
||||
import org.keycloak.models.cache.infinispan.GroupAdapter;
|
||||
import org.keycloak.models.cache.infinispan.RealmAdapter;
|
||||
import org.keycloak.models.cache.infinispan.RoleAdapter;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RevisionedCacheRealmProvider implements CacheRealmProvider {
|
||||
protected static final Logger logger = Logger.getLogger(RevisionedCacheRealmProvider.class);
|
||||
protected RevisionedRealmCache cache;
|
||||
protected KeycloakSession session;
|
||||
protected RealmProvider delegate;
|
||||
protected boolean transactionActive;
|
||||
protected boolean setRollbackOnly;
|
||||
|
||||
protected Set<String> realmInvalidations = new HashSet<>();
|
||||
protected Set<String> appInvalidations = new HashSet<>();
|
||||
protected Set<String> clientTemplateInvalidations = new HashSet<>();
|
||||
protected Set<String> roleInvalidations = new HashSet<>();
|
||||
protected Set<String> groupInvalidations = new HashSet<>();
|
||||
protected Map<String, RealmModel> managedRealms = new HashMap<>();
|
||||
protected Map<String, ClientModel> managedApplications = new HashMap<>();
|
||||
protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
|
||||
protected Map<String, RoleModel> managedRoles = new HashMap<>();
|
||||
protected Map<String, GroupModel> managedGroups = new HashMap<>();
|
||||
|
||||
protected boolean clearAll;
|
||||
|
||||
public RevisionedCacheRealmProvider(RevisionedRealmCache cache, KeycloakSession session) {
|
||||
this.cache = cache;
|
||||
this.session = session;
|
||||
|
||||
session.getTransaction().enlistAfterCompletion(getTransaction());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MigrationModel getMigrationModel() {
|
||||
return getDelegate().getMigrationModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmProvider getDelegate() {
|
||||
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
|
||||
if (delegate != null) return delegate;
|
||||
delegate = session.getProvider(RealmProvider.class);
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRealmInvalidation(String id) {
|
||||
realmInvalidations.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerApplicationInvalidation(String id) {
|
||||
appInvalidations.add(id);
|
||||
}
|
||||
@Override
|
||||
public void registerClientTemplateInvalidation(String id) {
|
||||
clientTemplateInvalidations.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRoleInvalidation(String id) {
|
||||
roleInvalidations.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerGroupInvalidation(String id) {
|
||||
groupInvalidations.add(id);
|
||||
|
||||
}
|
||||
|
||||
protected void runInvalidations() {
|
||||
for (String id : realmInvalidations) {
|
||||
cache.invalidateCachedRealmById(id);
|
||||
}
|
||||
for (String id : roleInvalidations) {
|
||||
cache.invalidateRoleById(id);
|
||||
}
|
||||
for (String id : groupInvalidations) {
|
||||
cache.invalidateGroupById(id);
|
||||
}
|
||||
for (String id : appInvalidations) {
|
||||
cache.invalidateCachedApplicationById(id);
|
||||
}
|
||||
for (String id : clientTemplateInvalidations) {
|
||||
cache.invalidateCachedClientTemplateById(id);
|
||||
}
|
||||
}
|
||||
|
||||
private KeycloakTransaction getTransaction() {
|
||||
return new KeycloakTransaction() {
|
||||
@Override
|
||||
public void begin() {
|
||||
transactionActive = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
if (delegate == null) return;
|
||||
if (clearAll) {
|
||||
cache.clear();
|
||||
}
|
||||
runInvalidations();
|
||||
transactionActive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
setRollbackOnly = true;
|
||||
runInvalidations();
|
||||
transactionActive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
setRollbackOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRollbackOnly() {
|
||||
return setRollbackOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return transactionActive;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel createRealm(String name) {
|
||||
RealmModel realm = getDelegate().createRealm(name);
|
||||
registerRealmInvalidation(realm.getId());
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel createRealm(String id, String name) {
|
||||
RealmModel realm = getDelegate().createRealm(id, name);
|
||||
registerRealmInvalidation(realm.getId());
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm(String id) {
|
||||
CachedRealm cached = cache.getCachedRealm(id);
|
||||
if (cached != null) {
|
||||
logger.tracev("by id cache hit: {0}", cached.getName());
|
||||
}
|
||||
if (cached == null) {
|
||||
Long loaded = UpdateCounter.current();
|
||||
RealmModel model = getDelegate().getRealm(id);
|
||||
if (model == null) return null;
|
||||
if (realmInvalidations.contains(id)) return model;
|
||||
cached = new RevisionedCachedRealm(loaded, cache, this, model);
|
||||
logger.tracev("try caching realm: {0} {1}", cached.getName(), loaded);
|
||||
cache.addCachedRealm(cached);
|
||||
} else if (realmInvalidations.contains(id)) {
|
||||
return getDelegate().getRealm(id);
|
||||
} else if (managedRealms.containsKey(id)) {
|
||||
return managedRealms.get(id);
|
||||
}
|
||||
RealmAdapter adapter = new RealmAdapter(cached, this);
|
||||
managedRealms.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealmByName(String name) {
|
||||
CachedRealm cached = cache.getCachedRealmByName(name);
|
||||
if (cached != null) {
|
||||
logger.tracev("by name cache hit: {0}", cached.getName());
|
||||
}
|
||||
if (cached == null) {
|
||||
Long loaded = UpdateCounter.current();
|
||||
RealmModel model = getDelegate().getRealmByName(name);
|
||||
if (model == null) return null;
|
||||
if (realmInvalidations.contains(model.getId())) return model;
|
||||
cached = new RevisionedCachedRealm(loaded, cache, this, model);
|
||||
logger.tracev("try caching realm: {0}", cached.getName());
|
||||
cache.addCachedRealm(cached);
|
||||
} else if (realmInvalidations.contains(cached.getId())) {
|
||||
return getDelegate().getRealmByName(name);
|
||||
} else if (managedRealms.containsKey(cached.getId())) {
|
||||
return managedRealms.get(cached.getId());
|
||||
}
|
||||
RealmAdapter adapter = new RealmAdapter(cached, this);
|
||||
managedRealms.put(cached.getId(), adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RealmModel> getRealms() {
|
||||
// Retrieve realms from backend
|
||||
List<RealmModel> backendRealms = getDelegate().getRealms();
|
||||
|
||||
// Return cache delegates to ensure cache invalidated during write operations
|
||||
List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
|
||||
for (RealmModel realm : backendRealms) {
|
||||
RealmModel cached = getRealm(realm.getId());
|
||||
cachedRealms.add(cached);
|
||||
}
|
||||
return cachedRealms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRealm(String id) {
|
||||
cache.invalidateCachedRealmById(id);
|
||||
|
||||
RealmModel realm = getDelegate().getRealm(id);
|
||||
Set<RoleModel> realmRoles = null;
|
||||
if (realm != null) {
|
||||
realmRoles = realm.getRoles();
|
||||
}
|
||||
|
||||
boolean didIt = getDelegate().removeRealm(id);
|
||||
realmInvalidations.add(id);
|
||||
|
||||
// TODO: Temporary workaround to invalidate cached realm roles
|
||||
if (didIt && realmRoles != null) {
|
||||
for (RoleModel role : realmRoles) {
|
||||
roleInvalidations.add(role.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return didIt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (delegate != null) delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getRoleById(String id, RealmModel realm) {
|
||||
CachedRole cached = cache.getRole(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
Long loaded = UpdateCounter.current();
|
||||
RoleModel model = getDelegate().getRoleById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (roleInvalidations.contains(id)) return model;
|
||||
if (model.getContainer() instanceof ClientModel) {
|
||||
cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
|
||||
} else {
|
||||
cached = new RevisionedCachedRealmRole(loaded, model, realm);
|
||||
}
|
||||
cache.addCachedRole(cached);
|
||||
|
||||
} else if (roleInvalidations.contains(id)) {
|
||||
return getDelegate().getRoleById(id, realm);
|
||||
} else if (managedRoles.containsKey(id)) {
|
||||
return managedRoles.get(id);
|
||||
}
|
||||
RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
|
||||
managedRoles.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id, RealmModel realm) {
|
||||
CachedGroup cached = cache.getGroup(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
Long loaded = UpdateCounter.current();
|
||||
GroupModel model = getDelegate().getGroupById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (groupInvalidations.contains(id)) return model;
|
||||
cached = new RevisionedCachedGroup(loaded, realm, model);
|
||||
cache.addCachedGroup(cached);
|
||||
|
||||
} else if (groupInvalidations.contains(id)) {
|
||||
return getDelegate().getGroupById(id, realm);
|
||||
} else if (managedGroups.containsKey(id)) {
|
||||
return managedGroups.get(id);
|
||||
}
|
||||
GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
|
||||
managedGroups.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClientById(String id, RealmModel realm) {
|
||||
CachedClient cached = cache.getApplication(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
if (cached != null) {
|
||||
logger.tracev("client by id cache hit: {0}", cached.getClientId());
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
Long loaded = UpdateCounter.current();
|
||||
ClientModel model = getDelegate().getClientById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (appInvalidations.contains(id)) return model;
|
||||
cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
|
||||
cache.addCachedClient(cached);
|
||||
} else if (appInvalidations.contains(id)) {
|
||||
return getDelegate().getClientById(id, realm);
|
||||
} else if (managedApplications.containsKey(id)) {
|
||||
return managedApplications.get(id);
|
||||
}
|
||||
ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
|
||||
managedApplications.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
@Override
|
||||
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
|
||||
CachedClientTemplate cached = cache.getClientTemplate(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
Long loaded = UpdateCounter.current();
|
||||
ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (clientTemplateInvalidations.contains(id)) return model;
|
||||
cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model);
|
||||
cache.addCachedClientTemplate(cached);
|
||||
} else if (clientTemplateInvalidations.contains(id)) {
|
||||
return getDelegate().getClientTemplateById(id, realm);
|
||||
} else if (managedClientTemplates.containsKey(id)) {
|
||||
return managedClientTemplates.get(id);
|
||||
}
|
||||
ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
|
||||
managedClientTemplates.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
}
|
161
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java
vendored
Executable file
161
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java
vendored
Executable file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* 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.counter;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.notifications.Listener;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.cache.CacheRealmProvider;
|
||||
import org.keycloak.models.cache.CacheRealmProviderFactory;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RevisionedCacheRealmProviderFactory implements CacheRealmProviderFactory {
|
||||
|
||||
private static final Logger log = Logger.getLogger(RevisionedCacheRealmProviderFactory.class);
|
||||
|
||||
protected volatile RevisionedRealmCache realmCache;
|
||||
|
||||
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public CacheRealmProvider create(KeycloakSession session) {
|
||||
lazyInit(session);
|
||||
return new RevisionedCacheRealmProvider(realmCache, session);
|
||||
}
|
||||
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
if (realmCache == null) {
|
||||
synchronized (this) {
|
||||
if (realmCache == null) {
|
||||
Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||
Cache<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(RevisionedConnectionProviderFactory.VERSION_CACHE_NAME);
|
||||
cache.addListener(new CacheListener());
|
||||
realmCache = new RevisionedRealmCache(cache, counterCache, realmLookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "infinispan-revisioned";
|
||||
}
|
||||
|
||||
@Listener
|
||||
public class CacheListener {
|
||||
|
||||
@CacheEntryCreated
|
||||
public void created(CacheEntryCreatedEvent<String, Object> event) {
|
||||
if (!event.isPre()) {
|
||||
Object object = event.getValue();
|
||||
if (object != null) {
|
||||
if (object instanceof CachedRealm) {
|
||||
CachedRealm realm = (CachedRealm) object;
|
||||
realmLookup.put(realm.getName(), realm.getId());
|
||||
log.tracev("Realm added realm={0}", realm.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntryRemoved
|
||||
public void removed(CacheEntryRemovedEvent<String, Object> event) {
|
||||
if (event.isPre()) {
|
||||
Object object = event.getValue();
|
||||
if (object != null) {
|
||||
remove(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntryInvalidated
|
||||
public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
|
||||
if (event.isPre()) {
|
||||
Object object = event.getValue();
|
||||
if (object != null) {
|
||||
remove(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntriesEvicted
|
||||
public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
|
||||
for (Object object : event.getEntries().values()) {
|
||||
remove(object);
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(Object object) {
|
||||
if (object instanceof CachedRealm) {
|
||||
CachedRealm realm = (CachedRealm) object;
|
||||
|
||||
realmLookup.remove(realm.getName());
|
||||
|
||||
for (String r : realm.getRealmRoles().values()) {
|
||||
realmCache.evictCachedRoleById(r);
|
||||
}
|
||||
|
||||
for (String c : realm.getClients().values()) {
|
||||
realmCache.evictCachedApplicationById(c);
|
||||
}
|
||||
|
||||
log.tracev("Realm removed realm={0}", realm.getName());
|
||||
} else if (object instanceof CachedClient) {
|
||||
CachedClient client = (CachedClient) object;
|
||||
|
||||
for (String r : client.getRoles().values()) {
|
||||
realmCache.evictCachedRoleById(r);
|
||||
}
|
||||
|
||||
log.tracev("Client removed client={0}", client.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.counter;
|
||||
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RevisionedConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
|
||||
public static final String VERSION_CACHE_NAME = "realmVersions";
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(RevisionedConnectionProviderFactory.class);
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "revisioned";
|
||||
}
|
||||
|
||||
|
||||
protected void initEmbedded() {
|
||||
super.initEmbedded();
|
||||
ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
|
||||
Configuration counterCacheConfiguration = counterConfigBuilder.build();
|
||||
|
||||
cacheManager.defineConfiguration(VERSION_CACHE_NAME, counterCacheConfiguration);
|
||||
}
|
||||
|
||||
}
|
255
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
vendored
Executable file
255
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
vendored
Executable file
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* 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.counter;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.cache.RealmCache;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedClientTemplate;
|
||||
import org.keycloak.models.cache.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.entities.CachedRole;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RevisionedRealmCache implements RealmCache {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(RevisionedRealmCache.class);
|
||||
|
||||
protected final Cache<String, Long> revisions;
|
||||
protected final Cache<String, Object> cache;
|
||||
|
||||
protected final ConcurrentHashMap<String, String> realmLookup;
|
||||
|
||||
public RevisionedRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
|
||||
this.cache = cache;
|
||||
this.realmLookup = realmLookup;
|
||||
this.revisions = revisions;
|
||||
}
|
||||
|
||||
public Cache<String, Object> getCache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
private <T> T get(String id, Class<T> type) {
|
||||
Revisioned o = (Revisioned)cache.get(id);
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
Long rev = revisions.get(id);
|
||||
if (rev == null) {
|
||||
logger.tracev("get() missing rev");
|
||||
return null;
|
||||
}
|
||||
long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
|
||||
if (rev > oRev) {
|
||||
logger.tracev("stale rev: {0} o.rev: {1}", rev.longValue(), oRev);
|
||||
return null;
|
||||
}
|
||||
return o != null && type.isInstance(o) ? type.cast(o) : null;
|
||||
}
|
||||
|
||||
protected Object invalidateObject(String id) {
|
||||
Object removed = cache.remove(id);
|
||||
revisions.put(id, UpdateCounter.next());
|
||||
return removed;
|
||||
}
|
||||
|
||||
protected void addRevisioned(String id, Revisioned object) {
|
||||
Long rev = revisions.get(id);
|
||||
if (rev == null) {
|
||||
logger.tracev("rev was null in addRevisioned, adding one");
|
||||
rev = UpdateCounter.next();
|
||||
revisions.put(id, rev);
|
||||
return;
|
||||
}
|
||||
cache.putForExternalRead(id, object);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRealm getCachedRealm(String id) {
|
||||
return get(id, CachedRealm.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRealm(CachedRealm realm) {
|
||||
logger.tracev("Invalidating realm {0}", realm.getId());
|
||||
invalidateObject(realm.getId());
|
||||
realmLookup.remove(realm.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRealmById(String id) {
|
||||
CachedRealm cached = (CachedRealm) invalidateObject(id);
|
||||
if (cached != null) realmLookup.remove(cached.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedRealm(CachedRealm realm) {
|
||||
logger.tracev("Adding realm {0}", realm.getId());
|
||||
addRevisioned(realm.getId(), (Revisioned) realm);
|
||||
realmLookup.put(realm.getName(), realm.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRealm getCachedRealmByName(String name) {
|
||||
String id = realmLookup.get(name);
|
||||
return id != null ? getCachedRealm(id) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedClient getApplication(String id) {
|
||||
return get(id, CachedClient.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateApplication(CachedClient app) {
|
||||
logger.tracev("Removing application {0}", app.getId());
|
||||
invalidateObject(app.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedClient(CachedClient app) {
|
||||
logger.tracev("Adding application {0}", app.getId());
|
||||
addRevisioned(app.getId(), (Revisioned) app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedApplicationById(String id) {
|
||||
CachedClient client = (CachedClient)invalidateObject(id);
|
||||
if (client != null) logger.tracev("Removing application {0}", client.getClientId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictCachedApplicationById(String id) {
|
||||
logger.tracev("Evicting application {0}", id);
|
||||
cache.evict(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedGroup getGroup(String id) {
|
||||
return get(id, CachedGroup.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateGroup(CachedGroup role) {
|
||||
logger.tracev("Removing group {0}", role.getId());
|
||||
invalidateObject(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedGroup(CachedGroup role) {
|
||||
logger.tracev("Adding group {0}", role.getId());
|
||||
addRevisioned(role.getId(), (Revisioned) role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedGroupById(String id) {
|
||||
logger.tracev("Removing group {0}", id);
|
||||
invalidateObject(id);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateGroupById(String id) {
|
||||
logger.tracev("Removing group {0}", id);
|
||||
invalidateObject(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRole getRole(String id) {
|
||||
return get(id, CachedRole.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateRole(CachedRole role) {
|
||||
logger.tracev("Removing role {0}", role.getId());
|
||||
invalidateObject(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateRoleById(String id) {
|
||||
logger.tracev("Removing role {0}", id);
|
||||
invalidateObject(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictCachedRoleById(String id) {
|
||||
logger.tracev("Evicting role {0}", id);
|
||||
cache.evict(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedRole(CachedRole role) {
|
||||
logger.tracev("Adding role {0}", role.getId());
|
||||
addRevisioned(role.getId(), (Revisioned) role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRoleById(String id) {
|
||||
logger.tracev("Removing role {0}", id);
|
||||
invalidateObject(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedClientTemplate getClientTemplate(String id) {
|
||||
return get(id, CachedClientTemplate.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateClientTemplate(CachedClientTemplate app) {
|
||||
logger.tracev("Removing client template {0}", app.getId());
|
||||
invalidateObject(app.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedClientTemplate(CachedClientTemplate app) {
|
||||
logger.tracev("Adding client template {0}", app.getId());
|
||||
addRevisioned(app.getId(), (Revisioned) app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedClientTemplateById(String id) {
|
||||
logger.tracev("Removing client template {0}", id);
|
||||
invalidateObject(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictCachedClientTemplateById(String id) {
|
||||
logger.tracev("Evicting client template {0}", id);
|
||||
cache.evict(id);
|
||||
}
|
||||
|
||||
|
||||
}
|
20
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java
vendored
Executable file
20
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java
vendored
Executable file
|
@ -0,0 +1,20 @@
|
|||
package org.keycloak.models.cache.infinispan.counter;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UpdateCounter {
|
||||
|
||||
private static final AtomicLong counter = new AtomicLong();
|
||||
|
||||
public static long current() {
|
||||
return counter.get();
|
||||
}
|
||||
|
||||
public static long next() {
|
||||
return counter.incrementAndGet();
|
||||
}
|
||||
|
||||
}
|
41
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java
vendored
Executable file
41
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java
vendored
Executable file
|
@ -0,0 +1,41 @@
|
|||
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.cache.RealmCache;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedClientRole;
|
||||
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RevisionedCachedClient extends CachedClient implements Revisioned {
|
||||
|
||||
public RevisionedCachedClient(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
|
||||
super(cache, delegate, realm, model);
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
private Long revision;
|
||||
|
||||
@Override
|
||||
public Long getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRevision(Long revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cacheRoles(RealmCache cache, RealmModel realm, ClientModel model) {
|
||||
for (RoleModel role : model.getRoles()) {
|
||||
roles.put(role.getName(), role.getId());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.cache.entities.CachedClientRole;
|
||||
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RevisionedCachedClientRole extends CachedClientRole implements Revisioned {
|
||||
|
||||
public RevisionedCachedClientRole(Long revision, String idClient, RoleModel model, RealmModel realm) {
|
||||
super(idClient, model, realm);
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
private Long revision;
|
||||
|
||||
@Override
|
||||
public Long getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRevision(Long revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||
|
||||
import org.keycloak.models.ClientTemplateModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.cache.RealmCache;
|
||||
import org.keycloak.models.cache.entities.CachedClientTemplate;
|
||||
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RevisionedCachedClientTemplate extends CachedClientTemplate implements Revisioned {
|
||||
|
||||
public RevisionedCachedClientTemplate(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientTemplateModel model) {
|
||||
super(cache, delegate, realm, model);
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
private Long revision;
|
||||
|
||||
@Override
|
||||
public Long getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRevision(Long revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
|
||||
}
|
31
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java
vendored
Executable file
31
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java
vendored
Executable file
|
@ -0,0 +1,31 @@
|
|||
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.cache.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RevisionedCachedGroup extends CachedGroup implements Revisioned {
|
||||
public RevisionedCachedGroup(Long revision, RealmModel realm, GroupModel group) {
|
||||
super(realm, group);
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
private Long revision;
|
||||
|
||||
@Override
|
||||
public Long getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRevision(Long revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
|
||||
}
|
59
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java
vendored
Executable file
59
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java
vendored
Executable file
|
@ -0,0 +1,59 @@
|
|||
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientTemplateModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.cache.RealmCache;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedClientTemplate;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.entities.CachedRealmRole;
|
||||
import org.keycloak.models.cache.entities.CachedRole;
|
||||
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RevisionedCachedRealm extends CachedRealm implements Revisioned {
|
||||
|
||||
public RevisionedCachedRealm(Long revision, RealmCache cache, RealmProvider delegate, RealmModel model) {
|
||||
super(cache, delegate, model);
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
private Long revision;
|
||||
|
||||
@Override
|
||||
public Long getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRevision(Long revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cacheClientTemplates(RealmCache cache, RealmProvider delegate, RealmModel model) {
|
||||
for (ClientTemplateModel template : model.getClientTemplates()) {
|
||||
clientTemplates.add(template.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cacheClients(RealmCache cache, RealmProvider delegate, RealmModel model) {
|
||||
for (ClientModel client : model.getClients()) {
|
||||
clients.put(client.getClientId(), client.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cacheRealmRoles(RealmCache cache, RealmModel model) {
|
||||
for (RoleModel role : model.getRoles()) {
|
||||
realmRoles.put(role.getName(), role.getId());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.cache.entities.CachedRealmRole;
|
||||
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RevisionedCachedRealmRole extends CachedRealmRole implements Revisioned {
|
||||
|
||||
public RevisionedCachedRealmRole(Long revision, RoleModel model, RealmModel realm) {
|
||||
super(model, realm);
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
private Long revision;
|
||||
|
||||
@Override
|
||||
public Long getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRevision(Long revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
}
|
30
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java
vendored
Executable file
30
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java
vendored
Executable file
|
@ -0,0 +1,30 @@
|
|||
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.cache.entities.CachedUser;
|
||||
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RevisionedCachedUser extends CachedUser implements Revisioned {
|
||||
public RevisionedCachedUser(Long revision, RealmModel realm, UserModel user) {
|
||||
super(realm, user);
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
private Long revision;
|
||||
|
||||
@Override
|
||||
public Long getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRevision(Long revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
}
|
463
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
vendored
Executable file
463
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
vendored
Executable file
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* 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.locking;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientTemplateModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.cache.CacheRealmProvider;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedClientTemplate;
|
||||
import org.keycloak.models.cache.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.entities.CachedRole;
|
||||
import org.keycloak.models.cache.infinispan.ClientAdapter;
|
||||
import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
|
||||
import org.keycloak.models.cache.infinispan.GroupAdapter;
|
||||
import org.keycloak.models.cache.infinispan.RealmAdapter;
|
||||
import org.keycloak.models.cache.infinispan.RoleAdapter;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
|
||||
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class LockingCacheRealmProvider implements CacheRealmProvider {
|
||||
protected static final Logger logger = Logger.getLogger(LockingCacheRealmProvider.class);
|
||||
protected LockingRealmCache cache;
|
||||
protected KeycloakSession session;
|
||||
protected RealmProvider delegate;
|
||||
protected boolean transactionActive;
|
||||
protected boolean setRollbackOnly;
|
||||
|
||||
protected Set<String> realmInvalidations = new HashSet<>();
|
||||
protected Set<String> appInvalidations = new HashSet<>();
|
||||
protected Set<String> clientTemplateInvalidations = new HashSet<>();
|
||||
protected Set<String> roleInvalidations = new HashSet<>();
|
||||
protected Set<String> groupInvalidations = new HashSet<>();
|
||||
protected Map<String, RealmModel> managedRealms = new HashMap<>();
|
||||
protected Map<String, ClientModel> managedApplications = new HashMap<>();
|
||||
protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
|
||||
protected Map<String, RoleModel> managedRoles = new HashMap<>();
|
||||
protected Map<String, GroupModel> managedGroups = new HashMap<>();
|
||||
|
||||
protected boolean clearAll;
|
||||
|
||||
public LockingCacheRealmProvider(LockingRealmCache cache, KeycloakSession session) {
|
||||
this.cache = cache;
|
||||
this.session = session;
|
||||
|
||||
session.getTransaction().enlistPrepare(getPrepareTransaction());
|
||||
session.getTransaction().enlistAfterCompletion(getAfterTransaction());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MigrationModel getMigrationModel() {
|
||||
return getDelegate().getMigrationModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmProvider getDelegate() {
|
||||
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
|
||||
if (delegate != null) return delegate;
|
||||
delegate = session.getProvider(RealmProvider.class);
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRealmInvalidation(String id) {
|
||||
realmInvalidations.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerApplicationInvalidation(String id) {
|
||||
appInvalidations.add(id);
|
||||
}
|
||||
@Override
|
||||
public void registerClientTemplateInvalidation(String id) {
|
||||
clientTemplateInvalidations.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRoleInvalidation(String id) {
|
||||
roleInvalidations.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerGroupInvalidation(String id) {
|
||||
groupInvalidations.add(id);
|
||||
|
||||
}
|
||||
|
||||
protected void runInvalidations() {
|
||||
for (String id : realmInvalidations) {
|
||||
cache.invalidateCachedRealmById(id);
|
||||
}
|
||||
for (String id : roleInvalidations) {
|
||||
cache.invalidateRoleById(id);
|
||||
}
|
||||
for (String id : groupInvalidations) {
|
||||
cache.invalidateGroupById(id);
|
||||
}
|
||||
for (String id : appInvalidations) {
|
||||
cache.invalidateCachedApplicationById(id);
|
||||
}
|
||||
for (String id : clientTemplateInvalidations) {
|
||||
cache.invalidateCachedClientTemplateById(id);
|
||||
}
|
||||
}
|
||||
|
||||
private KeycloakTransaction getPrepareTransaction() {
|
||||
return new KeycloakTransaction() {
|
||||
@Override
|
||||
public void begin() {
|
||||
transactionActive = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
if (delegate == null) return;
|
||||
List<String> invalidates = new LinkedList<>();
|
||||
for (String id : realmInvalidations) {
|
||||
invalidates.add(id);
|
||||
}
|
||||
for (String id : roleInvalidations) {
|
||||
invalidates.add(id);
|
||||
}
|
||||
for (String id : groupInvalidations) {
|
||||
invalidates.add(id);
|
||||
}
|
||||
for (String id : appInvalidations) {
|
||||
invalidates.add(id);
|
||||
}
|
||||
for (String id : clientTemplateInvalidations) {
|
||||
invalidates.add(id);
|
||||
}
|
||||
|
||||
Collections.sort(invalidates); // lock ordering
|
||||
cache.getRevisions().startBatch();
|
||||
for (String id : invalidates) {
|
||||
cache.getRevisions().getAdvancedCache().lock(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
setRollbackOnly = true;
|
||||
transactionActive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
setRollbackOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRollbackOnly() {
|
||||
return setRollbackOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return transactionActive;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private KeycloakTransaction getAfterTransaction() {
|
||||
return new KeycloakTransaction() {
|
||||
@Override
|
||||
public void begin() {
|
||||
transactionActive = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
try {
|
||||
if (delegate == null) return;
|
||||
if (clearAll) {
|
||||
cache.clear();
|
||||
}
|
||||
runInvalidations();
|
||||
transactionActive = false;
|
||||
} finally {
|
||||
cache.endRevisionBatch();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
try {
|
||||
setRollbackOnly = true;
|
||||
runInvalidations();
|
||||
transactionActive = false;
|
||||
} finally {
|
||||
cache.endRevisionBatch();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
setRollbackOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRollbackOnly() {
|
||||
return setRollbackOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return transactionActive;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel createRealm(String name) {
|
||||
RealmModel realm = getDelegate().createRealm(name);
|
||||
registerRealmInvalidation(realm.getId());
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel createRealm(String id, String name) {
|
||||
RealmModel realm = getDelegate().createRealm(id, name);
|
||||
registerRealmInvalidation(realm.getId());
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm(String id) {
|
||||
CachedRealm cached = cache.getCachedRealm(id);
|
||||
if (cached != null) {
|
||||
logger.tracev("by id cache hit: {0}", cached.getName());
|
||||
}
|
||||
if (cached == null) {
|
||||
Long loaded = cache.getCurrentRevision(id);
|
||||
RealmModel model = getDelegate().getRealm(id);
|
||||
if (model == null) return null;
|
||||
if (realmInvalidations.contains(id)) return model;
|
||||
cached = new RevisionedCachedRealm(loaded, cache, this, model);
|
||||
cache.addCachedRealm(cached);
|
||||
} else if (realmInvalidations.contains(id)) {
|
||||
return getDelegate().getRealm(id);
|
||||
} else if (managedRealms.containsKey(id)) {
|
||||
return managedRealms.get(id);
|
||||
}
|
||||
RealmAdapter adapter = new RealmAdapter(cached, this);
|
||||
managedRealms.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealmByName(String name) {
|
||||
CachedRealm cached = cache.getCachedRealmByName(name);
|
||||
if (cached != null) {
|
||||
logger.tracev("by name cache hit: {0}", cached.getName());
|
||||
}
|
||||
if (cached == null) {
|
||||
RealmModel model = getDelegate().getRealmByName(name);
|
||||
if (model == null) return null;
|
||||
if (realmInvalidations.contains(model.getId())) return model;
|
||||
cached = new RevisionedCachedRealm(null, cache, this, model);
|
||||
cache.addCachedRealm(cached);
|
||||
} else if (realmInvalidations.contains(cached.getId())) {
|
||||
return getDelegate().getRealmByName(name);
|
||||
} else if (managedRealms.containsKey(cached.getId())) {
|
||||
return managedRealms.get(cached.getId());
|
||||
}
|
||||
RealmAdapter adapter = new RealmAdapter(cached, this);
|
||||
managedRealms.put(cached.getId(), adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RealmModel> getRealms() {
|
||||
// Retrieve realms from backend
|
||||
List<RealmModel> backendRealms = getDelegate().getRealms();
|
||||
|
||||
// Return cache delegates to ensure cache invalidated during write operations
|
||||
List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
|
||||
for (RealmModel realm : backendRealms) {
|
||||
RealmModel cached = getRealm(realm.getId());
|
||||
cachedRealms.add(cached);
|
||||
}
|
||||
return cachedRealms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRealm(String id) {
|
||||
cache.invalidateCachedRealmById(id);
|
||||
|
||||
RealmModel realm = getDelegate().getRealm(id);
|
||||
Set<RoleModel> realmRoles = null;
|
||||
if (realm != null) {
|
||||
realmRoles = realm.getRoles();
|
||||
}
|
||||
|
||||
boolean didIt = getDelegate().removeRealm(id);
|
||||
realmInvalidations.add(id);
|
||||
|
||||
// TODO: Temporary workaround to invalidate cached realm roles
|
||||
if (didIt && realmRoles != null) {
|
||||
for (RoleModel role : realmRoles) {
|
||||
roleInvalidations.add(role.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return didIt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (delegate != null) delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getRoleById(String id, RealmModel realm) {
|
||||
CachedRole cached = cache.getRole(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
Long loaded = cache.getCurrentRevision(id);
|
||||
RoleModel model = getDelegate().getRoleById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (roleInvalidations.contains(id)) return model;
|
||||
if (model.getContainer() instanceof ClientModel) {
|
||||
cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
|
||||
} else {
|
||||
cached = new RevisionedCachedRealmRole(loaded, model, realm);
|
||||
}
|
||||
cache.addCachedRole(cached);
|
||||
|
||||
} else if (roleInvalidations.contains(id)) {
|
||||
return getDelegate().getRoleById(id, realm);
|
||||
} else if (managedRoles.containsKey(id)) {
|
||||
return managedRoles.get(id);
|
||||
}
|
||||
RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
|
||||
managedRoles.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id, RealmModel realm) {
|
||||
CachedGroup cached = cache.getGroup(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
Long loaded = cache.getCurrentRevision(id);
|
||||
GroupModel model = getDelegate().getGroupById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (groupInvalidations.contains(id)) return model;
|
||||
cached = new RevisionedCachedGroup(loaded, realm, model);
|
||||
cache.addCachedGroup(cached);
|
||||
|
||||
} else if (groupInvalidations.contains(id)) {
|
||||
return getDelegate().getGroupById(id, realm);
|
||||
} else if (managedGroups.containsKey(id)) {
|
||||
return managedGroups.get(id);
|
||||
}
|
||||
GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
|
||||
managedGroups.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClientById(String id, RealmModel realm) {
|
||||
CachedClient cached = cache.getApplication(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
if (cached != null && cached.getClientId().equals("client")) {
|
||||
logger.tracev("client by id cache hit: {0}", cached.getClientId());
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
Long loaded = cache.getCurrentRevision(id);
|
||||
ClientModel model = getDelegate().getClientById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (appInvalidations.contains(id)) return model;
|
||||
cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
|
||||
cache.addCachedClient(cached);
|
||||
} else if (appInvalidations.contains(id)) {
|
||||
return getDelegate().getClientById(id, realm);
|
||||
} else if (managedApplications.containsKey(id)) {
|
||||
return managedApplications.get(id);
|
||||
}
|
||||
ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
|
||||
managedApplications.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
@Override
|
||||
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
|
||||
CachedClientTemplate cached = cache.getClientTemplate(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
Long loaded = cache.getCurrentRevision(id);
|
||||
ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (clientTemplateInvalidations.contains(id)) return model;
|
||||
cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model);
|
||||
cache.addCachedClientTemplate(cached);
|
||||
} else if (clientTemplateInvalidations.contains(id)) {
|
||||
return getDelegate().getClientTemplateById(id, realm);
|
||||
} else if (managedClientTemplates.containsKey(id)) {
|
||||
return managedClientTemplates.get(id);
|
||||
}
|
||||
ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
|
||||
managedClientTemplates.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
}
|
161
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
vendored
Executable file
161
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
vendored
Executable file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* 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.locking;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.notifications.Listener;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.cache.CacheRealmProvider;
|
||||
import org.keycloak.models.cache.CacheRealmProviderFactory;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LockingCacheRealmProviderFactory implements CacheRealmProviderFactory {
|
||||
|
||||
private static final Logger log = Logger.getLogger(LockingCacheRealmProviderFactory.class);
|
||||
|
||||
protected volatile LockingRealmCache realmCache;
|
||||
|
||||
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public CacheRealmProvider create(KeycloakSession session) {
|
||||
lazyInit(session);
|
||||
return new LockingCacheRealmProvider(realmCache, session);
|
||||
}
|
||||
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
if (realmCache == null) {
|
||||
synchronized (this) {
|
||||
if (realmCache == null) {
|
||||
Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||
Cache<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(LockingConnectionProviderFactory.VERSION_CACHE_NAME);
|
||||
cache.addListener(new CacheListener());
|
||||
realmCache = new LockingRealmCache(cache, counterCache, realmLookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "infinispan-locking";
|
||||
}
|
||||
|
||||
@Listener
|
||||
public class CacheListener {
|
||||
|
||||
@CacheEntryCreated
|
||||
public void created(CacheEntryCreatedEvent<String, Object> event) {
|
||||
if (!event.isPre()) {
|
||||
Object object = event.getValue();
|
||||
if (object != null) {
|
||||
if (object instanceof CachedRealm) {
|
||||
CachedRealm realm = (CachedRealm) object;
|
||||
realmLookup.put(realm.getName(), realm.getId());
|
||||
log.tracev("Realm added realm={0}", realm.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntryRemoved
|
||||
public void removed(CacheEntryRemovedEvent<String, Object> event) {
|
||||
if (event.isPre()) {
|
||||
Object object = event.getValue();
|
||||
if (object != null) {
|
||||
remove(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntryInvalidated
|
||||
public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
|
||||
if (event.isPre()) {
|
||||
Object object = event.getValue();
|
||||
if (object != null) {
|
||||
remove(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntriesEvicted
|
||||
public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
|
||||
for (Object object : event.getEntries().values()) {
|
||||
remove(object);
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(Object object) {
|
||||
if (object instanceof CachedRealm) {
|
||||
CachedRealm realm = (CachedRealm) object;
|
||||
|
||||
realmLookup.remove(realm.getName());
|
||||
|
||||
for (String r : realm.getRealmRoles().values()) {
|
||||
realmCache.evictCachedRoleById(r);
|
||||
}
|
||||
|
||||
for (String c : realm.getClients().values()) {
|
||||
realmCache.evictCachedApplicationById(c);
|
||||
}
|
||||
|
||||
log.tracev("Realm removed realm={0}", realm.getName());
|
||||
} else if (object instanceof CachedClient) {
|
||||
CachedClient client = (CachedClient) object;
|
||||
|
||||
for (String r : client.getRoles().values()) {
|
||||
realmCache.evictCachedRoleById(r);
|
||||
}
|
||||
|
||||
log.tracev("Client removed client={0}", client.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.locking;
|
||||
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.infinispan.transaction.LockingMode;
|
||||
import org.infinispan.transaction.TransactionMode;
|
||||
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LockingConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
|
||||
public static final String VERSION_CACHE_NAME = "realmVersions";
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(LockingConnectionProviderFactory.class);
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "locking";
|
||||
}
|
||||
|
||||
|
||||
protected void initEmbedded() {
|
||||
super.initEmbedded();
|
||||
ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
|
||||
counterConfigBuilder.invocationBatching().enable()
|
||||
.transaction().transactionMode(TransactionMode.TRANSACTIONAL);
|
||||
counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
|
||||
counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
|
||||
Configuration counterCacheConfiguration = counterConfigBuilder.build();
|
||||
|
||||
cacheManager.defineConfiguration(VERSION_CACHE_NAME, counterCacheConfiguration);
|
||||
}
|
||||
|
||||
}
|
294
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
vendored
Executable file
294
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
vendored
Executable file
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* 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.locking;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.cache.RealmCache;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedClientTemplate;
|
||||
import org.keycloak.models.cache.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.entities.CachedRole;
|
||||
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LockingRealmCache implements RealmCache {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(LockingRealmCache.class);
|
||||
|
||||
protected final Cache<String, Long> revisions;
|
||||
protected final Cache<String, Object> cache;
|
||||
final AtomicLong realmCounter = new AtomicLong();
|
||||
final AtomicLong clientCounter = new AtomicLong();
|
||||
final AtomicLong clientTemplateCounter = new AtomicLong();
|
||||
final AtomicLong roleCounter = new AtomicLong();
|
||||
final AtomicLong groupCounter = new AtomicLong();
|
||||
|
||||
protected final ConcurrentHashMap<String, String> realmLookup;
|
||||
|
||||
public LockingRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
|
||||
this.cache = cache;
|
||||
this.realmLookup = realmLookup;
|
||||
this.revisions = revisions;
|
||||
}
|
||||
|
||||
public Cache<String, Object> getCache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
public Cache<String, Long> getRevisions() {
|
||||
return revisions;
|
||||
}
|
||||
|
||||
public void startRevisionBatch() {
|
||||
revisions.startBatch();
|
||||
}
|
||||
|
||||
public void endRevisionBatch() {
|
||||
try {
|
||||
revisions.endBatch(true);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private <T> T get(String id, Class<T> type) {
|
||||
Revisioned o = (Revisioned)cache.get(id);
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
Long rev = revisions.get(id);
|
||||
if (rev == null) {
|
||||
logger.tracev("get() missing rev");
|
||||
return null;
|
||||
}
|
||||
long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
|
||||
if (rev > oRev) {
|
||||
logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
|
||||
return null;
|
||||
}
|
||||
return o != null && type.isInstance(o) ? type.cast(o) : null;
|
||||
}
|
||||
|
||||
protected Object invalidateObject(String id, AtomicLong counter) {
|
||||
Object removed = cache.remove(id);
|
||||
revisions.put(id, counter.incrementAndGet());
|
||||
return removed;
|
||||
}
|
||||
|
||||
protected void addRevisioned(String id, Revisioned object, AtomicLong counter) {
|
||||
//startRevisionBatch();
|
||||
try {
|
||||
//revisions.getAdvancedCache().lock(id);
|
||||
Long rev = revisions.get(id);
|
||||
if (rev == null) {
|
||||
rev = counter.incrementAndGet();
|
||||
revisions.put(id, rev);
|
||||
return;
|
||||
}
|
||||
revisions.startBatch();
|
||||
revisions.getAdvancedCache().lock(id);
|
||||
rev = revisions.get(id);
|
||||
if (rev == null) {
|
||||
rev = counter.incrementAndGet();
|
||||
revisions.put(id, rev);
|
||||
return;
|
||||
}
|
||||
if (rev.equals(object.getRevision())) {
|
||||
cache.putForExternalRead(id, object);
|
||||
}
|
||||
} finally {
|
||||
endRevisionBatch();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public Long getCurrentRevision(String id) {
|
||||
return revisions.get(id);
|
||||
}
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRealm getCachedRealm(String id) {
|
||||
return get(id, CachedRealm.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRealm(CachedRealm realm) {
|
||||
logger.tracev("Invalidating realm {0}", realm.getId());
|
||||
invalidateObject(realm.getId(), realmCounter);
|
||||
realmLookup.remove(realm.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRealmById(String id) {
|
||||
CachedRealm cached = (CachedRealm) invalidateObject(id, realmCounter);
|
||||
if (cached != null) realmLookup.remove(cached.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedRealm(CachedRealm realm) {
|
||||
logger.tracev("Adding realm {0}", realm.getId());
|
||||
addRevisioned(realm.getId(), (Revisioned) realm, realmCounter);
|
||||
realmLookup.put(realm.getName(), realm.getId());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CachedRealm getCachedRealmByName(String name) {
|
||||
String id = realmLookup.get(name);
|
||||
return id != null ? getCachedRealm(id) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedClient getApplication(String id) {
|
||||
return get(id, CachedClient.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateApplication(CachedClient app) {
|
||||
logger.tracev("Removing application {0}", app.getId());
|
||||
invalidateObject(app.getId(), clientCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedClient(CachedClient app) {
|
||||
logger.tracev("Adding application {0}", app.getId());
|
||||
addRevisioned(app.getId(), (Revisioned) app, clientCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedApplicationById(String id) {
|
||||
CachedClient client = (CachedClient)invalidateObject(id, clientCounter);
|
||||
if (client != null) logger.tracev("Removing application {0}", client.getClientId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictCachedApplicationById(String id) {
|
||||
logger.tracev("Evicting application {0}", id);
|
||||
cache.evict(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedGroup getGroup(String id) {
|
||||
return get(id, CachedGroup.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateGroup(CachedGroup role) {
|
||||
logger.tracev("Removing group {0}", role.getId());
|
||||
invalidateObject(role.getId(), groupCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedGroup(CachedGroup role) {
|
||||
logger.tracev("Adding group {0}", role.getId());
|
||||
addRevisioned(role.getId(), (Revisioned) role, groupCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedGroupById(String id) {
|
||||
logger.tracev("Removing group {0}", id);
|
||||
invalidateObject(id, groupCounter);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateGroupById(String id) {
|
||||
logger.tracev("Removing group {0}", id);
|
||||
invalidateObject(id, groupCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRole getRole(String id) {
|
||||
return get(id, CachedRole.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateRole(CachedRole role) {
|
||||
logger.tracev("Removing role {0}", role.getId());
|
||||
invalidateObject(role.getId(), roleCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateRoleById(String id) {
|
||||
logger.tracev("Removing role {0}", id);
|
||||
invalidateObject(id, roleCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictCachedRoleById(String id) {
|
||||
logger.tracev("Evicting role {0}", id);
|
||||
cache.evict(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedRole(CachedRole role) {
|
||||
logger.tracev("Adding role {0}", role.getId());
|
||||
addRevisioned(role.getId(), (Revisioned) role, roleCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRoleById(String id) {
|
||||
logger.tracev("Removing role {0}", id);
|
||||
invalidateObject(id, roleCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedClientTemplate getClientTemplate(String id) {
|
||||
return get(id, CachedClientTemplate.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateClientTemplate(CachedClientTemplate app) {
|
||||
logger.tracev("Removing client template {0}", app.getId());
|
||||
invalidateObject(app.getId(), clientTemplateCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedClientTemplate(CachedClientTemplate app) {
|
||||
logger.tracev("Adding client template {0}", app.getId());
|
||||
addRevisioned(app.getId(), (Revisioned) app, clientTemplateCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedClientTemplateById(String id) {
|
||||
logger.tracev("Removing client template {0}", id);
|
||||
invalidateObject(id, clientTemplateCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictCachedClientTemplateById(String id) {
|
||||
logger.tracev("Evicting client template {0}", id);
|
||||
cache.evict(id);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* 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.skewed;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.notifications.Listener;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
|
||||
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.cache.CacheRealmProvider;
|
||||
import org.keycloak.models.cache.CacheRealmProviderFactory;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RepeatableReadWriteSkewCacheRealmProviderFactory implements CacheRealmProviderFactory {
|
||||
|
||||
private static final Logger log = Logger.getLogger(RepeatableReadWriteSkewCacheRealmProviderFactory.class);
|
||||
|
||||
protected volatile RepeatableReadWriteSkewRealmCache realmCache;
|
||||
|
||||
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public CacheRealmProvider create(KeycloakSession session) {
|
||||
lazyInit(session);
|
||||
return new RepeatableReadWriteSkewRealmCacheProvider(realmCache, session);
|
||||
}
|
||||
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
if (realmCache == null) {
|
||||
synchronized (this) {
|
||||
if (realmCache == null) {
|
||||
Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||
cache.addListener(new CacheListener());
|
||||
realmCache = new RepeatableReadWriteSkewRealmCache(cache, realmLookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "infinispan-versioned";
|
||||
}
|
||||
|
||||
@Listener
|
||||
public class CacheListener {
|
||||
|
||||
@CacheEntryCreated
|
||||
public void created(CacheEntryCreatedEvent<String, Object> event) {
|
||||
if (!event.isPre()) {
|
||||
Object object = event.getValue();
|
||||
if (object != null) {
|
||||
if (object instanceof CachedRealm) {
|
||||
CachedRealm realm = (CachedRealm) object;
|
||||
realmLookup.put(realm.getName(), realm.getId());
|
||||
log.tracev("Realm added realm={0}", realm.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntryRemoved
|
||||
public void removed(CacheEntryRemovedEvent<String, Object> event) {
|
||||
if (event.isPre()) {
|
||||
Object object = event.getValue();
|
||||
if (object != null) {
|
||||
remove(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntryInvalidated
|
||||
public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
|
||||
if (event.isPre()) {
|
||||
Object object = event.getValue();
|
||||
if (object != null) {
|
||||
remove(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEntriesEvicted
|
||||
public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
|
||||
for (Object object : event.getEntries().values()) {
|
||||
remove(object);
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(Object object) {
|
||||
if (object instanceof CachedRealm) {
|
||||
CachedRealm realm = (CachedRealm) object;
|
||||
|
||||
realmLookup.remove(realm.getName());
|
||||
|
||||
for (String r : realm.getRealmRoles().values()) {
|
||||
realmCache.evictCachedRoleById(r);
|
||||
}
|
||||
|
||||
for (String c : realm.getClients().values()) {
|
||||
realmCache.evictCachedApplicationById(c);
|
||||
}
|
||||
|
||||
log.tracev("Realm removed realm={0}", realm.getName());
|
||||
} else if (object instanceof CachedClient) {
|
||||
CachedClient client = (CachedClient) object;
|
||||
|
||||
for (String r : client.getRoles().values()) {
|
||||
realmCache.evictCachedRoleById(r);
|
||||
}
|
||||
|
||||
log.tracev("Client removed client={0}", client.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* 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.skewed;
|
||||
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.infinispan.configuration.cache.VersioningScheme;
|
||||
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||
import org.infinispan.manager.DefaultCacheManager;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
||||
import org.infinispan.util.concurrent.IsolationLevel;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProvider;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RepeatableReadWriteSkewConnectionProviderFactory implements InfinispanConnectionProviderFactory {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewConnectionProviderFactory.class);
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
private EmbeddedCacheManager cacheManager;
|
||||
|
||||
private boolean containerManaged;
|
||||
|
||||
@Override
|
||||
public InfinispanConnectionProvider create(KeycloakSession session) {
|
||||
lazyInit();
|
||||
|
||||
return new DefaultInfinispanConnectionProvider(cacheManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (cacheManager != null && !containerManaged) {
|
||||
cacheManager.stop();
|
||||
}
|
||||
cacheManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "versioned";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
private void lazyInit() {
|
||||
if (cacheManager == null) {
|
||||
synchronized (this) {
|
||||
if (cacheManager == null) {
|
||||
String cacheContainer = config.get("cacheContainer");
|
||||
if (cacheContainer != null) {
|
||||
initContainerManaged(cacheContainer);
|
||||
} else {
|
||||
initEmbedded();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initContainerManaged(String cacheContainerLookup) {
|
||||
try {
|
||||
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
||||
containerManaged = true;
|
||||
|
||||
logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to retrieve cache container", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initEmbedded() {
|
||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||
|
||||
boolean clustered = config.getBoolean("clustered", false);
|
||||
boolean async = config.getBoolean("async", true);
|
||||
boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
|
||||
|
||||
if (clustered) {
|
||||
gcb.transport().defaultTransport();
|
||||
}
|
||||
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
|
||||
|
||||
cacheManager = new DefaultCacheManager(gcb.build());
|
||||
containerManaged = false;
|
||||
|
||||
logger.debug("Started embedded Infinispan cache container");
|
||||
|
||||
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
|
||||
if (clustered) {
|
||||
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
|
||||
}
|
||||
|
||||
invalidationConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
|
||||
invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationConfigBuilder.build());
|
||||
|
||||
ConfigurationBuilder userConfigBuilder = new ConfigurationBuilder();
|
||||
if (clustered) {
|
||||
userConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
|
||||
}
|
||||
Configuration userCacheConfiguration = userConfigBuilder.build();
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, userCacheConfiguration);
|
||||
|
||||
ConfigurationBuilder sessionConfigBuilder = new ConfigurationBuilder();
|
||||
if (clustered) {
|
||||
String sessionsMode = config.get("sessionsMode", "distributed");
|
||||
if (sessionsMode.equalsIgnoreCase("replicated")) {
|
||||
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
|
||||
} else if (sessionsMode.equalsIgnoreCase("distributed")) {
|
||||
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.DIST_ASYNC : CacheMode.DIST_SYNC);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid value for sessionsMode");
|
||||
}
|
||||
|
||||
sessionConfigBuilder.clustering().hash()
|
||||
.numOwners(config.getInt("sessionsOwners", 2))
|
||||
.numSegments(config.getInt("sessionsSegments", 60)).build();
|
||||
}
|
||||
|
||||
Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
|
||||
}
|
||||
|
||||
}
|
269
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java
vendored
Executable file
269
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java
vendored
Executable file
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* 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.skewed;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.cache.RealmCache;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedClientTemplate;
|
||||
import org.keycloak.models.cache.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.entities.CachedRole;
|
||||
|
||||
import javax.transaction.NotSupportedException;
|
||||
import javax.transaction.Status;
|
||||
import javax.transaction.SystemException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RepeatableReadWriteSkewRealmCache implements RealmCache {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCache.class);
|
||||
|
||||
protected final Cache<String, Object> cache;
|
||||
protected final ConcurrentHashMap<String, String> realmLookup;
|
||||
|
||||
public RepeatableReadWriteSkewRealmCache(Cache<String, Object> cache, ConcurrentHashMap<String, String> realmLookup) {
|
||||
this.cache = cache;
|
||||
this.realmLookup = realmLookup;
|
||||
}
|
||||
|
||||
public Cache<String, Object> getCache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
public void startBatch() {
|
||||
logger.trace("*** START BATCH ***");
|
||||
try {
|
||||
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
|
||||
cache.getAdvancedCache().getTransactionManager().begin();
|
||||
}
|
||||
} catch (NotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (SystemException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void endBatch(boolean commit) {
|
||||
logger.trace("*** END BATCH ***");
|
||||
try {
|
||||
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
|
||||
if (commit) {
|
||||
cache.getAdvancedCache().getTransactionManager().commit();
|
||||
|
||||
} else {
|
||||
cache.getAdvancedCache().getTransactionManager().rollback();
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRealm getCachedRealm(String id) {
|
||||
return get(id, CachedRealm.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRealm(CachedRealm realm) {
|
||||
logger.tracev("Invalidating realm {0}", realm.getId());
|
||||
invalidate(realm.getId());
|
||||
realmLookup.remove(realm.getName());
|
||||
}
|
||||
|
||||
protected Object invalidate(String id) {
|
||||
startBatch();
|
||||
Object rtn = cache.remove(id);
|
||||
logger.trace("*** END BATCH ***");
|
||||
try {
|
||||
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
|
||||
if (true) {
|
||||
cache.getAdvancedCache().getTransactionManager().commit();
|
||||
|
||||
} else {
|
||||
cache.getAdvancedCache().getTransactionManager().rollback();
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.trace("Failed to commit invalidate");
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRealmById(String id) {
|
||||
CachedRealm cached = (CachedRealm) invalidate(id);
|
||||
if (cached != null) realmLookup.remove(cached.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedRealm(CachedRealm realm) {
|
||||
logger.tracev("Adding realm {0}", realm.getId());
|
||||
cache.putForExternalRead(realm.getId(), realm);
|
||||
realmLookup.put(realm.getName(), realm.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRealm getCachedRealmByName(String name) {
|
||||
String id = realmLookup.get(name);
|
||||
return id != null ? getCachedRealm(id) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedClient getApplication(String id) {
|
||||
return get(id, CachedClient.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateApplication(CachedClient app) {
|
||||
logger.tracev("Removing application {0}", app.getId());
|
||||
invalidate(app.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedClient(CachedClient app) {
|
||||
logger.tracev("Adding application {0}", app.getId());
|
||||
cache.putForExternalRead(app.getId(), app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedApplicationById(String id) {
|
||||
logger.tracev("Removing application {0}", id);
|
||||
invalidate(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictCachedApplicationById(String id) {
|
||||
logger.tracev("Evicting application {0}", id);
|
||||
cache.evict(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedGroup getGroup(String id) {
|
||||
return get(id, CachedGroup.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateGroup(CachedGroup role) {
|
||||
logger.tracev("Removing group {0}", role.getId());
|
||||
invalidate(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedGroup(CachedGroup role) {
|
||||
logger.tracev("Adding group {0}", role.getId());
|
||||
cache.putForExternalRead(role.getId(), role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedGroupById(String id) {
|
||||
logger.tracev("Removing group {0}", id);
|
||||
invalidate(id);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateGroupById(String id) {
|
||||
logger.tracev("Removing group {0}", id);
|
||||
invalidate(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRole getRole(String id) {
|
||||
return get(id, CachedRole.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateRole(CachedRole role) {
|
||||
logger.tracev("Removing role {0}", role.getId());
|
||||
invalidate(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateRoleById(String id) {
|
||||
logger.tracev("Removing role {0}", id);
|
||||
invalidate(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictCachedRoleById(String id) {
|
||||
logger.tracev("Evicting role {0}", id);
|
||||
cache.evict(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedRole(CachedRole role) {
|
||||
logger.tracev("Adding role {0}", role.getId());
|
||||
cache.putForExternalRead(role.getId(), role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedRoleById(String id) {
|
||||
logger.tracev("Removing role {0}", id);
|
||||
invalidate(id);
|
||||
}
|
||||
|
||||
private <T> T get(String id, Class<T> type) {
|
||||
Object o = cache.get(id);
|
||||
return o != null && type.isInstance(o) ? type.cast(o) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedClientTemplate getClientTemplate(String id) {
|
||||
return get(id, CachedClientTemplate.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateClientTemplate(CachedClientTemplate app) {
|
||||
logger.tracev("Removing client template {0}", app.getId());
|
||||
invalidate(app.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedClientTemplate(CachedClientTemplate app) {
|
||||
logger.tracev("Adding client template {0}", app.getId());
|
||||
cache.putForExternalRead(app.getId(), app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedClientTemplateById(String id) {
|
||||
logger.tracev("Removing client template {0}", id);
|
||||
invalidate(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictCachedClientTemplateById(String id) {
|
||||
logger.tracev("Evicting client template {0}", id);
|
||||
invalidate(id);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,472 @@
|
|||
/*
|
||||
* 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.skewed;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientTemplateModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.cache.CacheRealmProvider;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedClientRole;
|
||||
import org.keycloak.models.cache.entities.CachedClientTemplate;
|
||||
import org.keycloak.models.cache.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.entities.CachedRealmRole;
|
||||
import org.keycloak.models.cache.entities.CachedRole;
|
||||
import org.keycloak.models.cache.infinispan.ClientAdapter;
|
||||
import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
|
||||
import org.keycloak.models.cache.infinispan.GroupAdapter;
|
||||
import org.keycloak.models.cache.infinispan.RealmAdapter;
|
||||
import org.keycloak.models.cache.infinispan.RoleAdapter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* DO NOT USE THIS!!
|
||||
*
|
||||
* Tries unsuccessfully to use Infinispan with REPEATABLE_READ, write-skew-checking
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RepeatableReadWriteSkewRealmCacheProvider implements CacheRealmProvider {
|
||||
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCacheProvider.class);
|
||||
|
||||
protected RepeatableReadWriteSkewRealmCache cache;
|
||||
protected KeycloakSession session;
|
||||
protected RealmProvider delegate;
|
||||
protected boolean transactionActive;
|
||||
protected boolean setRollbackOnly;
|
||||
|
||||
protected Set<String> realmInvalidations = new HashSet<>();
|
||||
protected Set<String> appInvalidations = new HashSet<>();
|
||||
protected Set<String> clientTemplateInvalidations = new HashSet<>();
|
||||
protected Set<String> roleInvalidations = new HashSet<>();
|
||||
protected Set<String> groupInvalidations = new HashSet<>();
|
||||
protected Map<String, RealmModel> managedRealms = new HashMap<>();
|
||||
protected Map<String, ClientModel> managedApplications = new HashMap<>();
|
||||
protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
|
||||
protected Map<String, RoleModel> managedRoles = new HashMap<>();
|
||||
protected Map<String, GroupModel> managedGroups = new HashMap<>();
|
||||
|
||||
protected boolean clearAll;
|
||||
|
||||
public RepeatableReadWriteSkewRealmCacheProvider(RepeatableReadWriteSkewRealmCache cache, KeycloakSession session) {
|
||||
this.cache = cache;
|
||||
this.session = session;
|
||||
|
||||
session.getTransaction().enlistAfterCompletion(getTransaction());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MigrationModel getMigrationModel() {
|
||||
return getDelegate().getMigrationModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmProvider getDelegate() {
|
||||
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
|
||||
if (delegate != null) return delegate;
|
||||
delegate = session.getProvider(RealmProvider.class);
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRealmInvalidation(String id) {
|
||||
realmInvalidations.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerApplicationInvalidation(String id) {
|
||||
appInvalidations.add(id);
|
||||
}
|
||||
@Override
|
||||
public void registerClientTemplateInvalidation(String id) {
|
||||
clientTemplateInvalidations.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRoleInvalidation(String id) {
|
||||
roleInvalidations.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerGroupInvalidation(String id) {
|
||||
groupInvalidations.add(id);
|
||||
|
||||
}
|
||||
|
||||
protected void runInvalidations() {
|
||||
for (String id : realmInvalidations) {
|
||||
cache.invalidateCachedRealmById(id);
|
||||
}
|
||||
for (String id : roleInvalidations) {
|
||||
cache.invalidateRoleById(id);
|
||||
}
|
||||
for (String id : groupInvalidations) {
|
||||
cache.invalidateGroupById(id);
|
||||
}
|
||||
for (String id : appInvalidations) {
|
||||
cache.invalidateCachedApplicationById(id);
|
||||
}
|
||||
for (String id : clientTemplateInvalidations) {
|
||||
cache.invalidateCachedClientTemplateById(id);
|
||||
}
|
||||
}
|
||||
|
||||
private KeycloakTransaction getTransaction() {
|
||||
return new KeycloakTransaction() {
|
||||
@Override
|
||||
public void begin() {
|
||||
transactionActive = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
if (delegate == null) return;
|
||||
if (clearAll) {
|
||||
cache.clear();
|
||||
}
|
||||
runInvalidations();
|
||||
transactionActive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
setRollbackOnly = true;
|
||||
runInvalidations();
|
||||
transactionActive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
setRollbackOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRollbackOnly() {
|
||||
return setRollbackOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return transactionActive;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel createRealm(String name) {
|
||||
RealmModel realm = getDelegate().createRealm(name);
|
||||
registerRealmInvalidation(realm.getId());
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel createRealm(String id, String name) {
|
||||
RealmModel realm = getDelegate().createRealm(id, name);
|
||||
registerRealmInvalidation(realm.getId());
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm(String id) {
|
||||
//cache.startBatch();
|
||||
cache.startBatch();
|
||||
boolean batchEnded = false;
|
||||
try {
|
||||
CachedRealm cached = cache.getCachedRealm(id);
|
||||
boolean wasNull = cached == null;
|
||||
if (cached == null) {
|
||||
RealmModel model = getDelegate().getRealm(id);
|
||||
if (model == null) return null;
|
||||
if (realmInvalidations.contains(id)) return model;
|
||||
cached = new CachedRealm(cache, this, model);
|
||||
cache.addCachedRealm(cached);
|
||||
try {
|
||||
batchEnded = true;
|
||||
cache.endBatch(true);
|
||||
logger.trace("returning new cached realm");
|
||||
} catch (Exception exception) {
|
||||
logger.trace("failed to add to cache", exception);
|
||||
return model;
|
||||
}
|
||||
} else if (realmInvalidations.contains(id)) {
|
||||
return getDelegate().getRealm(id);
|
||||
} else if (managedRealms.containsKey(id)) {
|
||||
return managedRealms.get(id);
|
||||
}
|
||||
if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
|
||||
RealmAdapter adapter = new RealmAdapter(cached, this);
|
||||
managedRealms.put(id, adapter);
|
||||
return adapter;
|
||||
} finally {
|
||||
if (!batchEnded) cache.endBatch(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealmByName(String name) {
|
||||
cache.startBatch();
|
||||
boolean batchEnded = false;
|
||||
try {
|
||||
CachedRealm cached = cache.getCachedRealmByName(name);
|
||||
boolean wasNull = cached == null;
|
||||
if (cached == null) {
|
||||
RealmModel model = getDelegate().getRealmByName(name);
|
||||
if (model == null) return null;
|
||||
if (realmInvalidations.contains(model.getId())) return model;
|
||||
cached = new CachedRealm(cache, this, model);
|
||||
cache.addCachedRealm(cached);
|
||||
try {
|
||||
batchEnded = true;
|
||||
cache.endBatch(true);
|
||||
logger.trace("returning new cached realm: " + cached.getName());
|
||||
} catch (Exception exception) {
|
||||
logger.trace("failed to add to cache", exception);
|
||||
return model;
|
||||
}
|
||||
} else if (realmInvalidations.contains(cached.getId())) {
|
||||
return getDelegate().getRealmByName(name);
|
||||
} else if (managedRealms.containsKey(cached.getId())) {
|
||||
return managedRealms.get(cached.getId());
|
||||
}
|
||||
if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
|
||||
RealmAdapter adapter = new RealmAdapter(cached, this);
|
||||
managedRealms.put(cached.getId(), adapter);
|
||||
return adapter;
|
||||
} finally {
|
||||
if (!batchEnded) cache.endBatch(true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RealmModel> getRealms() {
|
||||
// Retrieve realms from backend
|
||||
List<RealmModel> backendRealms = getDelegate().getRealms();
|
||||
|
||||
// Return cache delegates to ensure cache invalidated during write operations
|
||||
List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
|
||||
for (RealmModel realm : backendRealms) {
|
||||
RealmModel cached = getRealm(realm.getId());
|
||||
cachedRealms.add(cached);
|
||||
}
|
||||
return cachedRealms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRealm(String id) {
|
||||
cache.invalidateCachedRealmById(id);
|
||||
|
||||
RealmModel realm = getDelegate().getRealm(id);
|
||||
Set<RoleModel> realmRoles = null;
|
||||
if (realm != null) {
|
||||
realmRoles = realm.getRoles();
|
||||
}
|
||||
|
||||
boolean didIt = getDelegate().removeRealm(id);
|
||||
realmInvalidations.add(id);
|
||||
|
||||
// TODO: Temporary workaround to invalidate cached realm roles
|
||||
if (didIt && realmRoles != null) {
|
||||
for (RoleModel role : realmRoles) {
|
||||
roleInvalidations.add(role.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return didIt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (delegate != null) delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getRoleById(String id, RealmModel realm) {
|
||||
cache.startBatch();
|
||||
boolean batchEnded = false;
|
||||
try {
|
||||
CachedRole cached = cache.getRole(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
RoleModel model = getDelegate().getRoleById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (roleInvalidations.contains(id)) return model;
|
||||
if (model.getContainer() instanceof ClientModel) {
|
||||
cached = new CachedClientRole(((ClientModel) model.getContainer()).getId(), model, realm);
|
||||
} else {
|
||||
cached = new CachedRealmRole(model, realm);
|
||||
}
|
||||
cache.addCachedRole(cached);
|
||||
try {
|
||||
batchEnded = true;
|
||||
cache.endBatch(true);
|
||||
} catch (Exception exception) {
|
||||
logger.trace("failed to add to cache", exception);
|
||||
return model;
|
||||
}
|
||||
|
||||
} else if (roleInvalidations.contains(id)) {
|
||||
return getDelegate().getRoleById(id, realm);
|
||||
} else if (managedRoles.containsKey(id)) {
|
||||
return managedRoles.get(id);
|
||||
}
|
||||
RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
|
||||
managedRoles.put(id, adapter);
|
||||
return adapter;
|
||||
} finally {
|
||||
if (!batchEnded) cache.endBatch(true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id, RealmModel realm) {
|
||||
cache.startBatch();
|
||||
boolean batchEnded = false;
|
||||
try {
|
||||
CachedGroup cached = cache.getGroup(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
GroupModel model = getDelegate().getGroupById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (groupInvalidations.contains(id)) return model;
|
||||
cached = new CachedGroup(realm, model);
|
||||
cache.addCachedGroup(cached);
|
||||
try {
|
||||
batchEnded = true;
|
||||
cache.endBatch(true);
|
||||
} catch (Exception exception) {
|
||||
logger.trace("failed to add to cache", exception);
|
||||
return model;
|
||||
}
|
||||
|
||||
} else if (groupInvalidations.contains(id)) {
|
||||
return getDelegate().getGroupById(id, realm);
|
||||
} else if (managedGroups.containsKey(id)) {
|
||||
return managedGroups.get(id);
|
||||
}
|
||||
GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
|
||||
managedGroups.put(id, adapter);
|
||||
return adapter;
|
||||
} finally {
|
||||
if (!batchEnded) cache.endBatch(true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClientById(String id, RealmModel realm) {
|
||||
cache.startBatch();
|
||||
boolean batchEnded = false;
|
||||
CachedClient cached = cache.getApplication(id);
|
||||
try {
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
ClientModel model = getDelegate().getClientById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (appInvalidations.contains(id)) return model;
|
||||
cached = new CachedClient(cache, getDelegate(), realm, model);
|
||||
cache.addCachedClient(cached);
|
||||
try {
|
||||
batchEnded = true;
|
||||
cache.endBatch(true);
|
||||
} catch (Exception exception) {
|
||||
logger.trace("failed to add to cache", exception);
|
||||
return model;
|
||||
}
|
||||
} else if (appInvalidations.contains(id)) {
|
||||
return getDelegate().getClientById(id, realm);
|
||||
} else if (managedApplications.containsKey(id)) {
|
||||
return managedApplications.get(id);
|
||||
}
|
||||
ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
|
||||
managedApplications.put(id, adapter);
|
||||
return adapter;
|
||||
} finally {
|
||||
if (!batchEnded) cache.endBatch(true);
|
||||
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
|
||||
cache.startBatch();
|
||||
boolean batchEnded = false;
|
||||
try {
|
||||
CachedClientTemplate cached = cache.getClientTemplate(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (clientTemplateInvalidations.contains(id)) return model;
|
||||
cached = new CachedClientTemplate(cache, getDelegate(), realm, model);
|
||||
cache.addCachedClientTemplate(cached);
|
||||
try {
|
||||
batchEnded = true;
|
||||
cache.endBatch(true);
|
||||
} catch (Exception exception) {
|
||||
logger.trace("failed to add to cache", exception);
|
||||
return model;
|
||||
}
|
||||
} else if (clientTemplateInvalidations.contains(id)) {
|
||||
return getDelegate().getClientTemplateById(id, realm);
|
||||
} else if (managedClientTemplates.containsKey(id)) {
|
||||
return managedClientTemplates.get(id);
|
||||
}
|
||||
ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
|
||||
managedClientTemplates.put(id, adapter);
|
||||
return adapter;
|
||||
} finally {
|
||||
if (!batchEnded) cache.endBatch(true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -24,6 +24,7 @@ import org.keycloak.common.util.Time;
|
|||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.sessions.infinispan.entities.*;
|
||||
import org.keycloak.models.sessions.infinispan.initializer.TimeAwareInitializerState;
|
||||
import org.keycloak.models.sessions.infinispan.stream.*;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RealmInfoUtil;
|
||||
|
@ -410,6 +411,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClusterStartupTime() {
|
||||
TimeAwareInitializerState state = (TimeAwareInitializerState) offlineSessionCache.get(InfinispanUserSessionProviderFactory.SESSION_INITIALIZER_STATE_KEY);
|
||||
int startTime;
|
||||
if (state == null) {
|
||||
log.warn("Cluster startup time not yet available. Fallback to local startup time");
|
||||
startTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
|
||||
} else {
|
||||
startTime = state.getClusterStartupTime();
|
||||
}
|
||||
return startTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
|
|
@ -34,6 +34,9 @@ import org.keycloak.provider.ProviderEventListener;
|
|||
|
||||
public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory {
|
||||
|
||||
private static final String STATE_KEY_PREFIX = "initializerState";
|
||||
public static final String SESSION_INITIALIZER_STATE_KEY = STATE_KEY_PREFIX + "::offlineUserSessions";
|
||||
|
||||
private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
|
||||
|
||||
private Config.Scope config;
|
||||
|
@ -84,7 +87,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
|
||||
|
||||
InfinispanUserSessionInitializer initializer = new InfinispanUserSessionInitializer(sessionFactory, cache, new OfflineUserSessionLoader(), maxErrors, sessionsPerSegment, "offlineUserSessions");
|
||||
InfinispanUserSessionInitializer initializer = new InfinispanUserSessionInitializer(sessionFactory, cache, new OfflineUserSessionLoader(), maxErrors, sessionsPerSegment, SESSION_INITIALIZER_STATE_KEY);
|
||||
initializer.initCache();
|
||||
initializer.loadPersistentSessions();
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged;
|
|||
import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent;
|
||||
import org.infinispan.remoting.transport.Transport;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
|
@ -51,8 +52,6 @@ public class InfinispanUserSessionInitializer {
|
|||
|
||||
private static final Logger log = Logger.getLogger(InfinispanUserSessionInitializer.class);
|
||||
|
||||
private static final String STATE_KEY_PREFIX = "initializerState";
|
||||
|
||||
private final KeycloakSessionFactory sessionFactory;
|
||||
private final Cache<String, SessionEntity> cache;
|
||||
private final SessionLoader sessionLoader;
|
||||
|
@ -60,21 +59,18 @@ public class InfinispanUserSessionInitializer {
|
|||
private final int sessionsPerSegment;
|
||||
private final String stateKey;
|
||||
|
||||
private volatile CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
|
||||
public InfinispanUserSessionInitializer(KeycloakSessionFactory sessionFactory, Cache<String, SessionEntity> cache, SessionLoader sessionLoader, int maxErrors, int sessionsPerSegment, String stateKeySuffix) {
|
||||
public InfinispanUserSessionInitializer(KeycloakSessionFactory sessionFactory, Cache<String, SessionEntity> cache, SessionLoader sessionLoader, int maxErrors, int sessionsPerSegment, String stateKey) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.cache = cache;
|
||||
this.sessionLoader = sessionLoader;
|
||||
this.maxErrors = maxErrors;
|
||||
this.sessionsPerSegment = sessionsPerSegment;
|
||||
this.stateKey = STATE_KEY_PREFIX + "::" + stateKeySuffix;
|
||||
this.stateKey = stateKey;
|
||||
}
|
||||
|
||||
public void initCache() {
|
||||
this.cache.getAdvancedCache().getComponentRegistry().registerComponent(sessionFactory, KeycloakSessionFactory.class);
|
||||
cache.getCacheManager().addListener(new ViewChangeListener());
|
||||
}
|
||||
|
||||
|
||||
|
@ -86,7 +82,7 @@ public class InfinispanUserSessionInitializer {
|
|||
while (!isFinished()) {
|
||||
if (!isCoordinator()) {
|
||||
try {
|
||||
latch.await(500, TimeUnit.MILLISECONDS);
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ie) {
|
||||
log.error("Interrupted", ie);
|
||||
}
|
||||
|
@ -104,8 +100,10 @@ public class InfinispanUserSessionInitializer {
|
|||
|
||||
|
||||
private InitializerState getOrCreateInitializerState() {
|
||||
InitializerState state = (InitializerState) cache.get(stateKey);
|
||||
TimeAwareInitializerState state = (TimeAwareInitializerState) cache.get(stateKey);
|
||||
if (state == null) {
|
||||
int startTime = (int)(sessionFactory.getServerStartupTimestamp() / 1000);
|
||||
|
||||
final int[] count = new int[1];
|
||||
|
||||
// Rather use separate transactions for update and counting
|
||||
|
@ -113,7 +111,7 @@ public class InfinispanUserSessionInitializer {
|
|||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
sessionLoader.init(session);
|
||||
sessionLoader.init(session, startTime);
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -126,8 +124,9 @@ public class InfinispanUserSessionInitializer {
|
|||
|
||||
});
|
||||
|
||||
state = new InitializerState();
|
||||
state = new TimeAwareInitializerState();
|
||||
state.init(count[0], sessionsPerSegment);
|
||||
state.setClusterStartupTime(startTime);
|
||||
saveStateToCache(state);
|
||||
}
|
||||
return state;
|
||||
|
@ -251,24 +250,6 @@ public class InfinispanUserSessionInitializer {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Listener
|
||||
public class ViewChangeListener {
|
||||
|
||||
@ViewChanged
|
||||
public void viewChanged(ViewChangedEvent event) {
|
||||
boolean isCoordinator = isCoordinator();
|
||||
log.debug("View Changed: is coordinator: " + isCoordinator);
|
||||
|
||||
if (isCoordinator) {
|
||||
latch.countDown();
|
||||
latch = new CountDownLatch(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class WorkerResult implements Serializable {
|
||||
|
||||
private Integer segment;
|
||||
|
|
|
@ -33,14 +33,13 @@ public class OfflineUserSessionLoader implements SessionLoader {
|
|||
private static final Logger log = Logger.getLogger(OfflineUserSessionLoader.class);
|
||||
|
||||
@Override
|
||||
public void init(KeycloakSession session) {
|
||||
public void init(KeycloakSession session, int clusterStartupTime) {
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
int startTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
|
||||
|
||||
log.debugf("Clearing detached sessions from persistent storage and updating timestamps to %d", startTime);
|
||||
log.debugf("Clearing detached sessions from persistent storage and updating timestamps to %d", clusterStartupTime);
|
||||
|
||||
persister.clearDetachedUserSessions();
|
||||
persister.updateAllTimestamps(startTime);
|
||||
persister.updateAllTimestamps(clusterStartupTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
*/
|
||||
public interface SessionLoader extends Serializable {
|
||||
|
||||
void init(KeycloakSession session);
|
||||
void init(KeycloakSession session, int clusterStartupTime);
|
||||
|
||||
int getSessionsCount(KeycloakSession session);
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.sessions.infinispan.initializer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class TimeAwareInitializerState extends InitializerState {
|
||||
|
||||
private int clusterStartupTime;
|
||||
|
||||
public int getClusterStartupTime() {
|
||||
return clusterStartupTime;
|
||||
}
|
||||
|
||||
public void setClusterStartupTime(int clusterStartupTime) {
|
||||
this.clusterStartupTime = clusterStartupTime;
|
||||
}
|
||||
}
|
2
model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
Normal file → Executable file
2
model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
Normal file → Executable file
|
@ -16,3 +16,5 @@
|
|||
#
|
||||
|
||||
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
|
||||
org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
|
||||
org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory
|
|
@ -16,3 +16,5 @@
|
|||
#
|
||||
|
||||
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
|
||||
org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
|
||||
org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory
|
|
@ -0,0 +1,88 @@
|
|||
package org.keycloak.models.sessions.infinispan.initializer;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.infinispan.configuration.cache.VersioningScheme;
|
||||
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||
import org.infinispan.manager.DefaultCacheManager;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.infinispan.transaction.LockingMode;
|
||||
import org.infinispan.transaction.TransactionMode;
|
||||
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
||||
import org.infinispan.util.concurrent.IsolationLevel;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@Ignore
|
||||
public class ConcurrencyLockingTest {
|
||||
|
||||
@Test
|
||||
public void testLocking() throws Exception {
|
||||
final DefaultCacheManager cacheManager = getVersionedCacheManager();
|
||||
Cache<String, String> cache = cacheManager.getCache("COUNTER_CACHE");
|
||||
cache.put("key", "init");
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Cache<String, String> cache = cacheManager.getCache("COUNTER_CACHE");
|
||||
cache.startBatch();
|
||||
System.out.println("thread lock");
|
||||
cache.getAdvancedCache().lock("key");
|
||||
try {
|
||||
Thread.sleep(100000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
cache.endBatch(true);
|
||||
|
||||
}
|
||||
});
|
||||
Thread.sleep(10);
|
||||
cache.startBatch();
|
||||
cache.getAdvancedCache().lock("key");
|
||||
cache.put("key", "1234");
|
||||
System.out.println("after put");
|
||||
cache.endBatch(true);
|
||||
|
||||
Thread.sleep(1000000);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected DefaultCacheManager getVersionedCacheManager() {
|
||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||
|
||||
|
||||
boolean allowDuplicateJMXDomains = true;
|
||||
|
||||
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
|
||||
|
||||
final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
|
||||
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
|
||||
Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
|
||||
|
||||
ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
|
||||
counterConfigBuilder.invocationBatching().enable();
|
||||
counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
|
||||
counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
|
||||
Configuration counterCacheConfiguration = counterConfigBuilder.build();
|
||||
|
||||
cacheManager.defineConfiguration("COUNTER_CACHE", counterCacheConfiguration);
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
package org.keycloak.models.sessions.infinispan.initializer;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.commons.CacheException;
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.infinispan.configuration.cache.VersioningScheme;
|
||||
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||
import org.infinispan.manager.DefaultCacheManager;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.infinispan.transaction.TransactionMode;
|
||||
import org.infinispan.transaction.TransactionProtocol;
|
||||
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
||||
import org.infinispan.util.concurrent.IsolationLevel;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
|
||||
import javax.transaction.NotSupportedException;
|
||||
import javax.transaction.Status;
|
||||
import javax.transaction.SystemException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Unit tests to make sure our model caching concurrency model will work.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@Ignore
|
||||
public class ConcurrencyVersioningTest {
|
||||
|
||||
public static abstract class AbstractThread implements Runnable {
|
||||
EmbeddedCacheManager cacheManager;
|
||||
boolean success;
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
public AbstractThread(EmbeddedCacheManager cacheManager) {
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public CountDownLatch getLatch() {
|
||||
return latch;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RemoveThread extends AbstractThread {
|
||||
public RemoveThread(EmbeddedCacheManager cacheManager) {
|
||||
super(cacheManager);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||
try {
|
||||
startBatch(cache);
|
||||
cache.remove("key");
|
||||
//cache.getAdvancedCache().getTransactionManager().commit();
|
||||
endBatch(cache);
|
||||
success = true;
|
||||
} catch (Exception e) {
|
||||
success = false;
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class UpdateThread extends AbstractThread {
|
||||
public UpdateThread(EmbeddedCacheManager cacheManager) {
|
||||
super(cacheManager);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||
try {
|
||||
startBatch(cache);
|
||||
cache.putForExternalRead("key", "value2");
|
||||
//cache.getAdvancedCache().getTransactionManager().commit();
|
||||
endBatch(cache);
|
||||
success = true;
|
||||
} catch (Exception e) {
|
||||
success = false;
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that if remove executes before put, then put still succeeds.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testGetRemovePutOnNonExisting() throws Exception {
|
||||
final DefaultCacheManager cacheManager = getVersionedCacheManager();
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
RemoveThread removeThread = new RemoveThread(cacheManager);
|
||||
|
||||
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||
cache.remove("key");
|
||||
startBatch(cache);
|
||||
cache.get("key");
|
||||
executor.execute(removeThread);
|
||||
removeThread.getLatch().await();
|
||||
cache.putForExternalRead("key", "value1");
|
||||
endBatch(cache);
|
||||
Assert.assertEquals(cache.get("key"), "value1");
|
||||
Assert.assertTrue(removeThread.isSuccess());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testGetRemovePutOnExisting() throws Exception {
|
||||
final DefaultCacheManager cacheManager = getVersionedCacheManager();
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
RemoveThread removeThread = new RemoveThread(cacheManager);
|
||||
|
||||
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||
cache.put("key", "value0");
|
||||
startBatch(cache);
|
||||
cache.get("key");
|
||||
executor.execute(removeThread);
|
||||
removeThread.getLatch().await();
|
||||
cache.put("key", "value1");
|
||||
try {
|
||||
endBatch(cache);
|
||||
Assert.fail("Write skew should be detected");
|
||||
} catch (Exception e) {
|
||||
|
||||
|
||||
}
|
||||
Assert.assertNull(cache.get("key"));
|
||||
Assert.assertTrue(removeThread.isSuccess());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testGetRemovePutEternalOnExisting() throws Exception {
|
||||
final DefaultCacheManager cacheManager = getVersionedCacheManager();
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
RemoveThread removeThread = new RemoveThread(cacheManager);
|
||||
|
||||
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||
cache.put("key", "value0");
|
||||
startBatch(cache);
|
||||
cache.get("key");
|
||||
executor.execute(removeThread);
|
||||
cache.putForExternalRead("key", "value1");
|
||||
removeThread.getLatch().await();
|
||||
try {
|
||||
endBatch(cache);
|
||||
// Assert.fail("Write skew should be detected");
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
Assert.assertNull(cache.get("key"));
|
||||
Assert.assertTrue(removeThread.isSuccess());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutExternalRemoveOnExisting() throws Exception {
|
||||
final DefaultCacheManager cacheManager = getVersionedCacheManager();
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
RemoveThread removeThread = new RemoveThread(cacheManager);
|
||||
|
||||
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||
cache.put("key", "value0");
|
||||
startBatch(cache);
|
||||
cache.putForExternalRead("key", "value1");
|
||||
executor.execute(removeThread);
|
||||
removeThread.getLatch().await();
|
||||
try {
|
||||
endBatch(cache);
|
||||
// Assert.fail("Write skew should be detected");
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
Assert.assertNull(cache.get("key"));
|
||||
Assert.assertTrue(removeThread.isSuccess());
|
||||
}
|
||||
|
||||
|
||||
public static void startBatch(Cache<String, String> cache) {
|
||||
try {
|
||||
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
|
||||
System.out.println("begin");
|
||||
cache.getAdvancedCache().getTransactionManager().begin();
|
||||
}
|
||||
} catch (NotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (SystemException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void endBatch(Cache<String, String> cache) {
|
||||
boolean commit = true;
|
||||
try {
|
||||
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
|
||||
if (commit) {
|
||||
cache.getAdvancedCache().getTransactionManager().commit();
|
||||
|
||||
} else {
|
||||
cache.getAdvancedCache().getTransactionManager().rollback();
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected DefaultCacheManager getVersionedCacheManager() {
|
||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||
|
||||
|
||||
boolean clustered = false;
|
||||
boolean async = false;
|
||||
boolean allowDuplicateJMXDomains = true;
|
||||
|
||||
if (clustered) {
|
||||
gcb.transport().defaultTransport();
|
||||
}
|
||||
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
|
||||
|
||||
final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
|
||||
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
|
||||
invalidationConfigBuilder
|
||||
//.invocationBatching().enable()
|
||||
.transaction().transactionMode(TransactionMode.TRANSACTIONAL)
|
||||
.transaction().transactionManagerLookup(new DummyTransactionManagerLookup())
|
||||
.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
|
||||
|
||||
|
||||
//invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
|
||||
|
||||
if (clustered) {
|
||||
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
|
||||
}
|
||||
Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
|
||||
return cacheManager;
|
||||
}
|
||||
}
|
|
@ -633,8 +633,8 @@ public class ClientAdapter implements ClientModel {
|
|||
roleEntity.setClient(entity);
|
||||
roleEntity.setClientRole(true);
|
||||
roleEntity.setRealmId(realm.getId());
|
||||
em.persist(roleEntity);
|
||||
entity.getRoles().add(roleEntity);
|
||||
em.persist(roleEntity);
|
||||
em.flush();
|
||||
return new RoleAdapter(realm, em, roleEntity);
|
||||
}
|
||||
|
@ -667,12 +667,21 @@ public class ClientAdapter implements ClientModel {
|
|||
@Override
|
||||
public Set<RoleModel> getRoles() {
|
||||
Set<RoleModel> list = new HashSet<RoleModel>();
|
||||
/*
|
||||
Collection<RoleEntity> roles = entity.getRoles();
|
||||
if (roles == null) return list;
|
||||
for (RoleEntity entity : roles) {
|
||||
list.add(new RoleAdapter(realm, em, entity));
|
||||
}
|
||||
*/
|
||||
TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class);
|
||||
query.setParameter("client", entity);
|
||||
List<RoleEntity> roles = query.getResultList();
|
||||
for (RoleEntity roleEntity : roles) {
|
||||
list.add(new RoleAdapter(realm, em, roleEntity));
|
||||
}
|
||||
return list;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -124,9 +124,18 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
.setParameter("realm", realm).executeUpdate();
|
||||
num = em.createNamedQuery("deleteGroupsByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
|
||||
TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
|
||||
query.setParameter("realm", realm);
|
||||
List<ClientEntity> clients = query.getResultList();
|
||||
for (ClientEntity a : clients) {
|
||||
adapter.removeClient(a.getId());
|
||||
}
|
||||
/*
|
||||
for (ClientEntity a : new LinkedList<>(realm.getClients())) {
|
||||
adapter.removeClient(a.getId());
|
||||
}
|
||||
*/
|
||||
for (ClientTemplateEntity a : new LinkedList<>(realm.getClientTemplates())) {
|
||||
adapter.removeClientTemplate(a.getId());
|
||||
}
|
||||
|
|
|
@ -729,9 +729,11 @@ public class RealmAdapter implements RealmModel {
|
|||
|
||||
@Override
|
||||
public List<ClientModel> getClients() {
|
||||
List<ClientModel> list = new ArrayList<ClientModel>();
|
||||
if (realm.getClients() == null) return list;
|
||||
for (ClientEntity entity : realm.getClients()) {
|
||||
List<ClientModel> list = new LinkedList<>();
|
||||
TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
|
||||
query.setParameter("realm", realm);
|
||||
List<ClientEntity> clients = query.getResultList();
|
||||
for (ClientEntity entity : clients) {
|
||||
list.add(new ClientAdapter(this, em, session, entity));
|
||||
}
|
||||
return list;
|
||||
|
@ -794,11 +796,9 @@ public class RealmAdapter implements RealmModel {
|
|||
clientEntity = a;
|
||||
}
|
||||
}
|
||||
if (client == null) {
|
||||
return false;
|
||||
}
|
||||
em.remove(clientEntity);
|
||||
if (clientEntity == null) return false;
|
||||
em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
|
||||
em.remove(clientEntity);
|
||||
em.flush();
|
||||
|
||||
return true;
|
||||
|
@ -1017,6 +1017,7 @@ public class RealmAdapter implements RealmModel {
|
|||
entity.setFullSyncPeriod(model.getFullSyncPeriod());
|
||||
entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
|
||||
entity.setLastSync(model.getLastSync());
|
||||
entity.setRealm(realm);
|
||||
em.persist(entity);
|
||||
realm.getUserFederationProviders().add(entity);
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.keycloak.models.jpa.entities;
|
|||
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
|
@ -43,6 +45,7 @@ import javax.persistence.Table;
|
|||
public class AuthenticationExecutionEntity {
|
||||
@Id
|
||||
@Column(name="ID", length = 36)
|
||||
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
|
||||
protected String id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue