Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
6696c44dc0
176 changed files with 2818 additions and 2001 deletions
|
@ -89,7 +89,7 @@ class PathMatcher {
|
||||||
pathString = "/";
|
pathString = "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingUri.equals(targetUri)) {
|
if (matchingUri.equals(targetUri) || pathString.equals(targetUri)) {
|
||||||
cache.put(targetUri, entry);
|
cache.put(targetUri, entry);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,11 @@
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.samaxes.maven</groupId>
|
<groupId>com.samaxes.maven</groupId>
|
||||||
<artifactId>minify-maven-plugin</artifactId>
|
<artifactId>minify-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<jsEngine>CLOSURE</jsEngine>
|
||||||
|
<closureLanguageIn>ECMASCRIPT5</closureLanguageIn>
|
||||||
|
<closureCreateSourceMap>true</closureCreateSourceMap>
|
||||||
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>min-js</id>
|
<id>min-js</id>
|
||||||
|
|
|
@ -221,7 +221,7 @@
|
||||||
var callbackState = {
|
var callbackState = {
|
||||||
state: state,
|
state: state,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
redirectUri: encodeURIComponent(redirectUri),
|
redirectUri: encodeURIComponent(redirectUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options && options.prompt) {
|
if (options && options.prompt) {
|
||||||
|
@ -843,6 +843,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe.setAttribute('src', src );
|
iframe.setAttribute('src', src );
|
||||||
|
iframe.setAttribute('title', 'keycloak-session-iframe' );
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = 'none';
|
||||||
document.body.appendChild(iframe);
|
document.body.appendChild(iframe);
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,9 @@ import java.net.InetSocketAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,6 +68,7 @@ class ElytronHttpFacade implements OIDCHttpFacade {
|
||||||
private ElytronAccount account;
|
private ElytronAccount account;
|
||||||
private SecurityIdentity securityIdentity;
|
private SecurityIdentity securityIdentity;
|
||||||
private boolean restored;
|
private boolean restored;
|
||||||
|
private final Map<String, String> headers = new HashMap<>();
|
||||||
|
|
||||||
public ElytronHttpFacade(HttpServerRequest request, AdapterDeploymentContext deploymentContext, CallbackHandler handler) {
|
public ElytronHttpFacade(HttpServerRequest request, AdapterDeploymentContext deploymentContext, CallbackHandler handler) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
|
@ -261,6 +264,7 @@ class ElytronHttpFacade implements OIDCHttpFacade {
|
||||||
@Override
|
@Override
|
||||||
public Response getResponse() {
|
public Response getResponse() {
|
||||||
return new Response() {
|
return new Response() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setStatus(final int status) {
|
public void setStatus(final int status) {
|
||||||
responseConsumer = responseConsumer.andThen(response -> response.setStatusCode(status));
|
responseConsumer = responseConsumer.andThen(response -> response.setStatusCode(status));
|
||||||
|
@ -268,7 +272,17 @@ class ElytronHttpFacade implements OIDCHttpFacade {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addHeader(final String name, final String value) {
|
public void addHeader(final String name, final String value) {
|
||||||
responseConsumer = responseConsumer.andThen(response -> response.addResponseHeader(name, value));
|
headers.put(name, value);
|
||||||
|
responseConsumer = responseConsumer.andThen(new Consumer<HttpServerResponse>() {
|
||||||
|
@Override
|
||||||
|
public void accept(HttpServerResponse response) {
|
||||||
|
String latestValue = headers.get(name);
|
||||||
|
|
||||||
|
if (latestValue.equals(value)) {
|
||||||
|
response.addResponseHeader(name, latestValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.keycloak.adapters.elytron;
|
package org.keycloak.adapters.elytron;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
|
@ -54,7 +55,7 @@ public class KeycloakSecurityRealm implements SecurityRealm {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
|
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
|
||||||
return SupportLevel.UNSUPPORTED;
|
return SupportLevel.UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ public class KeycloakSecurityRealm implements SecurityRealm {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
|
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
|
||||||
return SupportLevel.UNSUPPORTED;
|
return SupportLevel.UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,10 @@
|
||||||
<artifactId>wildfly-web-common</artifactId>
|
<artifactId>wildfly-web-common</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.wildfly.security</groupId>
|
||||||
|
<artifactId>wildfly-elytron</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging-annotations</artifactId>
|
<artifactId>jboss-logging-annotations</artifactId>
|
||||||
|
@ -101,5 +105,9 @@
|
||||||
<artifactId>keycloak-wildfly-adapter</artifactId>
|
<artifactId>keycloak-wildfly-adapter</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-wildfly-elytron-oidc-adapter</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -17,41 +17,32 @@
|
||||||
|
|
||||||
package org.keycloak.subsystem.adapter.extension;
|
package org.keycloak.subsystem.adapter.extension;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.jboss.as.controller.AbstractAddStepHandler;
|
import org.jboss.as.controller.AbstractAddStepHandler;
|
||||||
import org.jboss.as.controller.AttributeDefinition;
|
|
||||||
import org.jboss.as.controller.OperationContext;
|
import org.jboss.as.controller.OperationContext;
|
||||||
import org.jboss.as.controller.OperationFailedException;
|
import org.jboss.as.controller.OperationFailedException;
|
||||||
|
import org.jboss.as.controller.SimpleAttributeDefinition;
|
||||||
|
import org.jboss.as.controller.capability.RuntimeCapability;
|
||||||
import org.jboss.dmr.ModelNode;
|
import org.jboss.dmr.ModelNode;
|
||||||
|
|
||||||
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
|
|
||||||
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a deployment to a realm.
|
* Add a deployment to a realm.
|
||||||
*
|
*
|
||||||
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
||||||
*/
|
*/
|
||||||
public final class SecureDeploymentAddHandler extends AbstractAddStepHandler {
|
abstract class AbstractAdapterConfigurationAddHandler extends AbstractAddStepHandler {
|
||||||
|
|
||||||
public static SecureDeploymentAddHandler INSTANCE = new SecureDeploymentAddHandler();
|
private final boolean elytronEnabled;
|
||||||
|
|
||||||
private SecureDeploymentAddHandler() {}
|
AbstractAdapterConfigurationAddHandler(RuntimeCapability<Void> runtimeCapability, List<SimpleAttributeDefinition> attributes) {
|
||||||
|
super(runtimeCapability, attributes);
|
||||||
@Override
|
elytronEnabled = runtimeCapability != null;
|
||||||
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
|
|
||||||
// TODO: localize exception. get id number
|
|
||||||
if (!operation.get(OP).asString().equals(ADD)) {
|
|
||||||
throw new OperationFailedException("Unexpected operation for add secure deployment. operation=" + operation.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (AttributeDefinition attr : SecureDeploymentDefinition.ALL_ATTRIBUTES) {
|
|
||||||
attr.validateAndSet(operation, model);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||||
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
|
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
|
||||||
ckService.addSecureDeployment(operation, context.resolveExpressions(model));
|
ckService.addSecureDeployment(operation, context.resolveExpressions(model), elytronEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* JBoss, Home of Professional Open Source.
|
||||||
|
* Copyright 2016 Red Hat, Inc., and individual contributors
|
||||||
|
* as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.subsystem.adapter.extension;
|
||||||
|
|
||||||
|
import org.jboss.as.controller.AttributeDefinition;
|
||||||
|
import org.jboss.as.controller.PathElement;
|
||||||
|
import org.jboss.as.controller.SimpleAttributeDefinition;
|
||||||
|
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
|
||||||
|
import org.jboss.as.controller.SimpleResourceDefinition;
|
||||||
|
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
|
||||||
|
import org.jboss.as.controller.operations.validation.IntRangeValidator;
|
||||||
|
import org.jboss.as.controller.operations.validation.StringLengthValidator;
|
||||||
|
import org.jboss.as.controller.registry.ManagementResourceRegistration;
|
||||||
|
import org.jboss.dmr.ModelNode;
|
||||||
|
import org.jboss.dmr.ModelType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines attributes and operations for a secure-deployment.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
abstract class AbstractAdapterConfigurationDefinition extends SimpleResourceDefinition {
|
||||||
|
|
||||||
|
protected static final SimpleAttributeDefinition REALM =
|
||||||
|
new SimpleAttributeDefinitionBuilder("realm", ModelType.STRING, true)
|
||||||
|
.setXmlName("realm")
|
||||||
|
.setAllowExpression(true)
|
||||||
|
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||||
|
.build();
|
||||||
|
protected static final SimpleAttributeDefinition RESOURCE =
|
||||||
|
new SimpleAttributeDefinitionBuilder("resource", ModelType.STRING, true)
|
||||||
|
.setXmlName("resource")
|
||||||
|
.setAllowExpression(true)
|
||||||
|
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||||
|
.build();
|
||||||
|
protected static final SimpleAttributeDefinition USE_RESOURCE_ROLE_MAPPINGS =
|
||||||
|
new SimpleAttributeDefinitionBuilder("use-resource-role-mappings", ModelType.BOOLEAN, true)
|
||||||
|
.setXmlName("use-resource-role-mappings")
|
||||||
|
.setAllowExpression(true)
|
||||||
|
.setDefaultValue(new ModelNode(false))
|
||||||
|
.build();
|
||||||
|
protected static final SimpleAttributeDefinition BEARER_ONLY =
|
||||||
|
new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true)
|
||||||
|
.setXmlName("bearer-only")
|
||||||
|
.setAllowExpression(true)
|
||||||
|
.setDefaultValue(new ModelNode(false))
|
||||||
|
.build();
|
||||||
|
protected static final SimpleAttributeDefinition ENABLE_BASIC_AUTH =
|
||||||
|
new SimpleAttributeDefinitionBuilder("enable-basic-auth", ModelType.BOOLEAN, true)
|
||||||
|
.setXmlName("enable-basic-auth")
|
||||||
|
.setAllowExpression(true)
|
||||||
|
.setDefaultValue(new ModelNode(false))
|
||||||
|
.build();
|
||||||
|
protected static final SimpleAttributeDefinition PUBLIC_CLIENT =
|
||||||
|
new SimpleAttributeDefinitionBuilder("public-client", ModelType.BOOLEAN, true)
|
||||||
|
.setXmlName("public-client")
|
||||||
|
.setAllowExpression(true)
|
||||||
|
.setDefaultValue(new ModelNode(false))
|
||||||
|
.build();
|
||||||
|
protected static final SimpleAttributeDefinition TURN_OFF_CHANGE_SESSION =
|
||||||
|
new SimpleAttributeDefinitionBuilder("turn-off-change-session-id-on-login", ModelType.BOOLEAN, true)
|
||||||
|
.setXmlName("turn-off-change-session-id-on-login")
|
||||||
|
.setAllowExpression(true)
|
||||||
|
.setDefaultValue(new ModelNode(false))
|
||||||
|
.build();
|
||||||
|
protected static final SimpleAttributeDefinition TOKEN_MINIMUM_TIME_TO_LIVE =
|
||||||
|
new SimpleAttributeDefinitionBuilder("token-minimum-time-to-live", ModelType.INT, true)
|
||||||
|
.setXmlName("token-minimum-time-to-live")
|
||||||
|
.setValidator(new IntRangeValidator(-1, true))
|
||||||
|
.setAllowExpression(true)
|
||||||
|
.build();
|
||||||
|
protected static final SimpleAttributeDefinition MIN_TIME_BETWEEN_JWKS_REQUESTS =
|
||||||
|
new SimpleAttributeDefinitionBuilder("min-time-between-jwks-requests", ModelType.INT, true)
|
||||||
|
.setXmlName("min-time-between-jwks-requests")
|
||||||
|
.setValidator(new IntRangeValidator(-1, true))
|
||||||
|
.setAllowExpression(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(REALM);
|
||||||
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE);
|
||||||
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS);
|
||||||
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
|
||||||
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
|
||||||
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
|
||||||
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
|
||||||
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(TOKEN_MINIMUM_TIME_TO_LIVE);
|
||||||
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(MIN_TIME_BETWEEN_JWKS_REQUESTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES);
|
||||||
|
ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Map<String, SimpleAttributeDefinition> XML_ATTRIBUTES = new HashMap<String, SimpleAttributeDefinition>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
|
||||||
|
XML_ATTRIBUTES.put(def.getXmlName(), def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
|
||||||
|
static {
|
||||||
|
for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
|
||||||
|
DEFINITION_LOOKUP.put(def.getXmlName(), def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AbstractAdapterConfigurationWriteAttributeHandler attrWriteHandler;
|
||||||
|
private final List<SimpleAttributeDefinition> attributes;
|
||||||
|
|
||||||
|
protected AbstractAdapterConfigurationDefinition(String name, List<SimpleAttributeDefinition> attributes, AbstractAdapterConfigurationAddHandler addHandler, AbstractAdapterConfigurationRemoveHandler removeHandler, AbstractAdapterConfigurationWriteAttributeHandler attrWriteHandler) {
|
||||||
|
super(PathElement.pathElement(name),
|
||||||
|
KeycloakExtension.getResourceDescriptionResolver(name),
|
||||||
|
addHandler,
|
||||||
|
removeHandler);
|
||||||
|
this.attributes = attributes;
|
||||||
|
this.attrWriteHandler = attrWriteHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
|
||||||
|
super.registerOperations(resourceRegistration);
|
||||||
|
resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
|
||||||
|
super.registerAttributes(resourceRegistration);
|
||||||
|
for (AttributeDefinition attrDef : this.attributes) {
|
||||||
|
resourceRegistration.registerReadWriteAttribute(attrDef, null, this.attrWriteHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SimpleAttributeDefinition lookup(String name) {
|
||||||
|
return DEFINITION_LOOKUP.get(name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,11 +27,7 @@ import org.jboss.dmr.ModelNode;
|
||||||
*
|
*
|
||||||
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
||||||
*/
|
*/
|
||||||
public final class SecureDeploymentRemoveHandler extends AbstractRemoveStepHandler {
|
abstract class AbstractAdapterConfigurationRemoveHandler extends AbstractRemoveStepHandler {
|
||||||
|
|
||||||
public static SecureDeploymentRemoveHandler INSTANCE = new SecureDeploymentRemoveHandler();
|
|
||||||
|
|
||||||
private SecureDeploymentRemoveHandler() {}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
|
@ -31,14 +31,10 @@ import java.util.List;
|
||||||
*
|
*
|
||||||
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
||||||
*/
|
*/
|
||||||
public class SecureDeploymentWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
|
abstract class AbstractAdapterConfigurationWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
|
||||||
|
|
||||||
public SecureDeploymentWriteAttributeHandler(List<SimpleAttributeDefinition> definitions) {
|
AbstractAdapterConfigurationWriteAttributeHandler(List<SimpleAttributeDefinition> definitions) {
|
||||||
this(definitions.toArray(new AttributeDefinition[definitions.size()]));
|
super(definitions.toArray(new AttributeDefinition[definitions.size()]));
|
||||||
}
|
|
||||||
|
|
||||||
public SecureDeploymentWriteAttributeHandler(AttributeDefinition... definitions) {
|
|
||||||
super(definitions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -25,7 +25,9 @@ import org.jboss.as.web.common.WarMetaData;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.metadata.javaee.spec.ParamValueMetaData;
|
import org.jboss.metadata.javaee.spec.ParamValueMetaData;
|
||||||
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
||||||
|
import org.jboss.metadata.web.spec.ListenerMetaData;
|
||||||
import org.jboss.metadata.web.spec.LoginConfigMetaData;
|
import org.jboss.metadata.web.spec.LoginConfigMetaData;
|
||||||
|
import org.keycloak.adapters.elytron.KeycloakConfigurationServletListener;
|
||||||
import org.keycloak.subsystem.adapter.logging.KeycloakLogger;
|
import org.keycloak.subsystem.adapter.logging.KeycloakLogger;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -69,6 +71,9 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
||||||
KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
|
KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
|
||||||
if (service.isSecureDeployment(deploymentUnit) && service.isDeploymentConfigured(deploymentUnit)) {
|
if (service.isSecureDeployment(deploymentUnit) && service.isDeploymentConfigured(deploymentUnit)) {
|
||||||
addKeycloakAuthData(phaseContext, service);
|
addKeycloakAuthData(phaseContext, service);
|
||||||
|
} else if (service.isElytronEnabled(deploymentUnit)) {
|
||||||
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
|
addConfigurationListener(warMetaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
|
// FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
|
||||||
|
@ -99,6 +104,10 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
||||||
loginConfig.setAuthMethod("KEYCLOAK");
|
loginConfig.setAuthMethod("KEYCLOAK");
|
||||||
loginConfig.setRealmName(service.getRealmName(deploymentUnit));
|
loginConfig.setRealmName(service.getRealmName(deploymentUnit));
|
||||||
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName());
|
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName());
|
||||||
|
|
||||||
|
if (service.isElytronEnabled(deploymentUnit)) {
|
||||||
|
addConfigurationListener(warMetaData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addJSONData(String json, WarMetaData warMetaData) {
|
private void addJSONData(String json, WarMetaData warMetaData) {
|
||||||
|
@ -121,6 +130,31 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
||||||
webMetaData.setContextParams(contextParams);
|
webMetaData.setContextParams(contextParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addConfigurationListener(WarMetaData warMetaData) {
|
||||||
|
if (warMetaData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||||
|
if (webMetaData == null) {
|
||||||
|
webMetaData = new JBossWebMetaData();
|
||||||
|
warMetaData.setMergedJBossWebMetaData(webMetaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
|
||||||
|
if (loginConfig == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!loginConfig.getAuthMethod().equals("KEYCLOAK")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ListenerMetaData listenerMetaData = new ListenerMetaData();
|
||||||
|
|
||||||
|
listenerMetaData.setListenerClass(KeycloakConfigurationServletListener.class.getName());
|
||||||
|
|
||||||
|
webMetaData.getListeners().add(listenerMetaData);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void undeploy(DeploymentUnit du) {
|
public void undeploy(DeploymentUnit du) {
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,11 @@ import org.jboss.dmr.ModelNode;
|
||||||
import org.jboss.dmr.Property;
|
import org.jboss.dmr.Property;
|
||||||
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
|
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
|
||||||
|
|
||||||
|
@ -50,6 +53,7 @@ public final class KeycloakAdapterConfigService {
|
||||||
|
|
||||||
// keycloak-secured deployments
|
// keycloak-secured deployments
|
||||||
private final Map<String, ModelNode> secureDeployments = new HashMap<String, ModelNode>();
|
private final Map<String, ModelNode> secureDeployments = new HashMap<String, ModelNode>();
|
||||||
|
private final Set<String> elytronEnabledDeployments = new HashSet<>();
|
||||||
|
|
||||||
|
|
||||||
private KeycloakAdapterConfigService() {
|
private KeycloakAdapterConfigService() {
|
||||||
|
@ -68,9 +72,13 @@ public final class KeycloakAdapterConfigService {
|
||||||
this.realms.remove(realmNameFromOp(operation));
|
this.realms.remove(realmNameFromOp(operation));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSecureDeployment(ModelNode operation, ModelNode model) {
|
public void addSecureDeployment(ModelNode operation, ModelNode model, boolean elytronEnabled) {
|
||||||
ModelNode deployment = model.clone();
|
ModelNode deployment = model.clone();
|
||||||
this.secureDeployments.put(deploymentNameFromOp(operation), deployment);
|
String name = deploymentNameFromOp(operation);
|
||||||
|
this.secureDeployments.put(name, deployment);
|
||||||
|
if (elytronEnabled) {
|
||||||
|
elytronEnabledDeployments.add(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateSecureDeployment(ModelNode operation, String attrName, ModelNode resolvedValue) {
|
public void updateSecureDeployment(ModelNode operation, String attrName, ModelNode resolvedValue) {
|
||||||
|
@ -79,7 +87,9 @@ public final class KeycloakAdapterConfigService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeSecureDeployment(ModelNode operation) {
|
public void removeSecureDeployment(ModelNode operation) {
|
||||||
this.secureDeployments.remove(deploymentNameFromOp(operation));
|
String name = deploymentNameFromOp(operation);
|
||||||
|
this.secureDeployments.remove(name);
|
||||||
|
elytronEnabledDeployments.remove(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCredential(ModelNode operation, ModelNode model) {
|
public void addCredential(ModelNode operation, ModelNode model) {
|
||||||
|
@ -187,7 +197,19 @@ public final class KeycloakAdapterConfigService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String deploymentNameFromOp(ModelNode operation) {
|
private String deploymentNameFromOp(ModelNode operation) {
|
||||||
return valueFromOpAddress(SecureDeploymentDefinition.TAG_NAME, operation);
|
String deploymentName = valueFromOpAddress(SecureDeploymentDefinition.TAG_NAME, operation);
|
||||||
|
|
||||||
|
if (deploymentName == null) {
|
||||||
|
deploymentName = valueFromOpAddress(KeycloakHttpServerAuthenticationMechanismFactoryDefinition.TAG_NAME, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deploymentName == null) {
|
||||||
|
deploymentName = valueFromOpAddress(SecureServerDefinition.TAG_NAME, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deploymentName == null) throw new RuntimeException("Can't find deployment name in address " + operation);
|
||||||
|
|
||||||
|
return deploymentName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String credentialNameFromOp(ModelNode operation) {
|
private String credentialNameFromOp(ModelNode operation) {
|
||||||
|
@ -199,9 +221,7 @@ public final class KeycloakAdapterConfigService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String valueFromOpAddress(String addrElement, ModelNode operation) {
|
private String valueFromOpAddress(String addrElement, ModelNode operation) {
|
||||||
String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement);
|
return getValueOfAddrElement(operation.get(ADDRESS), addrElement);
|
||||||
if (deploymentName == null) throw new RuntimeException("Can't find '" + addrElement + "' in address " + operation.toString());
|
|
||||||
return deploymentName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getValueOfAddrElement(ModelNode address, String elementName) {
|
private String getValueOfAddrElement(ModelNode address, String elementName) {
|
||||||
|
@ -241,8 +261,22 @@ public final class KeycloakAdapterConfigService {
|
||||||
return json.toJSONString(true);
|
return json.toJSONString(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getJSON(String deploymentName) {
|
||||||
|
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||||
|
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||||
|
ModelNode realm = this.realms.get(realmName);
|
||||||
|
|
||||||
|
ModelNode json = new ModelNode();
|
||||||
|
json.get(RealmDefinition.TAG_NAME).set(realmName);
|
||||||
|
|
||||||
|
// Realm values set first. Some can be overridden by deployment values.
|
||||||
|
if (realm != null) setJSONValues(json, realm);
|
||||||
|
setJSONValues(json, deployment);
|
||||||
|
return json.toJSONString(true);
|
||||||
|
}
|
||||||
|
|
||||||
private void setJSONValues(ModelNode json, ModelNode values) {
|
private void setJSONValues(ModelNode json, ModelNode values) {
|
||||||
for (Property prop : values.asPropertyList()) {
|
for (Property prop : new ArrayList<>(values.asPropertyList())) {
|
||||||
String name = prop.getName();
|
String name = prop.getName();
|
||||||
ModelNode value = prop.getValue();
|
ModelNode value = prop.getValue();
|
||||||
if (value.isDefined()) {
|
if (value.isDefined()) {
|
||||||
|
@ -258,6 +292,10 @@ public final class KeycloakAdapterConfigService {
|
||||||
return this.secureDeployments.containsKey(deploymentName);
|
return this.secureDeployments.containsKey(deploymentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isElytronEnabled(DeploymentUnit deploymentUnit) {
|
||||||
|
return elytronEnabledDeployments.contains(preferredDeploymentName(deploymentUnit));
|
||||||
|
}
|
||||||
|
|
||||||
private ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) {
|
private ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) {
|
||||||
String deploymentName = preferredDeploymentName(deploymentUnit);
|
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||||
return this.secureDeployments.containsKey(deploymentName)
|
return this.secureDeployments.containsKey(deploymentName)
|
||||||
|
|
|
@ -29,13 +29,14 @@ import org.jboss.modules.ModuleLoader;
|
||||||
*/
|
*/
|
||||||
public class KeycloakDependencyProcessorWildFly extends KeycloakDependencyProcessor {
|
public class KeycloakDependencyProcessorWildFly extends KeycloakDependencyProcessor {
|
||||||
|
|
||||||
|
private static final ModuleIdentifier KEYCLOAK_ELYTRON_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-wildfly-elytron-oidc-adapter");
|
||||||
private static final ModuleIdentifier KEYCLOAK_WILDFLY_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-wildfly-adapter");
|
private static final ModuleIdentifier KEYCLOAK_WILDFLY_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-wildfly-adapter");
|
||||||
private static final ModuleIdentifier KEYCLOAK_UNDERTOW_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-undertow-adapter");
|
private static final ModuleIdentifier KEYCLOAK_UNDERTOW_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-undertow-adapter");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
|
protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
|
||||||
// ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
|
|
||||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_WILDFLY_ADAPTER, false, false, true, false));
|
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_WILDFLY_ADAPTER, false, false, true, false));
|
||||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_UNDERTOW_ADAPTER, false, false, false, false));
|
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_UNDERTOW_ADAPTER, false, false, false, false));
|
||||||
|
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_ELYTRON_ADAPTER, true, false, false, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,9 @@ public class KeycloakExtension implements Extension {
|
||||||
private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition();
|
private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition();
|
||||||
static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
|
static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
|
||||||
static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
|
static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
|
||||||
|
static final SecureServerDefinition SECURE_SERVER_DEFINITION = new SecureServerDefinition();
|
||||||
static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
|
static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
|
||||||
static final RedirecRewritetRuleDefinition REDIRECT_RULE_DEFINITON = new RedirecRewritetRuleDefinition();
|
static final RedirecRewritetRuleDefinition REDIRECT_RULE_DEFINITON = new RedirecRewritetRuleDefinition();
|
||||||
|
|
||||||
public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
|
public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
|
||||||
StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
|
StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
|
||||||
|
@ -80,6 +81,10 @@ public class KeycloakExtension implements Extension {
|
||||||
secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
|
secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
|
||||||
secureDeploymentRegistration.registerSubModel(REDIRECT_RULE_DEFINITON);
|
secureDeploymentRegistration.registerSubModel(REDIRECT_RULE_DEFINITON);
|
||||||
|
|
||||||
|
ManagementResourceRegistration secureServerRegistration = registration.registerSubModel(SECURE_SERVER_DEFINITION);
|
||||||
|
secureServerRegistration.registerSubModel(CREDENTIAL_DEFINITION);
|
||||||
|
secureServerRegistration.registerSubModel(REDIRECT_RULE_DEFINITON);
|
||||||
|
|
||||||
subsystem.registerXMLElementWriter(PARSER);
|
subsystem.registerXMLElementWriter(PARSER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* JBoss, Home of Professional Open Source.
|
||||||
|
* Copyright 2016 Red Hat, Inc., and individual contributors
|
||||||
|
* as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.subsystem.adapter.extension;
|
||||||
|
|
||||||
|
import org.jboss.msc.service.Service;
|
||||||
|
import org.jboss.msc.service.StartContext;
|
||||||
|
import org.jboss.msc.service.StartException;
|
||||||
|
import org.jboss.msc.service.StopContext;
|
||||||
|
import org.jboss.msc.value.InjectedValue;
|
||||||
|
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||||
|
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||||
|
import org.keycloak.adapters.elytron.KeycloakHttpServerAuthenticationMechanismFactory;
|
||||||
|
import org.wildfly.security.auth.server.SecurityDomain;
|
||||||
|
import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
|
||||||
|
import org.wildfly.security.http.util.SetMechanismInformationMechanismFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public class KeycloakHttpAuthenticationFactoryService implements Service<HttpServerAuthenticationMechanismFactory> {
|
||||||
|
|
||||||
|
private final String factoryName;
|
||||||
|
private HttpServerAuthenticationMechanismFactory httpAuthenticationFactory;
|
||||||
|
|
||||||
|
public KeycloakHttpAuthenticationFactoryService(String factoryName) {
|
||||||
|
this.factoryName = factoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(StartContext context) throws StartException {
|
||||||
|
KeycloakAdapterConfigService adapterConfigService = KeycloakAdapterConfigService.getInstance();
|
||||||
|
String config = adapterConfigService.getJSON(this.factoryName);
|
||||||
|
this.httpAuthenticationFactory = new KeycloakHttpServerAuthenticationMechanismFactory(createDeploymentContext(config.getBytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop(StopContext context) {
|
||||||
|
this.httpAuthenticationFactory = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpServerAuthenticationMechanismFactory getValue() throws IllegalStateException, IllegalArgumentException {
|
||||||
|
return new SetMechanismInformationMechanismFactory(this.httpAuthenticationFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdapterDeploymentContext createDeploymentContext(byte[] config) {
|
||||||
|
return new AdapterDeploymentContext(KeycloakDeploymentBuilder.build(new ByteArrayInputStream(config)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* JBoss, Home of Professional Open Source.
|
||||||
|
* Copyright 2016 Red Hat, Inc., and individual contributors
|
||||||
|
* as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.subsystem.adapter.extension;
|
||||||
|
|
||||||
|
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
|
||||||
|
import static org.keycloak.subsystem.adapter.extension.KeycloakHttpServerAuthenticationMechanismFactoryDefinition.KeycloakHttpServerAuthenticationMechanismFactoryAddHandler.HTTP_SERVER_AUTHENTICATION_CAPABILITY;
|
||||||
|
|
||||||
|
import org.jboss.as.controller.OperationContext;
|
||||||
|
import org.jboss.as.controller.OperationFailedException;
|
||||||
|
import org.jboss.as.controller.PathAddress;
|
||||||
|
import org.jboss.as.controller.SimpleResourceDefinition;
|
||||||
|
import org.jboss.as.controller.capability.RuntimeCapability;
|
||||||
|
import org.jboss.dmr.ModelNode;
|
||||||
|
import org.jboss.msc.service.ServiceController;
|
||||||
|
import org.jboss.msc.service.ServiceName;
|
||||||
|
import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link SimpleResourceDefinition} that can be used to configure a {@link org.keycloak.adapters.elytron.KeycloakHttpServerAuthenticationMechanismFactory}
|
||||||
|
* and expose it as a capability for other subsystems.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
class KeycloakHttpServerAuthenticationMechanismFactoryDefinition extends AbstractAdapterConfigurationDefinition {
|
||||||
|
|
||||||
|
static final String TAG_NAME = "http-server-mechanism-factory";
|
||||||
|
|
||||||
|
KeycloakHttpServerAuthenticationMechanismFactoryDefinition() {
|
||||||
|
this(TAG_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeycloakHttpServerAuthenticationMechanismFactoryDefinition(String tagName) {
|
||||||
|
super(tagName, ALL_ATTRIBUTES, new KeycloakHttpServerAuthenticationMechanismFactoryAddHandler(), new KeycloakHttpServerAuthenticationMechanismFactoryRemoveHandler(), new KeycloakHttpServerAuthenticationMechanismFactoryWriteHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link AbstractAdapterConfigurationAddHandler} that exposes a {@link KeycloakHttpServerAuthenticationMechanismFactoryDefinition}
|
||||||
|
* as a capability through the installation of a {@link KeycloakHttpAuthenticationFactoryService}.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
static final class KeycloakHttpServerAuthenticationMechanismFactoryAddHandler extends AbstractAdapterConfigurationAddHandler {
|
||||||
|
|
||||||
|
static final String HTTP_SERVER_AUTHENTICATION_CAPABILITY = "org.wildfly.security.http-server-mechanism-factory";
|
||||||
|
static final RuntimeCapability<Void> HTTP_SERVER_AUTHENTICATION_RUNTIME_CAPABILITY = RuntimeCapability
|
||||||
|
.Builder.of(HTTP_SERVER_AUTHENTICATION_CAPABILITY, true, HttpServerAuthenticationMechanismFactory.class)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
KeycloakHttpServerAuthenticationMechanismFactoryAddHandler() {
|
||||||
|
super(HTTP_SERVER_AUTHENTICATION_RUNTIME_CAPABILITY, ALL_ATTRIBUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||||
|
super.performRuntime(context, operation, model);
|
||||||
|
installCapability(context, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void installCapability(OperationContext context, ModelNode operation) {
|
||||||
|
PathAddress pathAddress = PathAddress.pathAddress(operation.get(OP_ADDR));
|
||||||
|
String factoryName = pathAddress.getLastElement().getValue();
|
||||||
|
ServiceName serviceName = context.getCapabilityServiceName(HTTP_SERVER_AUTHENTICATION_CAPABILITY, factoryName, HttpServerAuthenticationMechanismFactory.class);
|
||||||
|
KeycloakHttpAuthenticationFactoryService service = new KeycloakHttpAuthenticationFactoryService(factoryName);
|
||||||
|
context.getServiceTarget().addService(serviceName, service).setInitialMode(ServiceController.Mode.ACTIVE).install();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link AbstractAdapterConfigurationRemoveHandler} that handles the removal of {@link KeycloakHttpServerAuthenticationMechanismFactoryDefinition}.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
static final class KeycloakHttpServerAuthenticationMechanismFactoryRemoveHandler extends AbstractAdapterConfigurationRemoveHandler {
|
||||||
|
@Override
|
||||||
|
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||||
|
super.performRuntime(context, operation, model);
|
||||||
|
PathAddress pathAddress = PathAddress.pathAddress(operation.get(OP_ADDR));
|
||||||
|
String factoryName = pathAddress.getLastElement().getValue();
|
||||||
|
ServiceName serviceName = context.getCapabilityServiceName(HTTP_SERVER_AUTHENTICATION_CAPABILITY, factoryName, HttpServerAuthenticationMechanismFactory.class);
|
||||||
|
|
||||||
|
context.removeService(serviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void recoverServices(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||||
|
super.recoverServices(context, operation, model);
|
||||||
|
KeycloakHttpServerAuthenticationMechanismFactoryAddHandler.installCapability(context, operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link AbstractAdapterConfigurationWriteAttributeHandler} that updates attributes on a {@link KeycloakHttpServerAuthenticationMechanismFactoryDefinition}.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
static final class KeycloakHttpServerAuthenticationMechanismFactoryWriteHandler extends AbstractAdapterConfigurationWriteAttributeHandler {
|
||||||
|
KeycloakHttpServerAuthenticationMechanismFactoryWriteHandler() {
|
||||||
|
super(ALL_ATTRIBUTES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,6 +62,9 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||||
else if (reader.getLocalName().equals(SecureDeploymentDefinition.TAG_NAME)) {
|
else if (reader.getLocalName().equals(SecureDeploymentDefinition.TAG_NAME)) {
|
||||||
readDeployment(reader, list);
|
readDeployment(reader, list);
|
||||||
}
|
}
|
||||||
|
else if (reader.getLocalName().equals(SecureServerDefinition.TAG_NAME)) {
|
||||||
|
readSecureServer(reader, list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,27 +92,35 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readDeployment(XMLExtendedStreamReader reader, List<ModelNode> resourcesToAdd) throws XMLStreamException {
|
private void readDeployment(XMLExtendedStreamReader reader, List<ModelNode> resourcesToAdd) throws XMLStreamException {
|
||||||
|
readSecureResource(KeycloakExtension.SECURE_DEPLOYMENT_DEFINITION.TAG_NAME, KeycloakExtension.SECURE_DEPLOYMENT_DEFINITION, reader, resourcesToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readSecureServer(XMLExtendedStreamReader reader, List<ModelNode> resourcesToAdd) throws XMLStreamException {
|
||||||
|
readSecureResource(KeycloakExtension.SECURE_SERVER_DEFINITION.TAG_NAME, KeycloakExtension.SECURE_SERVER_DEFINITION, reader, resourcesToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readSecureResource(String tagName, AbstractAdapterConfigurationDefinition resource, XMLExtendedStreamReader reader, List<ModelNode> resourcesToAdd) throws XMLStreamException {
|
||||||
String name = readNameAttribute(reader);
|
String name = readNameAttribute(reader);
|
||||||
ModelNode addSecureDeployment = new ModelNode();
|
ModelNode addSecureDeployment = new ModelNode();
|
||||||
addSecureDeployment.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
|
addSecureDeployment.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
|
||||||
PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
|
PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
|
||||||
PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name));
|
PathElement.pathElement(tagName, name));
|
||||||
addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
|
addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
|
||||||
List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>();
|
List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>();
|
||||||
List<ModelNode> redirectRulesToAdd = new ArrayList<ModelNode>();
|
List<ModelNode> redirectRulesToAdd = new ArrayList<ModelNode>();
|
||||||
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
|
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
|
||||||
String tagName = reader.getLocalName();
|
String localName = reader.getLocalName();
|
||||||
if (tagName.equals(CredentialDefinition.TAG_NAME)) {
|
if (localName.equals(CredentialDefinition.TAG_NAME)) {
|
||||||
readCredential(reader, addr, credentialsToAdd);
|
readCredential(reader, addr, credentialsToAdd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (tagName.equals(RedirecRewritetRuleDefinition.TAG_NAME)) {
|
if (localName.equals(RedirecRewritetRuleDefinition.TAG_NAME)) {
|
||||||
readRewriteRule(reader, addr, redirectRulesToAdd);
|
readRewriteRule(reader, addr, redirectRulesToAdd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName);
|
SimpleAttributeDefinition def = resource.lookup(localName);
|
||||||
if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName);
|
if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + localName);
|
||||||
def.parseAndSetParameter(reader.getElementText(), addSecureDeployment, reader);
|
def.parseAndSetParameter(reader.getElementText(), addSecureDeployment, reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,6 +247,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||||
context.startSubsystemElement(KeycloakExtension.NAMESPACE, false);
|
context.startSubsystemElement(KeycloakExtension.NAMESPACE, false);
|
||||||
writeRealms(writer, context);
|
writeRealms(writer, context);
|
||||||
writeSecureDeployments(writer, context);
|
writeSecureDeployments(writer, context);
|
||||||
|
writeSecureServers(writer, context);
|
||||||
writer.writeEndElement();
|
writer.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,14 +268,22 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeSecureDeployments(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
|
private void writeSecureDeployments(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
|
||||||
if (!context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).isDefined()) {
|
writeSecureResource(SecureDeploymentDefinition.TAG_NAME, SecureDeploymentDefinition.ALL_ATTRIBUTES, writer, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeSecureServers(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
|
||||||
|
writeSecureResource(SecureServerDefinition.TAG_NAME, SecureServerDefinition.ALL_ATTRIBUTES, writer, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeSecureResource(String tagName, List<SimpleAttributeDefinition> attributes, XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
|
||||||
|
if (!context.getModelNode().get(tagName).isDefined()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (Property deployment : context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).asPropertyList()) {
|
for (Property deployment : context.getModelNode().get(tagName).asPropertyList()) {
|
||||||
writer.writeStartElement(SecureDeploymentDefinition.TAG_NAME);
|
writer.writeStartElement(tagName);
|
||||||
writer.writeAttribute("name", deployment.getName());
|
writer.writeAttribute("name", deployment.getName());
|
||||||
ModelNode deploymentElements = deployment.getValue();
|
ModelNode deploymentElements = deployment.getValue();
|
||||||
for (AttributeDefinition element : SecureDeploymentDefinition.ALL_ATTRIBUTES) {
|
for (AttributeDefinition element : attributes) {
|
||||||
element.marshallAsElement(deploymentElements, writer);
|
element.marshallAsElement(deploymentElements, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +291,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||||
if (credentials.isDefined()) {
|
if (credentials.isDefined()) {
|
||||||
writeCredentials(writer, credentials);
|
writeCredentials(writer, credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelNode redirectRewriteRule = deploymentElements.get(RedirecRewritetRuleDefinition.TAG_NAME);
|
ModelNode redirectRewriteRule = deploymentElements.get(RedirecRewritetRuleDefinition.TAG_NAME);
|
||||||
if (redirectRewriteRule.isDefined()) {
|
if (redirectRewriteRule.isDefined()) {
|
||||||
writeRedirectRules(writer, redirectRewriteRule);
|
writeRedirectRules(writer, redirectRewriteRule);
|
||||||
|
|
|
@ -16,138 +16,88 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.subsystem.adapter.extension;
|
package org.keycloak.subsystem.adapter.extension;
|
||||||
|
|
||||||
import org.jboss.as.controller.AttributeDefinition;
|
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
|
||||||
import org.jboss.as.controller.PathElement;
|
|
||||||
import org.jboss.as.controller.SimpleAttributeDefinition;
|
|
||||||
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
|
|
||||||
import org.jboss.as.controller.SimpleResourceDefinition;
|
|
||||||
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
|
|
||||||
import org.jboss.as.controller.operations.validation.IntRangeValidator;
|
|
||||||
import org.jboss.as.controller.operations.validation.StringLengthValidator;
|
|
||||||
import org.jboss.as.controller.registry.ManagementResourceRegistration;
|
|
||||||
import org.jboss.dmr.ModelNode;
|
|
||||||
import org.jboss.dmr.ModelType;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import org.jboss.as.controller.OperationContext;
|
||||||
import java.util.HashMap;
|
import org.jboss.as.controller.OperationFailedException;
|
||||||
import java.util.List;
|
import org.jboss.as.controller.PathAddress;
|
||||||
import java.util.Map;
|
import org.jboss.as.controller.capability.RuntimeCapability;
|
||||||
|
import org.jboss.dmr.ModelNode;
|
||||||
|
import org.jboss.msc.service.ServiceController;
|
||||||
|
import org.jboss.msc.service.ServiceName;
|
||||||
|
import org.jboss.msc.service.ServiceTarget;
|
||||||
|
import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines attributes and operations for a secure-deployment.
|
* Defines attributes and operations for a secure-deployment.
|
||||||
*
|
*
|
||||||
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
||||||
*/
|
*/
|
||||||
public class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
final class SecureDeploymentDefinition extends AbstractAdapterConfigurationDefinition {
|
||||||
|
|
||||||
public static final String TAG_NAME = "secure-deployment";
|
static final String TAG_NAME = "secure-deployment";
|
||||||
|
|
||||||
protected static final SimpleAttributeDefinition REALM =
|
|
||||||
new SimpleAttributeDefinitionBuilder("realm", ModelType.STRING, true)
|
|
||||||
.setXmlName("realm")
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
|
||||||
.build();
|
|
||||||
protected static final SimpleAttributeDefinition RESOURCE =
|
|
||||||
new SimpleAttributeDefinitionBuilder("resource", ModelType.STRING, true)
|
|
||||||
.setXmlName("resource")
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
|
||||||
.build();
|
|
||||||
protected static final SimpleAttributeDefinition USE_RESOURCE_ROLE_MAPPINGS =
|
|
||||||
new SimpleAttributeDefinitionBuilder("use-resource-role-mappings", ModelType.BOOLEAN, true)
|
|
||||||
.setXmlName("use-resource-role-mappings")
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.setDefaultValue(new ModelNode(false))
|
|
||||||
.build();
|
|
||||||
protected static final SimpleAttributeDefinition BEARER_ONLY =
|
|
||||||
new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true)
|
|
||||||
.setXmlName("bearer-only")
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.setDefaultValue(new ModelNode(false))
|
|
||||||
.build();
|
|
||||||
protected static final SimpleAttributeDefinition ENABLE_BASIC_AUTH =
|
|
||||||
new SimpleAttributeDefinitionBuilder("enable-basic-auth", ModelType.BOOLEAN, true)
|
|
||||||
.setXmlName("enable-basic-auth")
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.setDefaultValue(new ModelNode(false))
|
|
||||||
.build();
|
|
||||||
protected static final SimpleAttributeDefinition PUBLIC_CLIENT =
|
|
||||||
new SimpleAttributeDefinitionBuilder("public-client", ModelType.BOOLEAN, true)
|
|
||||||
.setXmlName("public-client")
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.setDefaultValue(new ModelNode(false))
|
|
||||||
.build();
|
|
||||||
protected static final SimpleAttributeDefinition TURN_OFF_CHANGE_SESSION =
|
|
||||||
new SimpleAttributeDefinitionBuilder("turn-off-change-session-id-on-login", ModelType.BOOLEAN, true)
|
|
||||||
.setXmlName("turn-off-change-session-id-on-login")
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.setDefaultValue(new ModelNode(false))
|
|
||||||
.build();
|
|
||||||
protected static final SimpleAttributeDefinition TOKEN_MINIMUM_TIME_TO_LIVE =
|
|
||||||
new SimpleAttributeDefinitionBuilder("token-minimum-time-to-live", ModelType.INT, true)
|
|
||||||
.setXmlName("token-minimum-time-to-live")
|
|
||||||
.setValidator(new IntRangeValidator(-1, true))
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.build();
|
|
||||||
protected static final SimpleAttributeDefinition MIN_TIME_BETWEEN_JWKS_REQUESTS =
|
|
||||||
new SimpleAttributeDefinitionBuilder("min-time-between-jwks-requests", ModelType.INT, true)
|
|
||||||
.setXmlName("min-time-between-jwks-requests")
|
|
||||||
.setValidator(new IntRangeValidator(-1, true))
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
|
|
||||||
protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
|
||||||
static {
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(REALM);
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE);
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS);
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TOKEN_MINIMUM_TIME_TO_LIVE);
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(MIN_TIME_BETWEEN_JWKS_REQUESTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
|
||||||
static {
|
|
||||||
ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES);
|
|
||||||
ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
|
|
||||||
static {
|
|
||||||
for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
|
|
||||||
DEFINITION_LOOKUP.put(def.getXmlName(), def);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SecureDeploymentWriteAttributeHandler attrHandler = new SecureDeploymentWriteAttributeHandler(ALL_ATTRIBUTES);
|
|
||||||
|
|
||||||
public SecureDeploymentDefinition() {
|
public SecureDeploymentDefinition() {
|
||||||
super(PathElement.pathElement(TAG_NAME),
|
super(TAG_NAME, ALL_ATTRIBUTES, new SecureDeploymentAddHandler(), new SecureDeploymentRemoveHandler(), new SecureDeploymentWriteAttributeHandler());
|
||||||
KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
|
|
||||||
SecureDeploymentAddHandler.INSTANCE,
|
|
||||||
SecureDeploymentRemoveHandler.INSTANCE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
|
* Add a deployment to a realm.
|
||||||
super.registerOperations(resourceRegistration);
|
*
|
||||||
resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
|
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
||||||
}
|
*/
|
||||||
|
static final class SecureDeploymentAddHandler extends AbstractAdapterConfigurationAddHandler {
|
||||||
|
|
||||||
@Override
|
static final String HTTP_SERVER_AUTHENTICATION_CAPABILITY = "org.wildfly.security.http-server-mechanism-factory";
|
||||||
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
|
static RuntimeCapability<Void> HTTP_SERVER_AUTHENTICATION_RUNTIME_CAPABILITY;
|
||||||
super.registerAttributes(resourceRegistration);
|
|
||||||
for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
|
static {
|
||||||
resourceRegistration.registerReadWriteAttribute(attrDef, null, attrHandler);
|
try {
|
||||||
|
HTTP_SERVER_AUTHENTICATION_RUNTIME_CAPABILITY = RuntimeCapability
|
||||||
|
.Builder.of(HTTP_SERVER_AUTHENTICATION_CAPABILITY, true, HttpServerAuthenticationMechanismFactory.class)
|
||||||
|
.build();
|
||||||
|
} catch (NoClassDefFoundError ncfe) {
|
||||||
|
// ignore, Elytron not present thus no capability will be published by this resource definition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SecureDeploymentAddHandler() {
|
||||||
|
super(HTTP_SERVER_AUTHENTICATION_RUNTIME_CAPABILITY, ALL_ATTRIBUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||||
|
super.performRuntime(context, operation, model);
|
||||||
|
if (HTTP_SERVER_AUTHENTICATION_RUNTIME_CAPABILITY != null) {
|
||||||
|
installCapability(context, operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void installCapability(OperationContext context, ModelNode operation) {
|
||||||
|
PathAddress pathAddress = PathAddress.pathAddress(operation.get(OP_ADDR));
|
||||||
|
String factoryName = pathAddress.getLastElement().getValue();
|
||||||
|
ServiceName serviceName = context.getCapabilityServiceName(HTTP_SERVER_AUTHENTICATION_CAPABILITY, factoryName, HttpServerAuthenticationMechanismFactory.class);
|
||||||
|
KeycloakHttpAuthenticationFactoryService service = new KeycloakHttpAuthenticationFactoryService(factoryName);
|
||||||
|
ServiceTarget serviceTarget = context.getServiceTarget();
|
||||||
|
serviceTarget.addService(serviceName, service).setInitialMode(ServiceController.Mode.ACTIVE).install();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SimpleAttributeDefinition lookup(String name) {
|
/**
|
||||||
return DEFINITION_LOOKUP.get(name);
|
* Remove a secure-deployment from a realm.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
static final class SecureDeploymentRemoveHandler extends AbstractAdapterConfigurationRemoveHandler {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an attribute on a secure-deployment.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
static final class SecureDeploymentWriteAttributeHandler extends AbstractAdapterConfigurationWriteAttributeHandler {
|
||||||
|
SecureDeploymentWriteAttributeHandler() {
|
||||||
|
super(ALL_ATTRIBUTES);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,258 @@
|
||||||
|
/*
|
||||||
|
* 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.subsystem.adapter.extension;
|
||||||
|
|
||||||
|
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
|
||||||
|
import static org.keycloak.subsystem.adapter.extension.KeycloakHttpServerAuthenticationMechanismFactoryDefinition.KeycloakHttpServerAuthenticationMechanismFactoryAddHandler.HTTP_SERVER_AUTHENTICATION_CAPABILITY;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.undertow.io.IoCallback;
|
||||||
|
import io.undertow.io.Sender;
|
||||||
|
import io.undertow.server.HttpServerExchange;
|
||||||
|
import io.undertow.server.handlers.resource.Resource;
|
||||||
|
import io.undertow.server.handlers.resource.ResourceChangeListener;
|
||||||
|
import io.undertow.server.handlers.resource.ResourceManager;
|
||||||
|
import io.undertow.util.ETag;
|
||||||
|
import io.undertow.util.MimeMappings;
|
||||||
|
import org.jboss.as.controller.OperationContext;
|
||||||
|
import org.jboss.as.controller.OperationFailedException;
|
||||||
|
import org.jboss.as.controller.PathAddress;
|
||||||
|
import org.jboss.as.controller.capability.RuntimeCapability;
|
||||||
|
import org.jboss.as.server.mgmt.domain.ExtensibleHttpManagement;
|
||||||
|
import org.jboss.dmr.ModelNode;
|
||||||
|
import org.jboss.msc.service.Service;
|
||||||
|
import org.jboss.msc.service.ServiceController.Mode;
|
||||||
|
import org.jboss.msc.service.ServiceName;
|
||||||
|
import org.jboss.msc.service.ServiceTarget;
|
||||||
|
import org.jboss.msc.service.StartContext;
|
||||||
|
import org.jboss.msc.service.StartException;
|
||||||
|
import org.jboss.msc.service.StopContext;
|
||||||
|
import org.jboss.msc.value.InjectedValue;
|
||||||
|
import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines attributes and operations for a secure-deployment.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
final class SecureServerDefinition extends AbstractAdapterConfigurationDefinition {
|
||||||
|
|
||||||
|
public static final String TAG_NAME = "secure-server";
|
||||||
|
|
||||||
|
SecureServerDefinition() {
|
||||||
|
super(TAG_NAME, ALL_ATTRIBUTES, new SecureServerAddHandler(), new SecureServerRemoveHandler(), new SecureServerWriteHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link AbstractAdapterConfigurationAddHandler} that exposes a {@link SecureServerDefinition}
|
||||||
|
* as a capability through the installation of a {@link KeycloakHttpAuthenticationFactoryService}.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
static final class SecureServerAddHandler extends AbstractAdapterConfigurationAddHandler {
|
||||||
|
|
||||||
|
static final String HTTP_SERVER_AUTHENTICATION_CAPABILITY = "org.wildfly.security.http-server-mechanism-factory";
|
||||||
|
static final String HTTP_MANAGEMENT_HTTP_EXTENSIBLE_CAPABILITY = "org.wildfly.management.http.extensible";
|
||||||
|
static RuntimeCapability<Void> HTTP_SERVER_AUTHENTICATION_RUNTIME_CAPABILITY;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
HTTP_SERVER_AUTHENTICATION_RUNTIME_CAPABILITY = RuntimeCapability
|
||||||
|
.Builder.of(HTTP_SERVER_AUTHENTICATION_CAPABILITY, true, HttpServerAuthenticationMechanismFactory.class)
|
||||||
|
.build();
|
||||||
|
} catch (NoClassDefFoundError ncfe) {
|
||||||
|
// ignore, Elytron not present thus no capability will be published by this resource definition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SecureServerAddHandler() {
|
||||||
|
super(HTTP_SERVER_AUTHENTICATION_RUNTIME_CAPABILITY, ALL_ATTRIBUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||||
|
super.performRuntime(context, operation, model);
|
||||||
|
if (HTTP_SERVER_AUTHENTICATION_RUNTIME_CAPABILITY != null) {
|
||||||
|
installCapability(context, operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void installCapability(OperationContext context, ModelNode operation) throws OperationFailedException {
|
||||||
|
PathAddress pathAddress = PathAddress.pathAddress(operation.get(OP_ADDR));
|
||||||
|
String factoryName = pathAddress.getLastElement().getValue();
|
||||||
|
ServiceName serviceName = context.getCapabilityServiceName(HTTP_SERVER_AUTHENTICATION_CAPABILITY, factoryName, HttpServerAuthenticationMechanismFactory.class);
|
||||||
|
boolean publicClient = SecureServerDefinition.PUBLIC_CLIENT.resolveModelAttribute(context, operation).asBoolean(false);
|
||||||
|
|
||||||
|
if (!publicClient) {
|
||||||
|
throw new OperationFailedException("Only public clients are allowed to have their configuration exposed through the management interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
KeycloakHttpAuthenticationFactoryService service = new KeycloakHttpAuthenticationFactoryService(factoryName);
|
||||||
|
ServiceTarget serviceTarget = context.getServiceTarget();
|
||||||
|
InjectedValue<ExtensibleHttpManagement> injectedValue = new InjectedValue<>();
|
||||||
|
serviceTarget.addService(serviceName.append("http-management-context"), createHttpManagementConfigContextService(factoryName, injectedValue))
|
||||||
|
.addDependency(context.getCapabilityServiceName(HTTP_MANAGEMENT_HTTP_EXTENSIBLE_CAPABILITY, ExtensibleHttpManagement.class), ExtensibleHttpManagement.class, injectedValue).setInitialMode(Mode.ACTIVE).install();
|
||||||
|
serviceTarget.addService(serviceName, service).setInitialMode(Mode.ACTIVE).install();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link AbstractAdapterConfigurationRemoveHandler} that handles the removal of {@link SecureServerDefinition}.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
static final class SecureServerRemoveHandler extends AbstractAdapterConfigurationRemoveHandler {
|
||||||
|
@Override
|
||||||
|
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||||
|
super.performRuntime(context, operation, model);
|
||||||
|
PathAddress pathAddress = PathAddress.pathAddress(operation.get(OP_ADDR));
|
||||||
|
String factoryName = pathAddress.getLastElement().getValue();
|
||||||
|
ServiceName serviceName = context.getCapabilityServiceName(HTTP_SERVER_AUTHENTICATION_CAPABILITY, factoryName, HttpServerAuthenticationMechanismFactory.class);
|
||||||
|
context.removeService(serviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void recoverServices(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||||
|
super.recoverServices(context, operation, model);
|
||||||
|
SecureServerDefinition.SecureServerAddHandler.installCapability(context, operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link AbstractAdapterConfigurationWriteAttributeHandler} that updates attributes on a {@link SecureServerDefinition}.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
static final class SecureServerWriteHandler extends AbstractAdapterConfigurationWriteAttributeHandler {
|
||||||
|
SecureServerWriteHandler() {
|
||||||
|
super(ALL_ATTRIBUTES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Service<Void> createHttpManagementConfigContextService(final String factoryName, final InjectedValue<ExtensibleHttpManagement> httpConfigContext) {
|
||||||
|
final String contextName = "/keycloak/adapter/" + factoryName + "/";
|
||||||
|
return new Service<Void>() {
|
||||||
|
public void start(StartContext startContext) throws StartException {
|
||||||
|
ExtensibleHttpManagement extensibleHttpManagement = (ExtensibleHttpManagement)httpConfigContext.getValue();
|
||||||
|
extensibleHttpManagement.addStaticContext(contextName, new ResourceManager() {
|
||||||
|
public Resource getResource(final String path) throws IOException {
|
||||||
|
KeycloakAdapterConfigService adapterConfigService = KeycloakAdapterConfigService.getInstance();
|
||||||
|
final String config = adapterConfigService.getJSON(factoryName);
|
||||||
|
|
||||||
|
if (config == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Resource() {
|
||||||
|
public String getPath() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getLastModified() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastModifiedString() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ETag getETag() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirectory() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Resource> list() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContentType(MimeMappings mimeMappings) {
|
||||||
|
return "application/json";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void serve(Sender sender, HttpServerExchange exchange, IoCallback completionCallback) {
|
||||||
|
sender.send(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getContentLength() {
|
||||||
|
return Long.valueOf((long)config.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCacheKey() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getFile() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getFilePath() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getResourceManagerRoot() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getResourceManagerRootPath() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL getUrl() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isResourceChangeListenerSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerResourceChangeListener(ResourceChangeListener listener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeResourceChangeListener(ResourceChangeListener listener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop(StopContext stopContext) {
|
||||||
|
((ExtensibleHttpManagement)httpConfigContext.getValue()).removeContext(contextName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Void getValue() throws IllegalStateException, IllegalArgumentException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,8 @@ keycloak.subsystem.add=Operation Adds Keycloak adapter subsystem
|
||||||
keycloak.subsystem.remove=Operation removes Keycloak adapter subsystem
|
keycloak.subsystem.remove=Operation removes Keycloak adapter subsystem
|
||||||
keycloak.subsystem.realm=A Keycloak realm.
|
keycloak.subsystem.realm=A Keycloak realm.
|
||||||
keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
|
keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
|
||||||
|
keycloak.subsystem.secure-server=A configuration exposed to the server.
|
||||||
|
keycloak.subsystem.http-server-mechanism-factory=A http-server-mechanism-factory exposed to the server.
|
||||||
|
|
||||||
keycloak.realm=A Keycloak realm.
|
keycloak.realm=A Keycloak realm.
|
||||||
keycloak.realm.add=Add a realm definition to the subsystem.
|
keycloak.realm.add=Add a realm definition to the subsystem.
|
||||||
|
@ -90,7 +92,48 @@ keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh t
|
||||||
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
||||||
keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||||
|
|
||||||
|
keycloak.secure-server=A deployment secured by Keycloak
|
||||||
|
keycloak.secure-server.add=Add a deployment to be secured by Keycloak
|
||||||
|
keycloak.secure-server.realm=Keycloak realm
|
||||||
|
keycloak.secure-server.remove=Remove a deployment to be secured by Keycloak
|
||||||
|
keycloak.secure-server.realm-public-key=Public key of the realm
|
||||||
|
keycloak.secure-server.auth-server-url=Base URL of the Realm Auth Server
|
||||||
|
keycloak.secure-server.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
|
||||||
|
keycloak.secure-server.ssl-required=Specify if SSL is required (valid values are all, external and none)
|
||||||
|
keycloak.secure-server.allow-any-hostname=SSL Setting
|
||||||
|
keycloak.secure-server.truststore=Truststore used for adapter client HTTPS requests
|
||||||
|
keycloak.secure-server.truststore-password=Password of the Truststore
|
||||||
|
keycloak.secure-server.connection-pool-size=Connection pool size for the client used by the adapter
|
||||||
|
keycloak.secure-server.resource=Application name
|
||||||
|
keycloak.secure-server.use-resource-role-mappings=Use resource level permissions from token
|
||||||
|
keycloak.secure-server.credentials=Adapter credentials
|
||||||
|
keycloak.secure-server.redirect-rewrite-rule=Apply a rewrite rule for the redirect URI
|
||||||
|
keycloak.secure-server.bearer-only=Bearer Token Auth only
|
||||||
|
keycloak.secure-server.enable-basic-auth=Enable Basic Authentication
|
||||||
|
keycloak.secure-server.public-client=Public client
|
||||||
|
keycloak.secure-server.enable-cors=Enable Keycloak CORS support
|
||||||
|
keycloak.secure-server.autodetect-bearer-only=autodetect bearer-only requests
|
||||||
|
keycloak.secure-server.client-keystore=n/a
|
||||||
|
keycloak.secure-server.client-keystore-password=n/a
|
||||||
|
keycloak.secure-server.client-key-password=n/a
|
||||||
|
keycloak.secure-server.cors-max-age=CORS max-age header
|
||||||
|
keycloak.secure-server.cors-allowed-headers=CORS allowed headers
|
||||||
|
keycloak.secure-server.cors-allowed-methods=CORS allowed methods
|
||||||
|
keycloak.secure-server.cors-exposed-headers=CORS exposed headers
|
||||||
|
keycloak.secure-server.expose-token=Enable secure URL that exposes access token
|
||||||
|
keycloak.secure-server.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
|
||||||
|
keycloak.secure-server.always-refresh-token=Refresh token on every single web request
|
||||||
|
keycloak.secure-server.register-node-at-startup=Cluster setting
|
||||||
|
keycloak.secure-server.register-node-period=how often to re-register node
|
||||||
|
keycloak.secure-server.token-store=cookie or session storage for auth session data
|
||||||
|
keycloak.secure-server.principal-attribute=token attribute to use to set Principal name
|
||||||
|
keycloak.secure-server.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
|
||||||
|
keycloak.secure-server.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
|
||||||
|
keycloak.secure-server.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
||||||
|
keycloak.secure-server.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||||
|
|
||||||
keycloak.secure-deployment.credential=Credential value
|
keycloak.secure-deployment.credential=Credential value
|
||||||
|
keycloak.secure-server.credential=Credential value
|
||||||
|
|
||||||
keycloak.credential=Credential
|
keycloak.credential=Credential
|
||||||
keycloak.credential.value=Credential value
|
keycloak.credential.value=Credential value
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
<xs:element name="realm" maxOccurs="unbounded" minOccurs="0" type="realm-type"/>
|
<xs:element name="realm" maxOccurs="unbounded" minOccurs="0" type="realm-type"/>
|
||||||
<xs:element name="secure-deployment" maxOccurs="unbounded" minOccurs="0" type="secure-deployment-type"/>
|
<xs:element name="secure-deployment" maxOccurs="unbounded" minOccurs="0" type="secure-deployment-type"/>
|
||||||
|
<xs:element name="secure-server" maxOccurs="unbounded" minOccurs="0" type="secure-deployment-type"/>
|
||||||
</xs:choice>
|
</xs:choice>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
|
@ -84,7 +85,7 @@
|
||||||
<xs:element name="allow-any-hostname" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
<xs:element name="allow-any-hostname" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="use-resource-role-mappings" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
<xs:element name="use-resource-role-mappings" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="cors-max-age" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="cors-max-age" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="auth-server-url" type="xs:string" minOccurs="1" maxOccurs="1"/>
|
<xs:element name="auth-server-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="realm" type="xs:string" minOccurs="1" maxOccurs="1"/>
|
<xs:element name="realm" type="xs:string" minOccurs="1" maxOccurs="1"/>
|
||||||
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||||
|
@ -99,9 +100,9 @@
|
||||||
<xs:element name="connection-pool-size" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="connection-pool-size" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
|
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
|
<xs:element name="realm-public-key" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
|
<xs:element name="credential" type="credential-type" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="redirect-rewrite-rule" type="redirect-rewrite-rule-type" minOccurs="1" maxOccurs="1"/>
|
<xs:element name="redirect-rewrite-rule" type="redirect-rewrite-rule-type" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||||
|
|
|
@ -73,7 +73,7 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest {
|
||||||
ModelNode deployment = new ModelNode();
|
ModelNode deployment = new ModelNode();
|
||||||
deployment.get("realm").set("demo");
|
deployment.get("realm").set("demo");
|
||||||
deployment.get("resource").set("customer-portal");
|
deployment.get("resource").set("customer-portal");
|
||||||
service.addSecureDeployment(deploymentOp, deployment);
|
service.addSecureDeployment(deploymentOp, deployment, false);
|
||||||
|
|
||||||
addCredential(addr, service, "secret", "secret1");
|
addCredential(addr, service, "secret", "secret1");
|
||||||
addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
|
addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
|
||||||
|
|
|
@ -40,6 +40,10 @@
|
||||||
<token-store>session</token-store>
|
<token-store>session</token-store>
|
||||||
<principal-attribute>sub</principal-attribute>
|
<principal-attribute>sub</principal-attribute>
|
||||||
</realm>
|
</realm>
|
||||||
|
<realm name="jboss-infra">
|
||||||
|
<realm-public-key>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqKoq+a9MgXepmsPJDmo45qswuChW9pWjanX68oIBuI4hGvhQxFHryCow230A+sr7tFdMQMt8f1l/ysmV/fYAuW29WaoY4kI4Ou1yYPuwywKSsxT6PooTs83hKyZ1h4LZMj5DkLGDDDyVRHob2WmPaYg9RGVRw3iGGsD/p+Yb+L/gnBYQnZZ7lYqmN7h36p5CkzzlgXQA1Ha8sQxL+rJNH8+sZm0vBrKsoII3Of7TqHGsm1RwFV3XCuGJ7S61AbjJMXL5DQgJl9Z5scvxGAyoRLKC294UgMnQdzyBTMPw2GybxkRKmiK2KjQKmcopmrJp/Bt6fBR6ZkGSs9qUlxGHgwIDAQAB</realm-public-key>
|
||||||
|
<auth-server-url>http://localhost:8180/auth</auth-server-url>
|
||||||
|
</realm>
|
||||||
<secure-deployment name="web-console">
|
<secure-deployment name="web-console">
|
||||||
<realm>master</realm>
|
<realm>master</realm>
|
||||||
<resource>web-console</resource>
|
<resource>web-console</resource>
|
||||||
|
@ -69,4 +73,16 @@
|
||||||
</credential>
|
</credential>
|
||||||
<redirect-rewrite-rule name="^/wsmaster/api/(.*)$">/api/$1/</redirect-rewrite-rule>
|
<redirect-rewrite-rule name="^/wsmaster/api/(.*)$">/api/$1/</redirect-rewrite-rule>
|
||||||
</secure-deployment>
|
</secure-deployment>
|
||||||
|
<secure-deployment name="wildfly-management">
|
||||||
|
<realm>jboss-infra</realm>
|
||||||
|
<resource>wildfly-management</resource>
|
||||||
|
<bearer-only>true</bearer-only>
|
||||||
|
<ssl-required>EXTERNAL</ssl-required>
|
||||||
|
<principal-attribute>preferred_username</principal-attribute>
|
||||||
|
</secure-deployment>
|
||||||
|
<secure-server name="wildfly-console">
|
||||||
|
<realm>jboss-infra</realm>
|
||||||
|
<resource>wildfly-console</resource>
|
||||||
|
<public-client>true</public-client>
|
||||||
|
</secure-server>
|
||||||
</subsystem>
|
</subsystem>
|
|
@ -17,6 +17,7 @@
|
||||||
package org.keycloak.adapters.saml.elytron;
|
package org.keycloak.adapters.saml.elytron;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -53,7 +54,7 @@ public class KeycloakSecurityRealm implements SecurityRealm {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
|
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> aClass, String s, AlgorithmParameterSpec algorithmParameterSpec) throws RealmUnavailableException {
|
||||||
return SupportLevel.UNSUPPORTED;
|
return SupportLevel.UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ public class KeycloakSecurityRealm implements SecurityRealm {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
|
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
|
||||||
return SupportLevel.UNSUPPORTED;
|
return SupportLevel.UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ public class UserRepresentation {
|
||||||
protected List<String> realmRoles;
|
protected List<String> realmRoles;
|
||||||
protected Map<String, List<String>> clientRoles;
|
protected Map<String, List<String>> clientRoles;
|
||||||
protected List<UserConsentRepresentation> clientConsents;
|
protected List<UserConsentRepresentation> clientConsents;
|
||||||
|
protected Integer notBefore;
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
protected Map<String, List<String>> applicationRoles;
|
protected Map<String, List<String>> applicationRoles;
|
||||||
|
@ -216,6 +217,14 @@ public class UserRepresentation {
|
||||||
this.clientConsents = clientConsents;
|
this.clientConsents = clientConsents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getNotBefore() {
|
||||||
|
return notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotBefore(Integer notBefore) {
|
||||||
|
this.notBefore = notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Map<String, List<String>> getApplicationRoles() {
|
public Map<String, List<String>> getApplicationRoles() {
|
||||||
return applicationRoles;
|
return applicationRoles;
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
<outputDirectory></outputDirectory>
|
<outputDirectory></outputDirectory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>**/*.js</include>
|
<include>**/*.js</include>
|
||||||
|
<include>**/*.map</include>
|
||||||
<include>**/*.d.ts</include>
|
<include>**/*.d.ts</include>
|
||||||
<include>**/*.html</include>
|
<include>**/*.html</include>
|
||||||
</includes>
|
</includes>
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-js-adapter</artifactId>
|
<artifactId>keycloak-js-adapter</artifactId>
|
||||||
<outputDirectory>${project.build.directory}/unpacked/js-adapter</outputDirectory>
|
<outputDirectory>${project.build.directory}/unpacked/js-adapter</outputDirectory>
|
||||||
<includes>*.js,*.d.ts</includes>
|
<includes>*.js,*.map,*.d.ts</includes>
|
||||||
</artifactItem>
|
</artifactItem>
|
||||||
</artifactItems>
|
</artifactItems>
|
||||||
<excludes>**/welcome-content/*</excludes>
|
<excludes>**/welcome-content/*</excludes>
|
||||||
|
|
|
@ -36,10 +36,10 @@ else
|
||||||
end-if
|
end-if
|
||||||
|
|
||||||
if (outcome != success) of /subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:read-resource
|
if (outcome != success) of /subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:read-resource
|
||||||
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:add(http-server-factories=[keycloak-oidc-http-server-mechanism-factory, global])
|
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:add(http-server-mechanism-factories=[keycloak-oidc-http-server-mechanism-factory, global])
|
||||||
else
|
else
|
||||||
echo Keycloak HTTP Mechanism Factory already installed. Trying to install Keycloak OpenID Connect HTTP Mechanism Factory.
|
echo Keycloak HTTP Mechanism Factory already installed. Trying to install Keycloak OpenID Connect HTTP Mechanism Factory.
|
||||||
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:list-add(name=http-server-factories, value=keycloak-oidc-http-server-mechanism-factory)
|
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:list-add(name=http-server-mechanism-factories, value=keycloak-oidc-http-server-mechanism-factory)
|
||||||
end-if
|
end-if
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,10 +38,10 @@ else
|
||||||
end-if
|
end-if
|
||||||
|
|
||||||
if (outcome != success) of /subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:read-resource
|
if (outcome != success) of /subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:read-resource
|
||||||
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:add(http-server-factories=[keycloak-oidc-http-server-mechanism-factory, global])
|
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:add(http-server-mechanism-factories=[keycloak-oidc-http-server-mechanism-factory, global])
|
||||||
else
|
else
|
||||||
echo Keycloak HTTP Mechanism Factory already installed. Trying to install Keycloak OpenID Connect HTTP Mechanism Factory.
|
echo Keycloak HTTP Mechanism Factory already installed. Trying to install Keycloak OpenID Connect HTTP Mechanism Factory.
|
||||||
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:list-add(name=http-server-factories, value=keycloak-oidc-http-server-mechanism-factory)
|
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:list-add(name=http-server-mechanism-factories, value=keycloak-oidc-http-server-mechanism-factory)
|
||||||
end-if
|
end-if
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
|
|
||||||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
xmlns:xalan="http://xml.apache.org/xalan"
|
xmlns:xalan="http://xml.apache.org/xalan"
|
||||||
xmlns:j="urn:jboss:domain:4.0"
|
xmlns:j="urn:jboss:domain:5.0"
|
||||||
xmlns:ds="urn:jboss:domain:datasources:4.0"
|
xmlns:ds="urn:jboss:domain:datasources:5.0"
|
||||||
xmlns:k="urn:jboss:domain:keycloak:1.1"
|
xmlns:k="urn:jboss:domain:keycloak:1.1"
|
||||||
xmlns:sec="urn:jboss:domain:security:1.2"
|
xmlns:sec="urn:jboss:domain:security:2.0"
|
||||||
version="2.0"
|
version="2.0"
|
||||||
exclude-result-prefixes="xalan j ds k sec">
|
exclude-result-prefixes="xalan j ds k sec">
|
||||||
|
|
||||||
|
|
|
@ -38,5 +38,9 @@
|
||||||
<module name="org.jboss.vfs"/>
|
<module name="org.jboss.vfs"/>
|
||||||
<module name="org.jboss.as.web-common"/>
|
<module name="org.jboss.as.web-common"/>
|
||||||
<module name="org.jboss.metadata"/>
|
<module name="org.jboss.metadata"/>
|
||||||
|
<module name="org.keycloak.keycloak-adapter-core"/>
|
||||||
|
<module name="org.keycloak.keycloak-wildfly-elytron-oidc-adapter"/>
|
||||||
|
<module name="org.wildfly.security.elytron"/>
|
||||||
|
<module name="io.undertow.core"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</module>
|
</module>
|
||||||
|
|
|
@ -36,10 +36,10 @@ else
|
||||||
end-if
|
end-if
|
||||||
|
|
||||||
if (outcome != success) of /subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:read-resource
|
if (outcome != success) of /subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:read-resource
|
||||||
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:add(http-server-factories=[keycloak-saml-http-server-mechanism-factory, global])
|
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:add(http-server-mechanism-factories=[keycloak-saml-http-server-mechanism-factory, global])
|
||||||
else
|
else
|
||||||
echo Keycloak HTTP Mechanism Factory already installed. Trying to install Keycloak SAML HTTP Mechanism Factory.
|
echo Keycloak HTTP Mechanism Factory already installed. Trying to install Keycloak SAML HTTP Mechanism Factory.
|
||||||
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:list-add(name=http-server-factories, value=keycloak-saml-http-server-mechanism-factory)
|
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:list-add(name=http-server-mechanism-factories, value=keycloak-saml-http-server-mechanism-factory)
|
||||||
end-if
|
end-if
|
||||||
|
|
||||||
if (outcome != success) of /subsystem=elytron/http-authentication-factory=keycloak-http-authentication:read-resource
|
if (outcome != success) of /subsystem=elytron/http-authentication-factory=keycloak-http-authentication:read-resource
|
||||||
|
|
1
distribution/server-overlay/src/main/modules/layers.conf
Normal file
1
distribution/server-overlay/src/main/modules/layers.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
layers=keycloak
|
|
@ -22,7 +22,6 @@ import org.keycloak.services.resource.RealmResourceProvider;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -41,7 +40,7 @@ public class HelloResourceProvider implements RealmResourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.TEXT_PLAIN)
|
@Produces("text/plain; charset=utf-8")
|
||||||
public String get() {
|
public String get() {
|
||||||
String name = session.getContext().getRealm().getDisplayName();
|
String name = session.getContext().getRealm().getDisplayName();
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
|
|
|
@ -262,7 +262,14 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
sessionCacheConfiguration = sessionConfigBuilder.build();
|
sessionCacheConfiguration = sessionConfigBuilder.build();
|
||||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||||
|
|
||||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfigurationBase);
|
if (jdgEnabled) {
|
||||||
|
sessionConfigBuilder = new ConfigurationBuilder();
|
||||||
|
sessionConfigBuilder.read(sessionCacheConfigurationBase);
|
||||||
|
configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, KcRemoteStoreConfigurationBuilder.class);
|
||||||
|
}
|
||||||
|
sessionCacheConfiguration = sessionConfigBuilder.build();
|
||||||
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
|
||||||
|
|
||||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, sessionCacheConfigurationBase);
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, sessionCacheConfigurationBase);
|
||||||
|
|
||||||
// Retrieve caches to enforce rebalance
|
// Retrieve caches to enforce rebalance
|
||||||
|
|
|
@ -334,6 +334,8 @@ public class UserCacheSession implements UserCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UserModel cacheUser(RealmModel realm, UserModel delegate, Long revision) {
|
protected UserModel cacheUser(RealmModel realm, UserModel delegate, Long revision) {
|
||||||
|
int notBefore = getDelegate().getNotBeforeOfUser(realm, delegate);
|
||||||
|
|
||||||
StorageId storageId = new StorageId(delegate.getId());
|
StorageId storageId = new StorageId(delegate.getId());
|
||||||
CachedUser cached = null;
|
CachedUser cached = null;
|
||||||
if (!storageId.isLocal()) {
|
if (!storageId.isLocal()) {
|
||||||
|
@ -343,7 +345,7 @@ public class UserCacheSession implements UserCache {
|
||||||
if (policy != null && policy == UserStorageProviderModel.CachePolicy.NO_CACHE) {
|
if (policy != null && policy == UserStorageProviderModel.CachePolicy.NO_CACHE) {
|
||||||
return delegate;
|
return delegate;
|
||||||
}
|
}
|
||||||
cached = new CachedUser(revision, realm, delegate);
|
cached = new CachedUser(revision, realm, delegate, notBefore);
|
||||||
if (policy == null || policy == UserStorageProviderModel.CachePolicy.DEFAULT) {
|
if (policy == null || policy == UserStorageProviderModel.CachePolicy.DEFAULT) {
|
||||||
cache.addRevisioned(cached, startupRevision);
|
cache.addRevisioned(cached, startupRevision);
|
||||||
} else {
|
} else {
|
||||||
|
@ -366,7 +368,7 @@ public class UserCacheSession implements UserCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cached = new CachedUser(revision, realm, delegate);
|
cached = new CachedUser(revision, realm, delegate, notBefore);
|
||||||
cache.addRevisioned(cached, startupRevision);
|
cache.addRevisioned(cached, startupRevision);
|
||||||
}
|
}
|
||||||
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
||||||
|
@ -765,6 +767,32 @@ public class UserCacheSession implements UserCache {
|
||||||
return consentModel;
|
return consentModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
|
||||||
|
if (!isRegisteredForInvalidation(realm, user.getId())) {
|
||||||
|
UserModel foundUser = getUserById(user.getId(), realm);
|
||||||
|
if (foundUser instanceof UserAdapter) {
|
||||||
|
((UserAdapter) foundUser).invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDelegate().setNotBeforeForUser(realm, user, notBefore);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
|
||||||
|
if (isRegisteredForInvalidation(realm, user.getId())) {
|
||||||
|
return getDelegate().getNotBeforeOfUser(realm, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserModel foundUser = getUserById(user.getId(), realm);
|
||||||
|
if (foundUser instanceof UserAdapter) {
|
||||||
|
return ((UserAdapter) foundUser).cached.getNotBefore();
|
||||||
|
} else {
|
||||||
|
return getDelegate().getNotBeforeOfUser(realm, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||||
@Override
|
@Override
|
||||||
public Resource getDelegateForUpdate() {
|
public Resource getDelegateForUpdate() {
|
||||||
if (updated == null) {
|
if (updated == null) {
|
||||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
|
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
|
||||||
updated = cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
|
updated = cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
|
||||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||||
@Override
|
@Override
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
getDelegateForUpdate();
|
getDelegateForUpdate();
|
||||||
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
|
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
|
||||||
updated.setName(name);
|
updated.setName(name);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||||
@Override
|
@Override
|
||||||
public void setUri(String uri) {
|
public void setUri(String uri) {
|
||||||
getDelegateForUpdate();
|
getDelegateForUpdate();
|
||||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uri, cached.getScopesIds(), cached.getResourceServerId());
|
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uri, cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
|
||||||
updated.setUri(uri);
|
updated.setUri(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||||
@Override
|
@Override
|
||||||
public void setType(String type) {
|
public void setType(String type) {
|
||||||
getDelegateForUpdate();
|
getDelegateForUpdate();
|
||||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
|
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUri(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
|
||||||
updated.setType(type);
|
updated.setType(type);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
||||||
@Override
|
@Override
|
||||||
public void updateScopes(Set<Scope> scopes) {
|
public void updateScopes(Set<Scope> scopes) {
|
||||||
getDelegateForUpdate();
|
getDelegateForUpdate();
|
||||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId());
|
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner());
|
||||||
updated.updateScopes(scopes);
|
updated.updateScopes(scopes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,9 +75,10 @@ public class StoreFactoryCacheManager extends CacheManager {
|
||||||
addInvalidations(InScopePredicate.create().scope(id), invalidations);
|
addInvalidations(InScopePredicate.create().scope(id), invalidations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resourceUpdated(String id, String name, String type, String uri, Set<String> scopes, String serverId, Set<String> invalidations) {
|
public void resourceUpdated(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner, Set<String> invalidations) {
|
||||||
invalidations.add(id);
|
invalidations.add(id);
|
||||||
invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, serverId));
|
invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, serverId));
|
||||||
|
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId));
|
||||||
|
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, serverId));
|
invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, serverId));
|
||||||
|
@ -97,8 +98,7 @@ public class StoreFactoryCacheManager extends CacheManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resourceRemoval(String id, String name, String type, String uri, String owner, Set<String> scopes, String serverId, Set<String> invalidations) {
|
public void resourceRemoval(String id, String name, String type, String uri, String owner, Set<String> scopes, String serverId, Set<String> invalidations) {
|
||||||
resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
|
resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations);
|
||||||
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId));
|
|
||||||
addInvalidations(InResourcePredicate.create().resource(id), invalidations);
|
addInvalidations(InResourcePredicate.create().resource(id), invalidations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -245,12 +245,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
||||||
invalidationEvents.add(ScopeUpdatedEvent.create(id, name, serverId));
|
invalidationEvents.add(ScopeUpdatedEvent.create(id, name, serverId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerResourceInvalidation(String id, String name, String type, String uri, Set<String> scopes, String serverId) {
|
public void registerResourceInvalidation(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner) {
|
||||||
cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
|
cache.resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations);
|
||||||
ResourceAdapter adapter = managedResources.get(id);
|
ResourceAdapter adapter = managedResources.get(id);
|
||||||
if (adapter != null) adapter.invalidateFlag();
|
if (adapter != null) adapter.invalidateFlag();
|
||||||
|
|
||||||
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId));
|
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId, owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerPolicyInvalidation(String id, String name, Set<String> resources, Set<String> scopes, String serverId) {
|
public void registerPolicyInvalidation(String id, String name, Set<String> resources, Set<String> scopes, String serverId) {
|
||||||
|
@ -509,7 +509,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
||||||
@Override
|
@Override
|
||||||
public Resource create(String name, ResourceServer resourceServer, String owner) {
|
public Resource create(String name, ResourceServer resourceServer, String owner) {
|
||||||
Resource resource = getResourceStoreDelegate().create(name, resourceServer, owner);
|
Resource resource = getResourceStoreDelegate().create(name, resourceServer, owner);
|
||||||
registerResourceInvalidation(resource.getId(), resource.getName(), resource.getType(), resource.getUri(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resourceServer.getId());
|
registerResourceInvalidation(resource.getId(), resource.getName(), resource.getType(), resource.getUri(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resourceServer.getId(), resource.getOwner());
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,9 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
|
||||||
private String type;
|
private String type;
|
||||||
private String uri;
|
private String uri;
|
||||||
private Set<String> scopes;
|
private Set<String> scopes;
|
||||||
|
private String owner;
|
||||||
|
|
||||||
public static ResourceUpdatedEvent create(String id, String name, String type, String uri, Set<String> scopes, String serverId) {
|
public static ResourceUpdatedEvent create(String id, String name, String type, String uri, Set<String> scopes, String serverId, String owner) {
|
||||||
ResourceUpdatedEvent event = new ResourceUpdatedEvent();
|
ResourceUpdatedEvent event = new ResourceUpdatedEvent();
|
||||||
event.id = id;
|
event.id = id;
|
||||||
event.name = name;
|
event.name = name;
|
||||||
|
@ -42,6 +43,7 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
|
||||||
event.uri = uri;
|
event.uri = uri;
|
||||||
event.scopes = scopes;
|
event.scopes = scopes;
|
||||||
event.serverId = serverId;
|
event.serverId = serverId;
|
||||||
|
event.owner = owner;
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +59,6 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
||||||
cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
|
cache.resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,10 +45,11 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
private Set<String> requiredActions = new HashSet<>();
|
private Set<String> requiredActions = new HashSet<>();
|
||||||
private Set<String> roleMappings = new HashSet<>();
|
private Set<String> roleMappings = new HashSet<>();
|
||||||
private Set<String> groups = new HashSet<>();
|
private Set<String> groups = new HashSet<>();
|
||||||
|
private int notBefore;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public CachedUser(Long revision, RealmModel realm, UserModel user) {
|
public CachedUser(Long revision, RealmModel realm, UserModel user, int notBefore) {
|
||||||
super(revision, user.getId());
|
super(revision, user.getId());
|
||||||
this.realm = realm.getId();
|
this.realm = realm.getId();
|
||||||
this.username = user.getUsername();
|
this.username = user.getUsername();
|
||||||
|
@ -71,6 +72,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
groups.add(group.getId());
|
groups.add(group.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.notBefore = notBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRealm() {
|
public String getRealm() {
|
||||||
|
@ -129,4 +131,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getNotBefore() {
|
||||||
|
return notBefore;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,6 @@ import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessi
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.events.ClientRemovedSessionEvent;
|
|
||||||
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
||||||
import org.keycloak.models.sessions.infinispan.events.RemoveAllUserLoginFailuresEvent;
|
import org.keycloak.models.sessions.infinispan.events.RemoveAllUserLoginFailuresEvent;
|
||||||
import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent;
|
import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent;
|
||||||
|
@ -76,11 +75,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
|
|
||||||
protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache;
|
protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache;
|
||||||
protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache;
|
protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache;
|
||||||
protected final Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache;
|
protected final Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache;
|
||||||
|
|
||||||
protected final InfinispanChangelogBasedTransaction<UserSessionEntity> sessionTx;
|
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> sessionTx;
|
||||||
protected final InfinispanChangelogBasedTransaction<UserSessionEntity> offlineSessionTx;
|
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> offlineSessionTx;
|
||||||
protected final InfinispanKeycloakTransaction tx;
|
protected final InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> loginFailuresTx;
|
||||||
|
|
||||||
protected final SessionEventsSenderTransaction clusterEventsSenderTx;
|
protected final SessionEventsSenderTransaction clusterEventsSenderTx;
|
||||||
|
|
||||||
|
@ -93,7 +92,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
LastSessionRefreshStore offlineLastSessionRefreshStore,
|
LastSessionRefreshStore offlineLastSessionRefreshStore,
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache,
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache,
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache,
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache,
|
||||||
Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache) {
|
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
|
||||||
this.sessionCache = sessionCache;
|
this.sessionCache = sessionCache;
|
||||||
|
@ -103,24 +102,24 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
this.sessionTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCache, remoteCacheInvoker);
|
this.sessionTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCache, remoteCacheInvoker);
|
||||||
this.offlineSessionTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, offlineSessionCache, remoteCacheInvoker);
|
this.offlineSessionTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, offlineSessionCache, remoteCacheInvoker);
|
||||||
|
|
||||||
this.tx = new InfinispanKeycloakTransaction();
|
this.loginFailuresTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, loginFailureCache, remoteCacheInvoker);
|
||||||
|
|
||||||
this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session);
|
this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session);
|
||||||
|
|
||||||
this.lastSessionRefreshStore = lastSessionRefreshStore;
|
this.lastSessionRefreshStore = lastSessionRefreshStore;
|
||||||
this.offlineLastSessionRefreshStore = offlineLastSessionRefreshStore;
|
this.offlineLastSessionRefreshStore = offlineLastSessionRefreshStore;
|
||||||
|
|
||||||
session.getTransactionManager().enlistAfterCompletion(tx);
|
|
||||||
session.getTransactionManager().enlistAfterCompletion(clusterEventsSenderTx);
|
session.getTransactionManager().enlistAfterCompletion(clusterEventsSenderTx);
|
||||||
session.getTransactionManager().enlistAfterCompletion(sessionTx);
|
session.getTransactionManager().enlistAfterCompletion(sessionTx);
|
||||||
session.getTransactionManager().enlistAfterCompletion(offlineSessionTx);
|
session.getTransactionManager().enlistAfterCompletion(offlineSessionTx);
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(loginFailuresTx);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Cache<String, SessionEntityWrapper<UserSessionEntity>> getCache(boolean offline) {
|
protected Cache<String, SessionEntityWrapper<UserSessionEntity>> getCache(boolean offline) {
|
||||||
return offline ? offlineSessionCache : sessionCache;
|
return offline ? offlineSessionCache : sessionCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InfinispanChangelogBasedTransaction<UserSessionEntity> getTransaction(boolean offline) {
|
protected InfinispanChangelogBasedTransaction<String, UserSessionEntity> getTransaction(boolean offline) {
|
||||||
return offline ? offlineSessionTx : sessionTx;
|
return offline ? offlineSessionTx : sessionTx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +135,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
||||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
|
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
|
||||||
|
|
||||||
InfinispanChangelogBasedTransaction<UserSessionEntity> updateTx = getTransaction(false);
|
InfinispanChangelogBasedTransaction<String, UserSessionEntity> updateTx = getTransaction(false);
|
||||||
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession, this, updateTx);
|
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession, this, updateTx);
|
||||||
adapter.setUserSession(userSession);
|
adapter.setUserSession(userSession);
|
||||||
return adapter;
|
return adapter;
|
||||||
|
@ -202,7 +201,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserSessionEntity getUserSessionEntity(String id, boolean offline) {
|
private UserSessionEntity getUserSessionEntity(String id, boolean offline) {
|
||||||
InfinispanChangelogBasedTransaction<UserSessionEntity> tx = getTransaction(offline);
|
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
|
||||||
SessionEntityWrapper<UserSessionEntity> entityWrapper = tx.get(id);
|
SessionEntityWrapper<UserSessionEntity> entityWrapper = tx.get(id);
|
||||||
return entityWrapper==null ? null : entityWrapper.getEntity();
|
return entityWrapper==null ? null : entityWrapper.getEntity();
|
||||||
}
|
}
|
||||||
|
@ -311,7 +310,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
UserSessionModel remoteSessionAdapter = wrap(realm, remoteSessionEntity, offline);
|
UserSessionModel remoteSessionAdapter = wrap(realm, remoteSessionEntity, offline);
|
||||||
if (predicate.test(remoteSessionAdapter)) {
|
if (predicate.test(remoteSessionAdapter)) {
|
||||||
|
|
||||||
InfinispanChangelogBasedTransaction<UserSessionEntity> tx = getTransaction(offline);
|
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
|
||||||
|
|
||||||
// Remote entity contains our predicate. Update local cache with the remote entity
|
// Remote entity contains our predicate. Update local cache with the remote entity
|
||||||
SessionEntityWrapper<UserSessionEntity> sessionWrapper = remoteSessionEntity.mergeRemoteEntityWithLocalEntity(tx.get(id));
|
SessionEntityWrapper<UserSessionEntity> sessionWrapper = remoteSessionEntity.mergeRemoteEntityWithLocalEntity(tx.get(id));
|
||||||
|
@ -511,7 +510,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
@Override
|
@Override
|
||||||
public UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId) {
|
public UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId) {
|
||||||
LoginFailureKey key = new LoginFailureKey(realm.getId(), userId);
|
LoginFailureKey key = new LoginFailureKey(realm.getId(), userId);
|
||||||
return wrap(key, loginFailureCache.get(key));
|
LoginFailureEntity entity = getLoginFailureEntity(key);
|
||||||
|
return wrap(key, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoginFailureEntity getLoginFailureEntity(LoginFailureKey key) {
|
||||||
|
InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> tx = getLoginFailuresTx();
|
||||||
|
SessionEntityWrapper<LoginFailureEntity> entityWrapper = tx.get(key);
|
||||||
|
return entityWrapper==null ? null : entityWrapper.getEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -520,13 +526,53 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
LoginFailureEntity entity = new LoginFailureEntity();
|
LoginFailureEntity entity = new LoginFailureEntity();
|
||||||
entity.setRealm(realm.getId());
|
entity.setRealm(realm.getId());
|
||||||
entity.setUserId(userId);
|
entity.setUserId(userId);
|
||||||
tx.put(loginFailureCache, key, entity);
|
|
||||||
|
SessionUpdateTask<LoginFailureEntity> createLoginFailureTask = new SessionUpdateTask<LoginFailureEntity>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void runUpdate(LoginFailureEntity session) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CacheOperation getOperation(LoginFailureEntity session) {
|
||||||
|
return CacheOperation.ADD_IF_ABSENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<LoginFailureEntity> sessionWrapper) {
|
||||||
|
return CrossDCMessageStatus.SYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
loginFailuresTx.addTask(key, createLoginFailureTask, entity);
|
||||||
|
|
||||||
return wrap(key, entity);
|
return wrap(key, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeUserLoginFailure(RealmModel realm, String userId) {
|
public void removeUserLoginFailure(RealmModel realm, String userId) {
|
||||||
tx.remove(loginFailureCache, new LoginFailureKey(realm.getId(), userId));
|
SessionUpdateTask<LoginFailureEntity> removeTask = new SessionUpdateTask<LoginFailureEntity>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void runUpdate(LoginFailureEntity entity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CacheOperation getOperation(LoginFailureEntity entity) {
|
||||||
|
return CacheOperation.REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<LoginFailureEntity> sessionWrapper) {
|
||||||
|
return CrossDCMessageStatus.SYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
loginFailuresTx.addTask(new LoginFailureKey(realm.getId(), userId), removeTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -543,9 +589,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
private void removeAllLocalUserLoginFailuresEvent(String realmId) {
|
private void removeAllLocalUserLoginFailuresEvent(String realmId) {
|
||||||
FuturesHelper futures = new FuturesHelper();
|
FuturesHelper futures = new FuturesHelper();
|
||||||
|
|
||||||
Cache<LoginFailureKey, LoginFailureEntity> localCache = CacheDecorators.localCache(loginFailureCache);
|
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> localCache = CacheDecorators.localCache(loginFailureCache);
|
||||||
|
|
||||||
Cache<LoginFailureKey, LoginFailureEntity> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
|
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
|
||||||
|
|
||||||
localCacheStoreIgnore
|
localCacheStoreIgnore
|
||||||
.entrySet()
|
.entrySet()
|
||||||
|
@ -593,8 +639,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
removeUserSessions(realm, user, true);
|
removeUserSessions(realm, user, true);
|
||||||
removeUserSessions(realm, user, false);
|
removeUserSessions(realm, user, false);
|
||||||
|
|
||||||
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getUsername()));
|
removeUserLoginFailure(realm, user.getId());
|
||||||
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -602,7 +647,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeUserSession(UserSessionEntity sessionEntity, boolean offline) {
|
protected void removeUserSession(UserSessionEntity sessionEntity, boolean offline) {
|
||||||
InfinispanChangelogBasedTransaction<UserSessionEntity> tx = getTransaction(offline);
|
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
|
||||||
|
|
||||||
SessionUpdateTask<UserSessionEntity> removeTask = new SessionUpdateTask<UserSessionEntity>() {
|
SessionUpdateTask<UserSessionEntity> removeTask = new SessionUpdateTask<UserSessionEntity>() {
|
||||||
|
|
||||||
|
@ -626,17 +671,17 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
tx.addTask(sessionEntity.getId(), removeTask);
|
tx.addTask(sessionEntity.getId(), removeTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
InfinispanKeycloakTransaction getTx() {
|
InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> getLoginFailuresTx() {
|
||||||
return tx;
|
return loginFailuresTx;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) {
|
UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) {
|
||||||
InfinispanChangelogBasedTransaction<UserSessionEntity> tx = getTransaction(offline);
|
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
|
||||||
return entity != null ? new UserSessionAdapter(session, this, tx, realm, entity, offline) : null;
|
return entity != null ? new UserSessionAdapter(session, this, tx, realm, entity, offline) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
|
UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
|
||||||
return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
|
return entity != null ? new UserLoginFailureAdapter(this, key, entity) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserSessionEntity getUserSessionEntity(UserSessionModel userSession, boolean offline) {
|
UserSessionEntity getUserSessionEntity(UserSessionModel userSession, boolean offline) {
|
||||||
|
@ -739,7 +784,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
|
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
|
||||||
|
|
||||||
|
|
||||||
InfinispanChangelogBasedTransaction<UserSessionEntity> tx = getTransaction(offline);
|
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline);
|
||||||
|
|
||||||
SessionUpdateTask importTask = new SessionUpdateTask<UserSessionEntity>() {
|
SessionUpdateTask importTask = new SessionUpdateTask<UserSessionEntity>() {
|
||||||
|
|
||||||
|
@ -775,7 +820,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
|
|
||||||
|
|
||||||
private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter importedUserSession, AuthenticatedClientSessionModel clientSession,
|
private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter importedUserSession, AuthenticatedClientSessionModel clientSession,
|
||||||
InfinispanChangelogBasedTransaction<UserSessionEntity> updateTx) {
|
InfinispanChangelogBasedTransaction<String, UserSessionEntity> updateTx) {
|
||||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
|
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
|
||||||
|
|
||||||
entity.setAction(clientSession.getAction());
|
entity.setAction(clientSession.getAction());
|
||||||
|
|
|
@ -85,7 +85,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
|
||||||
Cache<LoginFailureKey, LoginFailureEntity> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
||||||
|
|
||||||
return new InfinispanUserSessionProvider(session, remoteCacheInvoker, lastSessionRefreshStore, offlineLastSessionRefreshStore, cache, offlineSessionsCache, loginFailures);
|
return new InfinispanUserSessionProvider(session, remoteCacheInvoker, lastSessionRefreshStore, offlineLastSessionRefreshStore, cache, offlineSessionsCache, loginFailures);
|
||||||
}
|
}
|
||||||
|
@ -224,6 +224,11 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
if (offlineSessionsRemoteCache) {
|
if (offlineSessionsRemoteCache) {
|
||||||
offlineLastSessionRefreshStore = new LastSessionRefreshStoreFactory().createAndInit(session, offlineSessionsCache, true);
|
offlineLastSessionRefreshStore = new LastSessionRefreshStoreFactory().createAndInit(session, offlineSessionsCache, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cache loginFailuresCache = ispn.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
||||||
|
boolean loginFailuresRemoteCache = checkRemoteCache(session, loginFailuresCache, (RealmModel realm) -> {
|
||||||
|
return realm.getMaxDeltaTimeSeconds() * 1000;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkRemoteCache(KeycloakSession session, Cache ispnCache, RemoteCacheInvoker.MaxIdleTimeLoader maxIdleLoader) {
|
private boolean checkRemoteCache(KeycloakSession session, Cache ispnCache, RemoteCacheInvoker.MaxIdleTimeLoader maxIdleLoader) {
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.models.sessions.infinispan;
|
package org.keycloak.models.sessions.infinispan;
|
||||||
|
|
||||||
import org.infinispan.Cache;
|
|
||||||
import org.keycloak.models.UserLoginFailureModel;
|
import org.keycloak.models.UserLoginFailureModel;
|
||||||
|
import org.keycloak.models.sessions.infinispan.changes.LoginFailuresUpdateTask;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||||
|
|
||||||
|
@ -28,13 +28,11 @@ import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||||
public class UserLoginFailureAdapter implements UserLoginFailureModel {
|
public class UserLoginFailureAdapter implements UserLoginFailureModel {
|
||||||
|
|
||||||
private InfinispanUserSessionProvider provider;
|
private InfinispanUserSessionProvider provider;
|
||||||
private Cache<LoginFailureKey, LoginFailureEntity> cache;
|
|
||||||
private LoginFailureKey key;
|
private LoginFailureKey key;
|
||||||
private LoginFailureEntity entity;
|
private LoginFailureEntity entity;
|
||||||
|
|
||||||
public UserLoginFailureAdapter(InfinispanUserSessionProvider provider, Cache<LoginFailureKey, LoginFailureEntity> cache, LoginFailureKey key, LoginFailureEntity entity) {
|
public UserLoginFailureAdapter(InfinispanUserSessionProvider provider, LoginFailureKey key, LoginFailureEntity entity) {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.cache = cache;
|
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
}
|
}
|
||||||
|
@ -51,8 +49,16 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFailedLoginNotBefore(int notBefore) {
|
public void setFailedLoginNotBefore(int notBefore) {
|
||||||
entity.setFailedLoginNotBefore(notBefore);
|
LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() {
|
||||||
update();
|
|
||||||
|
@Override
|
||||||
|
public void runUpdate(LoginFailureEntity entity) {
|
||||||
|
entity.setFailedLoginNotBefore(notBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
update(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -62,14 +68,30 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementFailures() {
|
public void incrementFailures() {
|
||||||
entity.setNumFailures(getNumFailures() + 1);
|
LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() {
|
||||||
update();
|
|
||||||
|
@Override
|
||||||
|
public void runUpdate(LoginFailureEntity entity) {
|
||||||
|
entity.setNumFailures(entity.getNumFailures() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
update(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearFailures() {
|
public void clearFailures() {
|
||||||
entity.clearFailures();
|
LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() {
|
||||||
update();
|
|
||||||
|
@Override
|
||||||
|
public void runUpdate(LoginFailureEntity entity) {
|
||||||
|
entity.clearFailures();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
update(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -79,8 +101,16 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLastFailure(long lastFailure) {
|
public void setLastFailure(long lastFailure) {
|
||||||
entity.setLastFailure(lastFailure);
|
LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() {
|
||||||
update();
|
|
||||||
|
@Override
|
||||||
|
public void runUpdate(LoginFailureEntity entity) {
|
||||||
|
entity.setLastFailure(lastFailure);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
update(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -90,12 +120,20 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLastIPFailure(String ip) {
|
public void setLastIPFailure(String ip) {
|
||||||
entity.setLastIPFailure(ip);
|
LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() {
|
||||||
update();
|
|
||||||
|
@Override
|
||||||
|
public void runUpdate(LoginFailureEntity entity) {
|
||||||
|
entity.setLastIPFailure(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
update(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
void update() {
|
void update(LoginFailuresUpdateTask task) {
|
||||||
provider.getTx().replace(cache, key, entity);
|
provider.getLoginFailuresTx().addTask(key, task);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,18 +33,18 @@ import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extends AbstractKeycloakTransaction {
|
public class InfinispanChangelogBasedTransaction<K, V extends SessionEntity> extends AbstractKeycloakTransaction {
|
||||||
|
|
||||||
public static final Logger logger = Logger.getLogger(InfinispanChangelogBasedTransaction.class);
|
public static final Logger logger = Logger.getLogger(InfinispanChangelogBasedTransaction.class);
|
||||||
|
|
||||||
private final KeycloakSession kcSession;
|
private final KeycloakSession kcSession;
|
||||||
private final String cacheName;
|
private final String cacheName;
|
||||||
private final Cache<String, SessionEntityWrapper<S>> cache;
|
private final Cache<K, SessionEntityWrapper<V>> cache;
|
||||||
private final RemoteCacheInvoker remoteCacheInvoker;
|
private final RemoteCacheInvoker remoteCacheInvoker;
|
||||||
|
|
||||||
private final Map<String, SessionUpdatesList<S>> updates = new HashMap<>();
|
private final Map<K, SessionUpdatesList<V>> updates = new HashMap<>();
|
||||||
|
|
||||||
public InfinispanChangelogBasedTransaction(KeycloakSession kcSession, String cacheName, Cache<String, SessionEntityWrapper<S>> cache, RemoteCacheInvoker remoteCacheInvoker) {
|
public InfinispanChangelogBasedTransaction(KeycloakSession kcSession, String cacheName, Cache<K, SessionEntityWrapper<V>> cache, RemoteCacheInvoker remoteCacheInvoker) {
|
||||||
this.kcSession = kcSession;
|
this.kcSession = kcSession;
|
||||||
this.cacheName = cacheName;
|
this.cacheName = cacheName;
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
|
@ -52,11 +52,11 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addTask(String key, SessionUpdateTask<S> task) {
|
public void addTask(K key, SessionUpdateTask<V> task) {
|
||||||
SessionUpdatesList<S> myUpdates = updates.get(key);
|
SessionUpdatesList<V> myUpdates = updates.get(key);
|
||||||
if (myUpdates == null) {
|
if (myUpdates == null) {
|
||||||
// Lookup entity from cache
|
// Lookup entity from cache
|
||||||
SessionEntityWrapper<S> wrappedEntity = cache.get(key);
|
SessionEntityWrapper<V> wrappedEntity = cache.get(key);
|
||||||
if (wrappedEntity == null) {
|
if (wrappedEntity == null) {
|
||||||
logger.warnf("Not present cache item for key %s", key);
|
logger.warnf("Not present cache item for key %s", key);
|
||||||
return;
|
return;
|
||||||
|
@ -75,14 +75,14 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
|
||||||
|
|
||||||
|
|
||||||
// Create entity and new version for it
|
// Create entity and new version for it
|
||||||
public void addTask(String key, SessionUpdateTask<S> task, S entity) {
|
public void addTask(K key, SessionUpdateTask<V> task, V entity) {
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw new IllegalArgumentException("Null entity not allowed");
|
throw new IllegalArgumentException("Null entity not allowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
RealmModel realm = kcSession.realms().getRealm(entity.getRealm());
|
RealmModel realm = kcSession.realms().getRealm(entity.getRealm());
|
||||||
SessionEntityWrapper<S> wrappedEntity = new SessionEntityWrapper<>(entity);
|
SessionEntityWrapper<V> wrappedEntity = new SessionEntityWrapper<>(entity);
|
||||||
SessionUpdatesList<S> myUpdates = new SessionUpdatesList<>(realm, wrappedEntity);
|
SessionUpdatesList<V> myUpdates = new SessionUpdatesList<>(realm, wrappedEntity);
|
||||||
updates.put(key, myUpdates);
|
updates.put(key, myUpdates);
|
||||||
|
|
||||||
// Run the update now, so reader in same transaction can see it
|
// Run the update now, so reader in same transaction can see it
|
||||||
|
@ -91,19 +91,19 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void reloadEntityInCurrentTransaction(RealmModel realm, String key, SessionEntityWrapper<S> entity) {
|
public void reloadEntityInCurrentTransaction(RealmModel realm, K key, SessionEntityWrapper<V> entity) {
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw new IllegalArgumentException("Null entity not allowed");
|
throw new IllegalArgumentException("Null entity not allowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionEntityWrapper<S> latestEntity = cache.get(key);
|
SessionEntityWrapper<V> latestEntity = cache.get(key);
|
||||||
if (latestEntity == null) {
|
if (latestEntity == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionUpdatesList<S> newUpdates = new SessionUpdatesList<>(realm, latestEntity);
|
SessionUpdatesList<V> newUpdates = new SessionUpdatesList<>(realm, latestEntity);
|
||||||
|
|
||||||
SessionUpdatesList<S> existingUpdates = updates.get(key);
|
SessionUpdatesList<V> existingUpdates = updates.get(key);
|
||||||
if (existingUpdates != null) {
|
if (existingUpdates != null) {
|
||||||
newUpdates.setUpdateTasks(existingUpdates.getUpdateTasks());
|
newUpdates.setUpdateTasks(existingUpdates.getUpdateTasks());
|
||||||
}
|
}
|
||||||
|
@ -112,10 +112,10 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public SessionEntityWrapper<S> get(String key) {
|
public SessionEntityWrapper<V> get(K key) {
|
||||||
SessionUpdatesList<S> myUpdates = updates.get(key);
|
SessionUpdatesList<V> myUpdates = updates.get(key);
|
||||||
if (myUpdates == null) {
|
if (myUpdates == null) {
|
||||||
SessionEntityWrapper<S> wrappedEntity = cache.get(key);
|
SessionEntityWrapper<V> wrappedEntity = cache.get(key);
|
||||||
if (wrappedEntity == null) {
|
if (wrappedEntity == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
|
||||||
|
|
||||||
return wrappedEntity;
|
return wrappedEntity;
|
||||||
} else {
|
} else {
|
||||||
S entity = myUpdates.getEntityWrapper().getEntity();
|
V entity = myUpdates.getEntityWrapper().getEntity();
|
||||||
|
|
||||||
// If entity is scheduled for remove, we don't return it.
|
// If entity is scheduled for remove, we don't return it.
|
||||||
boolean scheduledForRemove = myUpdates.getUpdateTasks().stream().filter((SessionUpdateTask task) -> {
|
boolean scheduledForRemove = myUpdates.getUpdateTasks().stream().filter((SessionUpdateTask task) -> {
|
||||||
|
@ -143,13 +143,13 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void commitImpl() {
|
protected void commitImpl() {
|
||||||
for (Map.Entry<String, SessionUpdatesList<S>> entry : updates.entrySet()) {
|
for (Map.Entry<K, SessionUpdatesList<V>> entry : updates.entrySet()) {
|
||||||
SessionUpdatesList<S> sessionUpdates = entry.getValue();
|
SessionUpdatesList<V> sessionUpdates = entry.getValue();
|
||||||
SessionEntityWrapper<S> sessionWrapper = sessionUpdates.getEntityWrapper();
|
SessionEntityWrapper<V> sessionWrapper = sessionUpdates.getEntityWrapper();
|
||||||
|
|
||||||
RealmModel realm = sessionUpdates.getRealm();
|
RealmModel realm = sessionUpdates.getRealm();
|
||||||
|
|
||||||
MergedUpdate<S> merged = MergedUpdate.computeUpdate(sessionUpdates.getUpdateTasks(), sessionWrapper);
|
MergedUpdate<V> merged = MergedUpdate.computeUpdate(sessionUpdates.getUpdateTasks(), sessionWrapper);
|
||||||
|
|
||||||
if (merged != null) {
|
if (merged != null) {
|
||||||
// Now run the operation in our cluster
|
// Now run the operation in our cluster
|
||||||
|
@ -162,8 +162,8 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void runOperationInCluster(String key, MergedUpdate<S> task, SessionEntityWrapper<S> sessionWrapper) {
|
private void runOperationInCluster(K key, MergedUpdate<V> task, SessionEntityWrapper<V> sessionWrapper) {
|
||||||
S session = sessionWrapper.getEntity();
|
V session = sessionWrapper.getEntity();
|
||||||
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
|
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
|
||||||
|
|
||||||
// Don't need to run update of underlying entity. Local updates were already run
|
// Don't need to run update of underlying entity. Local updates were already run
|
||||||
|
@ -182,9 +182,14 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
|
||||||
.put(key, sessionWrapper, task.getLifespanMs(), TimeUnit.MILLISECONDS);
|
.put(key, sessionWrapper, task.getLifespanMs(), TimeUnit.MILLISECONDS);
|
||||||
break;
|
break;
|
||||||
case ADD_IF_ABSENT:
|
case ADD_IF_ABSENT:
|
||||||
SessionEntityWrapper existing = cache.putIfAbsent(key, sessionWrapper);
|
SessionEntityWrapper<V> existing = cache.putIfAbsent(key, sessionWrapper);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
throw new IllegalStateException("There is already existing value in cache for key " + key);
|
logger.debugf("Existing entity in cache for key: %s . Will update it", key);
|
||||||
|
|
||||||
|
// Apply updates on the existing entity and replace it
|
||||||
|
task.runUpdate(existing.getEntity());
|
||||||
|
|
||||||
|
replace(key, task, existing);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case REPLACE:
|
case REPLACE:
|
||||||
|
@ -197,12 +202,12 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void replace(String key, MergedUpdate<S> task, SessionEntityWrapper<S> oldVersionEntity) {
|
private void replace(K key, MergedUpdate<V> task, SessionEntityWrapper<V> oldVersionEntity) {
|
||||||
boolean replaced = false;
|
boolean replaced = false;
|
||||||
S session = oldVersionEntity.getEntity();
|
V session = oldVersionEntity.getEntity();
|
||||||
|
|
||||||
while (!replaced) {
|
while (!replaced) {
|
||||||
SessionEntityWrapper<S> newVersionEntity = generateNewVersionAndWrapEntity(session, oldVersionEntity.getLocalMetadata());
|
SessionEntityWrapper<V> newVersionEntity = generateNewVersionAndWrapEntity(session, oldVersionEntity.getLocalMetadata());
|
||||||
|
|
||||||
// Atomic cluster-aware replace
|
// Atomic cluster-aware replace
|
||||||
replaced = cache.replace(key, oldVersionEntity, newVersionEntity);
|
replaced = cache.replace(key, oldVersionEntity, newVersionEntity);
|
||||||
|
@ -235,7 +240,7 @@ public class InfinispanChangelogBasedTransaction<S extends SessionEntity> extend
|
||||||
protected void rollbackImpl() {
|
protected void rollbackImpl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private SessionEntityWrapper<S> generateNewVersionAndWrapEntity(S entity, Map<String, String> localMetadata) {
|
private SessionEntityWrapper<V> generateNewVersionAndWrapEntity(V entity, Map<String, String> localMetadata) {
|
||||||
return new SessionEntityWrapper<>(localMetadata, entity);
|
return new SessionEntityWrapper<>(localMetadata, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 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.changes;
|
||||||
|
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public abstract class LoginFailuresUpdateTask implements SessionUpdateTask<LoginFailureEntity> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CacheOperation getOperation(LoginFailureEntity session) {
|
||||||
|
return CacheOperation.REPLACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<LoginFailureEntity> sessionWrapper) {
|
||||||
|
return CrossDCMessageStatus.SYNC;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ class MergedUpdate<S extends SessionEntity> implements SessionUpdateTask<S> {
|
||||||
private CrossDCMessageStatus crossDCMessageStatus;
|
private CrossDCMessageStatus crossDCMessageStatus;
|
||||||
|
|
||||||
|
|
||||||
public MergedUpdate(CacheOperation operation, CrossDCMessageStatus crossDCMessageStatus) {
|
private MergedUpdate(CacheOperation operation, CrossDCMessageStatus crossDCMessageStatus) {
|
||||||
this.operation = operation;
|
this.operation = operation;
|
||||||
this.crossDCMessageStatus = crossDCMessageStatus;
|
this.crossDCMessageStatus = crossDCMessageStatus;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ public interface SessionUpdateTask<S extends SessionEntity> {
|
||||||
|
|
||||||
if (this == ADD | this == ADD_IF_ABSENT) {
|
if (this == ADD | this == ADD_IF_ABSENT) {
|
||||||
if (other == ADD | other == ADD_IF_ABSENT) {
|
if (other == ADD | other == ADD_IF_ABSENT) {
|
||||||
throw new IllegalStateException("Illegal state. Task already in progress for session " + entity.getId());
|
throw new IllegalStateException("Illegal state. Task already in progress for session " + entity.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.models.sessions.infinispan.changes;
|
package org.keycloak.models.sessions.infinispan.changes;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class LastSessionRefreshChecker {
|
||||||
|
|
||||||
Integer lsrr = sessionWrapper.getLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE);
|
Integer lsrr = sessionWrapper.getLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE);
|
||||||
if (lsrr == null) {
|
if (lsrr == null) {
|
||||||
logger.warnf("Not available lsrr note on user session %s.", sessionWrapper.getEntity().getId());
|
logger.debugf("Not available lsrr note on user session %s.", sessionWrapper.getEntity().getId());
|
||||||
return SessionUpdateTask.CrossDCMessageStatus.SYNC;
|
return SessionUpdateTask.CrossDCMessageStatus.SYNC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
|
@ -29,6 +30,8 @@ import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
*/
|
*/
|
||||||
public class AuthenticationSessionEntity extends SessionEntity {
|
public class AuthenticationSessionEntity extends SessionEntity {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
private String clientUuid;
|
private String clientUuid;
|
||||||
private String authUserId;
|
private String authUserId;
|
||||||
|
|
||||||
|
@ -46,6 +49,14 @@ public class AuthenticationSessionEntity extends SessionEntity {
|
||||||
private Set<String> requiredActions = new HashSet<>();
|
private Set<String> requiredActions = new HashSet<>();
|
||||||
private Map<String, String> userSessionNotes;
|
private Map<String, String> userSessionNotes;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
public String getClientUuid() {
|
public String getClientUuid() {
|
||||||
return clientUuid;
|
return clientUuid;
|
||||||
}
|
}
|
||||||
|
@ -149,4 +160,26 @@ public class AuthenticationSessionEntity extends SessionEntity {
|
||||||
public void setAuthNotes(Map<String, String> authNotes) {
|
public void setAuthNotes(Map<String, String> authNotes) {
|
||||||
this.authNotes = authNotes;
|
this.authNotes = authNotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof AuthenticationSessionEntity)) return false;
|
||||||
|
|
||||||
|
AuthenticationSessionEntity that = (AuthenticationSessionEntity) o;
|
||||||
|
|
||||||
|
if (id != null ? !id.equals(that.id) : that.id != null) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return id != null ? id.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("AuthenticationSessionEntity [id=%s, realm=%s, clientUuid=%s ]", getId(), getRealm(), getClientUuid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,12 @@
|
||||||
|
|
||||||
package org.keycloak.models.sessions.infinispan.entities;
|
package org.keycloak.models.sessions.infinispan.entities;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class LoginFailureEntity implements Serializable {
|
public class LoginFailureEntity extends SessionEntity {
|
||||||
|
|
||||||
private String userId;
|
private String userId;
|
||||||
private String realm;
|
|
||||||
private int failedLoginNotBefore;
|
private int failedLoginNotBefore;
|
||||||
private int numFailures;
|
private int numFailures;
|
||||||
private long lastFailure;
|
private long lastFailure;
|
||||||
|
@ -39,14 +36,6 @@ public class LoginFailureEntity implements Serializable {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRealm() {
|
|
||||||
return realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealm(String realm) {
|
|
||||||
this.realm = realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFailedLoginNotBefore() {
|
public int getFailedLoginNotBefore() {
|
||||||
return failedLoginNotBefore;
|
return failedLoginNotBefore;
|
||||||
}
|
}
|
||||||
|
@ -85,4 +74,30 @@ public class LoginFailureEntity implements Serializable {
|
||||||
this.lastFailure = 0;
|
this.lastFailure = 0;
|
||||||
this.lastIPFailure = null;
|
this.lastIPFailure = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof LoginFailureEntity)) return false;
|
||||||
|
|
||||||
|
LoginFailureEntity that = (LoginFailureEntity) o;
|
||||||
|
|
||||||
|
if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false;
|
||||||
|
if (getRealm() != null ? !getRealm().equals(that.getRealm()) : that.getRealm() != null) return false;
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int hashCode = getRealm() != null ? getRealm().hashCode() : 0;
|
||||||
|
hashCode = hashCode * 13 + (userId != null ? userId.hashCode() : 0);
|
||||||
|
return hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("LoginFailureEntity [ userId=%s, realm=%s, numFailures=%d ]", userId, getRealm(), numFailures);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,4 +52,9 @@ public class LoginFailureKey implements Serializable {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("LoginFailureKey [ realm=%s. userId=%s ]", realm, userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,17 +26,8 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
*/
|
*/
|
||||||
public abstract class SessionEntity implements Serializable {
|
public abstract class SessionEntity implements Serializable {
|
||||||
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
private String realm;
|
private String realm;
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(String id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRealm() {
|
public String getRealm() {
|
||||||
return realm;
|
return realm;
|
||||||
|
@ -46,26 +37,13 @@ public abstract class SessionEntity implements Serializable {
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (!(o instanceof SessionEntity)) return false;
|
|
||||||
|
|
||||||
SessionEntity that = (SessionEntity) o;
|
|
||||||
|
|
||||||
if (id != null ? !id.equals(that.id) : that.id != null) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return id != null ? id.hashCode() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) {
|
public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) {
|
||||||
throw new IllegalStateException("Not yet implemented");
|
if (localEntityWrapper == null) {
|
||||||
|
return new SessionEntityWrapper<>(this);
|
||||||
|
} else {
|
||||||
|
return new SessionEntityWrapper<>(localEntityWrapper.getLocalMetadata(), this);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,8 @@ public class UserSessionEntity extends SessionEntity {
|
||||||
// Metadata attribute, which contains the lastSessionRefresh available on remoteCache. Used in decide whether we need to write to remoteCache (DC) or not
|
// Metadata attribute, which contains the lastSessionRefresh available on remoteCache. Used in decide whether we need to write to remoteCache (DC) or not
|
||||||
public static final String LAST_SESSION_REFRESH_REMOTE = "lsrr";
|
public static final String LAST_SESSION_REFRESH_REMOTE = "lsrr";
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
private String user;
|
private String user;
|
||||||
|
|
||||||
private String brokerSessionId;
|
private String brokerSessionId;
|
||||||
|
@ -62,6 +64,14 @@ public class UserSessionEntity extends SessionEntity {
|
||||||
|
|
||||||
private UserSessionModel.State state;
|
private UserSessionModel.State state;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, String> notes = new ConcurrentHashMap<>();
|
private Map<String, String> notes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions = new ConcurrentHashMap<>();
|
private Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions = new ConcurrentHashMap<>();
|
||||||
|
@ -162,6 +172,23 @@ public class UserSessionEntity extends SessionEntity {
|
||||||
this.brokerUserId = brokerUserId;
|
this.brokerUserId = brokerUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof UserSessionEntity)) return false;
|
||||||
|
|
||||||
|
UserSessionEntity that = (UserSessionEntity) o;
|
||||||
|
|
||||||
|
if (id != null ? !id.equals(that.id) : that.id != null) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return id != null ? id.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("UserSessionEntity [id=%s, realm=%s, lastSessionRefresh=%d, clients=%s]", getId(), getRealm(), getLastSessionRefresh(),
|
return String.format("UserSessionEntity [id=%s, realm=%s, lastSessionRefresh=%d, clients=%s]", getId(), getRealm(), getLastSessionRefresh(),
|
||||||
|
|
|
@ -55,13 +55,13 @@ public class RemoteCacheInvoker {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public <S extends SessionEntity> void runTask(KeycloakSession kcSession, RealmModel realm, String cacheName, String key, SessionUpdateTask<S> task, SessionEntityWrapper<S> sessionWrapper) {
|
public <K, V extends SessionEntity> void runTask(KeycloakSession kcSession, RealmModel realm, String cacheName, K key, SessionUpdateTask<V> task, SessionEntityWrapper<V> sessionWrapper) {
|
||||||
RemoteCacheContext context = remoteCaches.get(cacheName);
|
RemoteCacheContext context = remoteCaches.get(cacheName);
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
S session = sessionWrapper.getEntity();
|
V session = sessionWrapper.getEntity();
|
||||||
|
|
||||||
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
|
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
|
||||||
SessionUpdateTask.CrossDCMessageStatus status = task.getCrossDCMessageStatus(sessionWrapper);
|
SessionUpdateTask.CrossDCMessageStatus status = task.getCrossDCMessageStatus(sessionWrapper);
|
||||||
|
@ -82,8 +82,8 @@ public class RemoteCacheInvoker {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private <S extends SessionEntity> void runOnRemoteCache(RemoteCache remoteCache, long maxIdleMs, String key, SessionUpdateTask<S> task, SessionEntityWrapper<S> sessionWrapper) {
|
private <K, V extends SessionEntity> void runOnRemoteCache(RemoteCache<K, V> remoteCache, long maxIdleMs, K key, SessionUpdateTask<V> task, SessionEntityWrapper<V> sessionWrapper) {
|
||||||
S session = sessionWrapper.getEntity();
|
V session = sessionWrapper.getEntity();
|
||||||
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
|
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
|
||||||
|
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
|
@ -96,13 +96,16 @@ public class RemoteCacheInvoker {
|
||||||
break;
|
break;
|
||||||
case ADD_IF_ABSENT:
|
case ADD_IF_ABSENT:
|
||||||
final int currentTime = Time.currentTime();
|
final int currentTime = Time.currentTime();
|
||||||
SessionEntity existing = (SessionEntity) remoteCache
|
SessionEntity existing = remoteCache
|
||||||
.withFlags(Flag.FORCE_RETURN_VALUE)
|
.withFlags(Flag.FORCE_RETURN_VALUE)
|
||||||
.putIfAbsent(key, session, -1, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
|
.putIfAbsent(key, session, -1, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
throw new IllegalStateException("There is already existing value in cache for key " + key);
|
logger.debugf("Existing entity in remote cache for key: %s . Will update it", key);
|
||||||
|
|
||||||
|
replace(remoteCache, task.getLifespanMs(), maxIdleMs, key, task);
|
||||||
|
} else {
|
||||||
|
sessionWrapper.putLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE, currentTime);
|
||||||
}
|
}
|
||||||
sessionWrapper.putLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE, currentTime);
|
|
||||||
break;
|
break;
|
||||||
case REPLACE:
|
case REPLACE:
|
||||||
replace(remoteCache, task.getLifespanMs(), maxIdleMs, key, task);
|
replace(remoteCache, task.getLifespanMs(), maxIdleMs, key, task);
|
||||||
|
@ -113,16 +116,16 @@ public class RemoteCacheInvoker {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private <S extends SessionEntity> void replace(RemoteCache remoteCache, long lifespanMs, long maxIdleMs, String key, SessionUpdateTask<S> task) {
|
private <K, V extends SessionEntity> void replace(RemoteCache<K, V> remoteCache, long lifespanMs, long maxIdleMs, K key, SessionUpdateTask<V> task) {
|
||||||
boolean replaced = false;
|
boolean replaced = false;
|
||||||
while (!replaced) {
|
while (!replaced) {
|
||||||
VersionedValue<S> versioned = remoteCache.getVersioned(key);
|
VersionedValue<V> versioned = remoteCache.getVersioned(key);
|
||||||
if (versioned == null) {
|
if (versioned == null) {
|
||||||
logger.warnf("Not found entity to replace for key '%s'", key);
|
logger.warnf("Not found entity to replace for key '%s'", key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
S session = versioned.getValue();
|
V session = versioned.getValue();
|
||||||
|
|
||||||
// Run task on the remote session
|
// Run task on the remote session
|
||||||
task.runUpdate(session);
|
task.runUpdate(session);
|
||||||
|
|
|
@ -44,12 +44,12 @@ import org.infinispan.client.hotrod.VersionedValue;
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
@ClientListener
|
@ClientListener
|
||||||
public class RemoteCacheSessionListener {
|
public class RemoteCacheSessionListener<K, V extends SessionEntity> {
|
||||||
|
|
||||||
protected static final Logger logger = Logger.getLogger(RemoteCacheSessionListener.class);
|
protected static final Logger logger = Logger.getLogger(RemoteCacheSessionListener.class);
|
||||||
|
|
||||||
private Cache<String, SessionEntityWrapper> cache;
|
private Cache<K, SessionEntityWrapper<V>> cache;
|
||||||
private RemoteCache remoteCache;
|
private RemoteCache<K, V> remoteCache;
|
||||||
private boolean distributed;
|
private boolean distributed;
|
||||||
private String myAddress;
|
private String myAddress;
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ public class RemoteCacheSessionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void init(KeycloakSession session, Cache<String, SessionEntityWrapper> cache, RemoteCache remoteCache) {
|
protected void init(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> cache, RemoteCache<K, V> remoteCache) {
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
this.remoteCache = remoteCache;
|
this.remoteCache = remoteCache;
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ public class RemoteCacheSessionListener {
|
||||||
|
|
||||||
@ClientCacheEntryCreated
|
@ClientCacheEntryCreated
|
||||||
public void created(ClientCacheEntryCreatedEvent event) {
|
public void created(ClientCacheEntryCreatedEvent event) {
|
||||||
String key = (String) event.getKey();
|
K key = (K) event.getKey();
|
||||||
|
|
||||||
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
|
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
|
||||||
// Should load it from remoteStore
|
// Should load it from remoteStore
|
||||||
|
@ -84,7 +84,7 @@ public class RemoteCacheSessionListener {
|
||||||
|
|
||||||
@ClientCacheEntryModified
|
@ClientCacheEntryModified
|
||||||
public void updated(ClientCacheEntryModifiedEvent event) {
|
public void updated(ClientCacheEntryModifiedEvent event) {
|
||||||
String key = (String) event.getKey();
|
K key = (K) event.getKey();
|
||||||
|
|
||||||
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
|
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ public class RemoteCacheSessionListener {
|
||||||
|
|
||||||
private static final int MAXIMUM_REPLACE_RETRIES = 10;
|
private static final int MAXIMUM_REPLACE_RETRIES = 10;
|
||||||
|
|
||||||
private void replaceRemoteEntityInCache(String key, long eventVersion) {
|
private void replaceRemoteEntityInCache(K key, long eventVersion) {
|
||||||
// TODO can be optimized and remoteSession sent in the event itself?
|
// TODO can be optimized and remoteSession sent in the event itself?
|
||||||
boolean replaced = false;
|
boolean replaced = false;
|
||||||
int replaceRetries = 0;
|
int replaceRetries = 0;
|
||||||
|
@ -102,8 +102,8 @@ public class RemoteCacheSessionListener {
|
||||||
do {
|
do {
|
||||||
replaceRetries++;
|
replaceRetries++;
|
||||||
|
|
||||||
SessionEntityWrapper localEntityWrapper = cache.get(key);
|
SessionEntityWrapper<V> localEntityWrapper = cache.get(key);
|
||||||
VersionedValue remoteSessionVersioned = remoteCache.getVersioned(key);
|
VersionedValue<V> remoteSessionVersioned = remoteCache.getVersioned(key);
|
||||||
if (remoteSessionVersioned == null || remoteSessionVersioned.getVersion() < eventVersion) {
|
if (remoteSessionVersioned == null || remoteSessionVersioned.getVersion() < eventVersion) {
|
||||||
try {
|
try {
|
||||||
logger.debugf("Got replace remote entity event prematurely, will try again. Event version: %d, got: %d",
|
logger.debugf("Got replace remote entity event prematurely, will try again. Event version: %d, got: %d",
|
||||||
|
@ -120,7 +120,7 @@ public class RemoteCacheSessionListener {
|
||||||
|
|
||||||
logger.debugf("Read session%s. Entity read from remote cache: %s", replaceRetries > 1 ? "" : " again", remoteSession);
|
logger.debugf("Read session%s. Entity read from remote cache: %s", replaceRetries > 1 ? "" : " again", remoteSession);
|
||||||
|
|
||||||
SessionEntityWrapper sessionWrapper = remoteSession.mergeRemoteEntityWithLocalEntity(localEntityWrapper);
|
SessionEntityWrapper<V> sessionWrapper = remoteSession.mergeRemoteEntityWithLocalEntity(localEntityWrapper);
|
||||||
|
|
||||||
// We received event from remoteCache, so we won't update it back
|
// We received event from remoteCache, so we won't update it back
|
||||||
replaced = cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES)
|
replaced = cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES)
|
||||||
|
@ -135,7 +135,7 @@ public class RemoteCacheSessionListener {
|
||||||
|
|
||||||
@ClientCacheEntryRemoved
|
@ClientCacheEntryRemoved
|
||||||
public void removed(ClientCacheEntryRemovedEvent event) {
|
public void removed(ClientCacheEntryRemovedEvent event) {
|
||||||
String key = (String) event.getKey();
|
K key = (K) event.getKey();
|
||||||
|
|
||||||
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
|
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
|
||||||
// We received event from remoteCache, so we won't update it back
|
// We received event from remoteCache, so we won't update it back
|
||||||
|
@ -152,7 +152,7 @@ public class RemoteCacheSessionListener {
|
||||||
|
|
||||||
|
|
||||||
// For distributed caches, ensure that local modification is executed just on owner OR if event.isCommandRetried
|
// For distributed caches, ensure that local modification is executed just on owner OR if event.isCommandRetried
|
||||||
protected boolean shouldUpdateLocalCache(ClientEvent.Type type, String key, boolean commandRetried) {
|
protected boolean shouldUpdateLocalCache(ClientEvent.Type type, K key, boolean commandRetried) {
|
||||||
boolean result;
|
boolean result;
|
||||||
|
|
||||||
// Case when cache is stopping or stopped already
|
// Case when cache is stopping or stopped already
|
||||||
|
@ -184,7 +184,7 @@ public class RemoteCacheSessionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static RemoteCacheSessionListener createListener(KeycloakSession session, Cache<String, SessionEntityWrapper> cache, RemoteCache remoteCache) {
|
public static <K, V extends SessionEntity> RemoteCacheSessionListener createListener(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> cache, RemoteCache<K, V> remoteCache) {
|
||||||
/*boolean isCoordinator = InfinispanUtil.isCoordinator(cache);
|
/*boolean isCoordinator = InfinispanUtil.isCoordinator(cache);
|
||||||
|
|
||||||
// Just cluster coordinator will fetch userSessions from remote cache.
|
// Just cluster coordinator will fetch userSessions from remote cache.
|
||||||
|
@ -198,7 +198,7 @@ public class RemoteCacheSessionListener {
|
||||||
listener = new DontFetchInitialStateCacheListener();
|
listener = new DontFetchInitialStateCacheListener();
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
RemoteCacheSessionListener listener = new RemoteCacheSessionListener();
|
RemoteCacheSessionListener<K, V> listener = new RemoteCacheSessionListener<>();
|
||||||
listener.init(session, cache, remoteCache);
|
listener.init(session, cache, remoteCache);
|
||||||
|
|
||||||
return listener;
|
return listener;
|
||||||
|
|
|
@ -115,14 +115,14 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
|
||||||
|
|
||||||
for (Map.Entry<byte[], byte[]> entry : remoteObjects.entrySet()) {
|
for (Map.Entry<byte[], byte[]> entry : remoteObjects.entrySet()) {
|
||||||
try {
|
try {
|
||||||
String key = (String) marshaller.objectFromByteBuffer(entry.getKey());
|
Object key = marshaller.objectFromByteBuffer(entry.getKey());
|
||||||
SessionEntity entity = (SessionEntity) marshaller.objectFromByteBuffer(entry.getValue());
|
SessionEntity entity = (SessionEntity) marshaller.objectFromByteBuffer(entry.getValue());
|
||||||
|
|
||||||
SessionEntityWrapper entityWrapper = new SessionEntityWrapper(entity);
|
SessionEntityWrapper entityWrapper = new SessionEntityWrapper(entity);
|
||||||
|
|
||||||
decoratedCache.putAsync(key, entityWrapper);
|
decoratedCache.putAsync(key, entityWrapper);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warnf("Error loading session from remote cache", e);
|
log.warn("Error loading session from remote cache", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class Mappers {
|
||||||
return new UserSessionEntityMapper();
|
return new UserSessionEntityMapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey> loginFailureId() {
|
public static Function<Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>>, LoginFailureKey> loginFailureId() {
|
||||||
return new LoginFailureIdMapper();
|
return new LoginFailureIdMapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,9 +103,9 @@ public class Mappers {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey>, Serializable {
|
private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>>, LoginFailureKey>, Serializable {
|
||||||
@Override
|
@Override
|
||||||
public LoginFailureKey apply(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
|
public LoginFailureKey apply(Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> entry) {
|
||||||
return entry.getKey();
|
return entry.getKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.models.sessions.infinispan.stream;
|
package org.keycloak.models.sessions.infinispan.stream;
|
||||||
|
|
||||||
|
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ import java.util.function.Predicate;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class UserLoginFailurePredicate implements Predicate<Map.Entry<LoginFailureKey, LoginFailureEntity>>, Serializable {
|
public class UserLoginFailurePredicate implements Predicate<Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>>>, Serializable {
|
||||||
|
|
||||||
private String realm;
|
private String realm;
|
||||||
|
|
||||||
|
@ -40,8 +41,8 @@ public class UserLoginFailurePredicate implements Predicate<Map.Entry<LoginFailu
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
|
public boolean test(Map.Entry<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> entry) {
|
||||||
LoginFailureEntity e = entry.getValue();
|
LoginFailureEntity e = entry.getValue().getEntity();
|
||||||
return realm.equals(e.getRealm());
|
return realm.equals(e.getRealm());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
|
import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
|
||||||
import org.keycloak.models.jpa.entities.CredentialEntity;
|
import org.keycloak.models.jpa.entities.CredentialEntity;
|
||||||
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserAttributeEntity;
|
|
||||||
import org.keycloak.models.jpa.entities.UserConsentEntity;
|
import org.keycloak.models.jpa.entities.UserConsentEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
|
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
||||||
|
@ -363,6 +362,18 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
|
||||||
|
UserEntity entity = em.getReference(UserEntity.class, user.getId());
|
||||||
|
entity.setNotBefore(notBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
|
||||||
|
UserEntity entity = em.getReference(UserEntity.class, user.getId());
|
||||||
|
return entity.getNotBefore();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
||||||
int num = em.createNamedQuery("grantRoleToAllUsers")
|
int num = em.createNamedQuery("grantRoleToAllUsers")
|
||||||
|
|
|
@ -103,6 +103,9 @@ public class UserEntity {
|
||||||
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
|
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
|
||||||
protected String serviceAccountClientLink;
|
protected String serviceAccountClientLink;
|
||||||
|
|
||||||
|
@Column(name="NOT_BEFORE")
|
||||||
|
protected int notBefore;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -224,6 +227,14 @@ public class UserEntity {
|
||||||
this.serviceAccountClientLink = serviceAccountClientLink;
|
this.serviceAccountClientLink = serviceAccountClientLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getNotBefore() {
|
||||||
|
return notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotBefore(int notBefore) {
|
||||||
|
this.notBefore = notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -416,6 +416,20 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotBeforeForUser(RealmModel realm, String userId, int notBefore) {
|
||||||
|
// Track it as attribute for now
|
||||||
|
String notBeforeStr = String.valueOf(notBefore);
|
||||||
|
setSingleAttribute(realm, userId, "fedNotBefore", notBeforeStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNotBeforeOfUser(RealmModel realm, String userId) {
|
||||||
|
MultivaluedHashMap<String, String> attrs = getAttributes(realm, userId);
|
||||||
|
String notBeforeStr = attrs.getFirst("fedNotBefore");
|
||||||
|
|
||||||
|
return notBeforeStr==null ? 0 : Integer.parseInt(notBeforeStr);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<GroupModel> getGroups(RealmModel realm, String userId) {
|
public Set<GroupModel> getGroups(RealmModel realm, String userId) {
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2017 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.
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||||
|
|
||||||
|
<changeSet author="keycloak" id="3.3.0">
|
||||||
|
<addColumn tableName="USER_ENTITY">
|
||||||
|
<column name="NOT_BEFORE" type="INT" defaultValueNumeric="0"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
|
@ -48,4 +48,5 @@
|
||||||
<include file="META-INF/jpa-changelog-2.5.1.xml"/>
|
<include file="META-INF/jpa-changelog-2.5.1.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-3.0.0.xml"/>
|
<include file="META-INF/jpa-changelog-3.0.0.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-3.2.0.xml"/>
|
<include file="META-INF/jpa-changelog-3.2.0.xml"/>
|
||||||
|
<include file="META-INF/jpa-changelog-3.3.0.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
6
pom.xml
6
pom.xml
|
@ -49,8 +49,8 @@
|
||||||
<eap.build-tools.version>1.2.2.Final</eap.build-tools.version>
|
<eap.build-tools.version>1.2.2.Final</eap.build-tools.version>
|
||||||
<wildfly.core.version>3.0.0.Beta30</wildfly.core.version>
|
<wildfly.core.version>3.0.0.Beta30</wildfly.core.version>
|
||||||
|
|
||||||
<version.org.wildfly.security.wildfly-elytron>1.1.0.Beta32</version.org.wildfly.security.wildfly-elytron>
|
<version.org.wildfly.security.wildfly-elytron>1.1.0.CR4</version.org.wildfly.security.wildfly-elytron>
|
||||||
<version.org.wildfly.security.elytron-web.undertow-server>1.0.0.Beta14</version.org.wildfly.security.elytron-web.undertow-server>
|
<version.org.wildfly.security.elytron-web.undertow-server>1.0.0.CR1</version.org.wildfly.security.elytron-web.undertow-server>
|
||||||
|
|
||||||
<aesh.version>0.66.15</aesh.version>
|
<aesh.version>0.66.15</aesh.version>
|
||||||
<apache.httpcomponents.version>4.5</apache.httpcomponents.version>
|
<apache.httpcomponents.version>4.5</apache.httpcomponents.version>
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
<jboss.as.plugin.version>7.5.Final</jboss.as.plugin.version>
|
<jboss.as.plugin.version>7.5.Final</jboss.as.plugin.version>
|
||||||
<jmeter.plugin.version>1.9.0</jmeter.plugin.version>
|
<jmeter.plugin.version>1.9.0</jmeter.plugin.version>
|
||||||
<jmeter.analysis.plugin.version>1.0.4</jmeter.analysis.plugin.version>
|
<jmeter.analysis.plugin.version>1.0.4</jmeter.analysis.plugin.version>
|
||||||
<minify.plugin.version>1.7.2</minify.plugin.version>
|
<minify.plugin.version>1.7.6</minify.plugin.version>
|
||||||
<osgi.bundle.plugin.version>2.3.7</osgi.bundle.plugin.version>
|
<osgi.bundle.plugin.version>2.3.7</osgi.bundle.plugin.version>
|
||||||
<wildfly.plugin.version>1.1.0.Final</wildfly.plugin.version>
|
<wildfly.plugin.version>1.1.0.Final</wildfly.plugin.version>
|
||||||
<nexus.staging.plugin.version>1.6.5</nexus.staging.plugin.version>
|
<nexus.staging.plugin.version>1.6.5</nexus.staging.plugin.version>
|
||||||
|
|
|
@ -49,7 +49,12 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean policyCheck(PasswordPolicy policy, CredentialModel credential) {
|
public boolean policyCheck(PasswordPolicy policy, CredentialModel credential) {
|
||||||
return credential.getHashIterations() == policy.getHashIterations() && providerId.equals(credential.getAlgorithm());
|
int policyHashIterations = policy.getHashIterations();
|
||||||
|
if (policyHashIterations == -1) {
|
||||||
|
policyHashIterations = defaultIterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
return credential.getHashIterations() == policyHashIterations && providerId.equals(credential.getAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -185,6 +185,8 @@ public class ModelToRepresentation {
|
||||||
rep.setDisableableCredentialTypes(session.userCredentialManager().getDisableableCredentialTypes(realm, user));
|
rep.setDisableableCredentialTypes(session.userCredentialManager().getDisableableCredentialTypes(realm, user));
|
||||||
rep.setFederationLink(user.getFederationLink());
|
rep.setFederationLink(user.getFederationLink());
|
||||||
|
|
||||||
|
rep.setNotBefore(session.users().getNotBeforeOfUser(realm, user));
|
||||||
|
|
||||||
List<String> reqActions = new ArrayList<String>();
|
List<String> reqActions = new ArrayList<String>();
|
||||||
Set<String> requiredActions = user.getRequiredActions();
|
Set<String> requiredActions = user.getRequiredActions();
|
||||||
for (String ra : requiredActions){
|
for (String ra : requiredActions){
|
||||||
|
|
|
@ -1454,6 +1454,11 @@ public class RepresentationToModel {
|
||||||
session.users().addConsent(newRealm, user.getId(), consentModel);
|
session.users().addConsent(newRealm, user.getId(), consentModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userRep.getNotBefore() != null) {
|
||||||
|
session.users().setNotBeforeForUser(newRealm, user, userRep.getNotBefore());
|
||||||
|
}
|
||||||
|
|
||||||
if (userRep.getServiceAccountClientId() != null) {
|
if (userRep.getServiceAccountClientId() != null) {
|
||||||
String clientId = userRep.getServiceAccountClientId();
|
String clientId = userRep.getServiceAccountClientId();
|
||||||
ClientModel client = newRealm.getClientByClientId(clientId);
|
ClientModel client = newRealm.getClientByClientId(clientId);
|
||||||
|
@ -2378,6 +2383,9 @@ public class RepresentationToModel {
|
||||||
federatedStorage.addConsent(newRealm, userRep.getId(), consentModel);
|
federatedStorage.addConsent(newRealm, userRep.getId(), consentModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (userRep.getNotBefore() != null) {
|
||||||
|
federatedStorage.setNotBeforeForUser(newRealm, userRep.getId(), userRep.getNotBefore());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,8 @@ public interface UserProvider extends Provider,
|
||||||
void updateConsent(RealmModel realm, String userId, UserConsentModel consent);
|
void updateConsent(RealmModel realm, String userId, UserConsentModel consent);
|
||||||
boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId);
|
boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId);
|
||||||
|
|
||||||
|
void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore);
|
||||||
|
int getNotBeforeOfUser(RealmModel realm, UserModel user);
|
||||||
|
|
||||||
UserModel getServiceAccount(ClientModel client);
|
UserModel getServiceAccount(ClientModel client);
|
||||||
List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
|
List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
|
||||||
|
|
|
@ -36,6 +36,7 @@ public interface UserFederatedStorageProvider extends Provider,
|
||||||
UserAttributeFederatedStorage,
|
UserAttributeFederatedStorage,
|
||||||
UserBrokerLinkFederatedStorage,
|
UserBrokerLinkFederatedStorage,
|
||||||
UserConsentFederatedStorage,
|
UserConsentFederatedStorage,
|
||||||
|
UserNotBeforeFederatedStorage,
|
||||||
UserGroupMembershipFederatedStorage,
|
UserGroupMembershipFederatedStorage,
|
||||||
UserRequiredActionsFederatedStorage,
|
UserRequiredActionsFederatedStorage,
|
||||||
UserRoleMappingsFederatedStorage,
|
UserRoleMappingsFederatedStorage,
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 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.storage.federated;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface UserNotBeforeFederatedStorage {
|
||||||
|
|
||||||
|
void setNotBeforeForUser(RealmModel realm, String userId, int notBefore);
|
||||||
|
int getNotBeforeOfUser(RealmModel realm, String userId);
|
||||||
|
}
|
|
@ -530,6 +530,10 @@ public class ExportUtils {
|
||||||
userRep.setClientConsents(consentReps);
|
userRep.setClientConsents(consentReps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not Before
|
||||||
|
int notBefore = session.users().getNotBeforeOfUser(realm, user);
|
||||||
|
userRep.setNotBefore(notBefore);
|
||||||
|
|
||||||
// Service account
|
// Service account
|
||||||
if (user.getServiceAccountClientLink() != null) {
|
if (user.getServiceAccountClientLink() != null) {
|
||||||
String clientInternalId = user.getServiceAccountClientLink();
|
String clientInternalId = user.getServiceAccountClientLink();
|
||||||
|
@ -717,6 +721,10 @@ public class ExportUtils {
|
||||||
userRep.setClientConsents(consentReps);
|
userRep.setClientConsents(consentReps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not Before
|
||||||
|
int notBefore = session.userFederatedStorage().getNotBeforeOfUser(realm, userRep.getId());
|
||||||
|
userRep.setNotBefore(notBefore);
|
||||||
|
|
||||||
if (options.isGroupsAndRolesIncluded()) {
|
if (options.isGroupsAndRolesIncluded()) {
|
||||||
List<String> groups = new LinkedList<>();
|
List<String> groups = new LinkedList<>();
|
||||||
for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) {
|
for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) {
|
||||||
|
|
|
@ -180,9 +180,10 @@ public abstract class AuthorizationEndpointBase {
|
||||||
return new AuthorizationEndpointChecks(authSession);
|
return new AuthorizationEndpointChecks(authSession);
|
||||||
|
|
||||||
} else if (isNewRequest(authSession, client, requestState)) {
|
} else if (isNewRequest(authSession, client, requestState)) {
|
||||||
// Check if we have lastProcessedExecution and restart the session just if yes. Otherwise update just client information from the AuthorizationEndpoint request.
|
// Check if we have lastProcessedExecution note or if some request parameter beside state (eg. prompt, kc_idp_hint) changed. Restart the session just if yes.
|
||||||
|
// Otherwise update just client information from the AuthorizationEndpoint request.
|
||||||
// This difference is needed, because of logout from JS applications in multiple browser tabs.
|
// This difference is needed, because of logout from JS applications in multiple browser tabs.
|
||||||
if (hasProcessedExecution(authSession)) {
|
if (shouldRestartAuthSession(authSession)) {
|
||||||
logger.debug("New request from application received, but authentication session already exists. Restart existing authentication session");
|
logger.debug("New request from application received, but authentication session already exists. Restart existing authentication session");
|
||||||
authSession.restartSession(realm, client);
|
authSession.restartSession(realm, client);
|
||||||
} else {
|
} else {
|
||||||
|
@ -223,11 +224,18 @@ public abstract class AuthorizationEndpointBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected boolean shouldRestartAuthSession(AuthenticationSessionModel authSession) {
|
||||||
|
return hasProcessedExecution(authSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean hasProcessedExecution(AuthenticationSessionModel authSession) {
|
private boolean hasProcessedExecution(AuthenticationSessionModel authSession) {
|
||||||
String lastProcessedExecution = authSession.getAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION);
|
String lastProcessedExecution = authSession.getAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION);
|
||||||
return (lastProcessedExecution != null);
|
return (lastProcessedExecution != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// See if we have lastProcessedExecution note. If yes, we are expired. Also if we are in different flow than initial one. Otherwise it is browser refresh of initial username/password form
|
// See if we have lastProcessedExecution note. If yes, we are expired. Also if we are in different flow than initial one. Otherwise it is browser refresh of initial username/password form
|
||||||
private boolean shouldShowExpirePage(AuthenticationSessionModel authSession) {
|
private boolean shouldShowExpirePage(AuthenticationSessionModel authSession) {
|
||||||
if (hasProcessedExecution(authSession)) {
|
if (hasProcessedExecution(authSession)) {
|
||||||
|
|
|
@ -180,6 +180,9 @@ public class TokenManager {
|
||||||
if (oldToken.getIssuedAt() < realm.getNotBefore()) {
|
if (oldToken.getIssuedAt() < realm.getNotBefore()) {
|
||||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
|
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
|
||||||
}
|
}
|
||||||
|
if (oldToken.getIssuedAt() < session.users().getNotBeforeOfUser(realm, user)) {
|
||||||
|
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// recreate token.
|
// recreate token.
|
||||||
|
@ -207,9 +210,12 @@ public class TokenManager {
|
||||||
if (!user.isEnabled()) {
|
if (!user.isEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (token.getIssuedAt() < session.users().getNotBeforeOfUser(realm, user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
||||||
if (client == null || !client.isEnabled()) {
|
if (client == null || !client.isEnabled() || token.getIssuedAt() < client.getNotBefore()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -816,9 +822,13 @@ public class TokenManager {
|
||||||
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());
|
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int notBefore = realm.getNotBefore();
|
int notBefore = realm.getNotBefore();
|
||||||
if (client.getNotBefore() > notBefore) notBefore = client.getNotBefore();
|
if (client.getNotBefore() > notBefore) notBefore = client.getNotBefore();
|
||||||
|
int userNotBefore = session.users().getNotBeforeOfUser(realm, userSession.getUser());
|
||||||
|
if (userNotBefore > notBefore) notBefore = userNotBefore;
|
||||||
res.setNotBeforePolicy(notBefore);
|
res.setNotBeforePolicy(notBefore);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,8 @@ import org.keycloak.util.TokenUtil;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@ -370,7 +372,48 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
||||||
|
|
||||||
// If state is same, we likely have the refresh of some previous request
|
// If state is same, we likely have the refresh of some previous request
|
||||||
String stateFromSession = authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM);
|
String stateFromSession = authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM);
|
||||||
return !stateFromRequest.equals(stateFromSession);
|
boolean stateChanged =!stateFromRequest.equals(stateFromSession);
|
||||||
|
if (stateChanged) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isOIDCAuthenticationRelatedParamsChanged(authSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldRestartAuthSession(AuthenticationSessionModel authSession) {
|
||||||
|
return super.shouldRestartAuthSession(authSession) || isOIDCAuthenticationRelatedParamsChanged(authSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check if some important OIDC parameters, which have impact on authentication, changed. If yes, we need to restart auth session
|
||||||
|
private boolean isOIDCAuthenticationRelatedParamsChanged(AuthenticationSessionModel authSession) {
|
||||||
|
if (isRequestParamChanged(authSession, OIDCLoginProtocol.LOGIN_HINT_PARAM, request.getLoginHint())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isRequestParamChanged(authSession, OIDCLoginProtocol.PROMPT_PARAM, request.getPrompt())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isRequestParamChanged(authSession, AdapterConstants.KC_IDP_HINT, request.getIdpHint())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String maxAgeValue = authSession.getClientNote(OIDCLoginProtocol.MAX_AGE_PARAM);
|
||||||
|
if (maxAgeValue == null && request.getMaxAge() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (maxAgeValue != null && Integer.parseInt(maxAgeValue) == request.getMaxAge()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isRequestParamChanged(AuthenticationSessionModel authSession, String noteName, String requestParamValue) {
|
||||||
|
String authSessionNoteValue = authSession.getClientNote(noteName);
|
||||||
|
return !Objects.equals(authSessionNoteValue, requestParamValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@ import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.services.util.CacheControlUtil;
|
import org.keycloak.services.util.CacheControlUtil;
|
||||||
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.FormParam;
|
import javax.ws.rs.FormParam;
|
||||||
|
@ -67,7 +68,6 @@ import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -594,7 +594,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("clients/{client}")
|
@Path("clients/{client}")
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML_UTF_8)
|
||||||
public Response idpInitiatedSSO(@PathParam("client") String clientUrlName, @QueryParam("RelayState") String relayState) {
|
public Response idpInitiatedSSO(@PathParam("client") String clientUrlName, @QueryParam("RelayState") String relayState) {
|
||||||
event.event(EventType.LOGIN);
|
event.event(EventType.LOGIN);
|
||||||
CacheControlUtil.noBackButtonCacheControlHeader();
|
CacheControlUtil.noBackButtonCacheControlHeader();
|
||||||
|
|
|
@ -817,6 +817,12 @@ public class AuthenticationManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int userNotBefore = session.users().getNotBeforeOfUser(realm, user);
|
||||||
|
if (token.getIssuedAt() < userNotBefore) {
|
||||||
|
logger.debug("User notBefore newer than token");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
|
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
|
||||||
if (!isSessionValid(realm, userSession)) {
|
if (!isSessionValid(realm, userSession)) {
|
||||||
// Check if accessToken was for the offline session.
|
// Check if accessToken was for the offline session.
|
||||||
|
|
|
@ -107,6 +107,8 @@ public class ResourceAdminManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logoutUser(URI requestUri, RealmModel realm, UserModel user, KeycloakSession keycloakSession) {
|
public void logoutUser(URI requestUri, RealmModel realm, UserModel user, KeycloakSession keycloakSession) {
|
||||||
|
keycloakSession.users().setNotBeforeForUser(realm, user, Time.currentTime());
|
||||||
|
|
||||||
List<UserSessionModel> userSessions = keycloakSession.sessions().getUserSessions(realm, user);
|
List<UserSessionModel> userSessions = keycloakSession.sessions().getUserSessions(realm, user);
|
||||||
logoutUserSessions(requestUri, realm, userSessions);
|
logoutUserSessions(requestUri, realm, userSessions);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.services.resources;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
@ -59,9 +60,9 @@ import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import org.keycloak.storage.ReadOnlyException;
|
import org.keycloak.storage.ReadOnlyException;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
@ -252,33 +253,24 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
*/
|
*/
|
||||||
@Path("/")
|
@Path("/")
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.TEXT_HTML)
|
|
||||||
public Response accountPage() {
|
public Response accountPage() {
|
||||||
return forwardToPage(null, AccountPages.ACCOUNT);
|
if (session.getContext().getRequestHeaders().getAcceptableMediaTypes().contains(MediaType.APPLICATION_JSON_TYPE)) {
|
||||||
}
|
requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
|
||||||
|
|
||||||
/**
|
UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, auth.getUser());
|
||||||
* Get account information.
|
if (rep.getAttributes() != null) {
|
||||||
*
|
Iterator<String> itr = rep.getAttributes().keySet().iterator();
|
||||||
* @return
|
while (itr.hasNext()) {
|
||||||
*/
|
if (itr.next().startsWith("keycloak.")) {
|
||||||
@Path("/")
|
itr.remove();
|
||||||
@GET
|
}
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
|
||||||
public Response accountPageJson() {
|
|
||||||
requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
|
|
||||||
|
|
||||||
UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, auth.getUser());
|
|
||||||
if (rep.getAttributes() != null) {
|
|
||||||
Iterator<String> itr = rep.getAttributes().keySet().iterator();
|
|
||||||
while (itr.hasNext()) {
|
|
||||||
if (itr.next().startsWith("keycloak.")) {
|
|
||||||
itr.remove();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Cors.add(request, Response.ok(rep)).auth().allowedOrigins(auth.getToken()).build();
|
return Cors.add(request, Response.ok(rep).type(MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(auth.getToken()).build();
|
||||||
|
} else {
|
||||||
|
return forwardToPage(null, AccountPages.ACCOUNT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder totpUrl(UriBuilder base) {
|
public static UriBuilder totpUrl(UriBuilder base) {
|
||||||
|
@ -514,6 +506,11 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
csrfCheck(stateChecker);
|
csrfCheck(stateChecker);
|
||||||
|
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
|
// Rather decrease time a bit. To avoid situation when user is immediatelly redirected to login screen, then automatically authenticated (eg. with Kerberos) and then seeing issues due the stale token
|
||||||
|
// as time on the token will be same like notBefore
|
||||||
|
session.users().setNotBeforeForUser(realm, user, Time.currentTime() - 1);
|
||||||
|
|
||||||
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
||||||
for (UserSessionModel userSession : userSessions) {
|
for (UserSessionModel userSession : userSessions) {
|
||||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||||
|
|
|
@ -509,10 +509,13 @@ public class AuthenticationManagementResource {
|
||||||
rep.setId(execution.getId());
|
rep.setId(execution.getId());
|
||||||
|
|
||||||
if (factory.isConfigurable()) {
|
if (factory.isConfigurable()) {
|
||||||
AuthenticatorConfigModel authenticatorConfig = realm.getAuthenticatorConfigById(execution.getAuthenticatorConfig());
|
String authenticatorConfigId = execution.getAuthenticatorConfig();
|
||||||
|
if(authenticatorConfigId != null) {
|
||||||
|
AuthenticatorConfigModel authenticatorConfig = realm.getAuthenticatorConfigById(authenticatorConfigId);
|
||||||
|
|
||||||
if (authenticatorConfig != null) {
|
if (authenticatorConfig != null) {
|
||||||
rep.setAlias(authenticatorConfig.getAlias());
|
rep.setAlias(authenticatorConfig.getAlias());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,8 @@ public class RealmsAdminResource {
|
||||||
|
|
||||||
return Response.created(location).build();
|
return Response.created(location).build();
|
||||||
} catch (ModelDuplicateException e) {
|
} catch (ModelDuplicateException e) {
|
||||||
return ErrorResponse.exists("Realm with same name exists");
|
logger.error("Conflict detected", e);
|
||||||
|
return ErrorResponse.exists("Conflict detected. See logs for details");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -508,6 +508,8 @@ public class UserResource {
|
||||||
public void logout() {
|
public void logout() {
|
||||||
auth.users().requireManage(user);
|
auth.users().requireManage(user);
|
||||||
|
|
||||||
|
session.users().setNotBeforeForUser(realm, user, Time.currentTime());
|
||||||
|
|
||||||
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
||||||
for (UserSessionModel userSession : userSessions) {
|
for (UserSessionModel userSession : userSessions) {
|
||||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||||
|
|
|
@ -231,6 +231,25 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
|
||||||
|
if (StorageId.isLocalStorage(user)) {
|
||||||
|
localStorage().setNotBeforeForUser(realm, user, notBefore);
|
||||||
|
} else {
|
||||||
|
getFederatedStorage().setNotBeforeForUser(realm, user.getId(), notBefore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
|
||||||
|
if (StorageId.isLocalStorage(user)) {
|
||||||
|
return localStorage().getNotBeforeOfUser(realm, user);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return getFederatedStorage().getNotBeforeOfUser(realm, user.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows a UserStorageProvider to proxy and/or synchronize an imported user.
|
* Allows a UserStorageProvider to proxy and/or synchronize an imported user.
|
||||||
*
|
*
|
||||||
|
|
|
@ -40,4 +40,8 @@ public class MediaType {
|
||||||
public static final String APPLICATION_JWT = "application/jwt";
|
public static final String APPLICATION_JWT = "application/jwt";
|
||||||
public static final javax.ws.rs.core.MediaType APPLICATION_JWT_TYPE = new javax.ws.rs.core.MediaType("application", "jwt");
|
public static final javax.ws.rs.core.MediaType APPLICATION_JWT_TYPE = new javax.ws.rs.core.MediaType("application", "jwt");
|
||||||
|
|
||||||
|
public static final String APPLICATION_XML = javax.ws.rs.core.MediaType.APPLICATION_XML;
|
||||||
|
|
||||||
|
public static final String TEXT_XML = javax.ws.rs.core.MediaType.TEXT_XML;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,9 +45,9 @@
|
||||||
<selenium.version>2.53.0</selenium.version>
|
<selenium.version>2.53.0</selenium.version>
|
||||||
<arquillian-drone.version>2.0.1.Final</arquillian-drone.version>
|
<arquillian-drone.version>2.0.1.Final</arquillian-drone.version>
|
||||||
<arquillian-graphene.version>2.1.0.Alpha3</arquillian-graphene.version>
|
<arquillian-graphene.version>2.1.0.Alpha3</arquillian-graphene.version>
|
||||||
<arquillian-wildfly-container.version>2.1.0.Alpha2</arquillian-wildfly-container.version>
|
<arquillian-wildfly-container.version>2.1.0.Beta1</arquillian-wildfly-container.version>
|
||||||
<arquillian-wls-container.version>1.0.1.Final</arquillian-wls-container.version>
|
<arquillian-wls-container.version>1.0.1.Final</arquillian-wls-container.version>
|
||||||
<arquillian-infinispan-container.version>1.2.0.Beta2</arquillian-infinispan-container.version>
|
<arquillian-infinispan-container.version>1.2.0.Alpha2</arquillian-infinispan-container.version>
|
||||||
<version.shrinkwrap.resolvers>2.2.2</version.shrinkwrap.resolvers>
|
<version.shrinkwrap.resolvers>2.2.2</version.shrinkwrap.resolvers>
|
||||||
<undertow-embedded.version>1.0.0.Alpha2</undertow-embedded.version>
|
<undertow-embedded.version>1.0.0.Alpha2</undertow-embedded.version>
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.rest;
|
package org.keycloak.testsuite.rest;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.Query;
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
@ -30,6 +29,7 @@ import org.keycloak.representations.adapters.action.TestAvailabilityAction;
|
||||||
import org.keycloak.services.resource.RealmResourceProvider;
|
import org.keycloak.services.resource.RealmResourceProvider;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
||||||
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
@ -38,7 +38,6 @@ import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
@ -69,21 +68,21 @@ public class TestApplicationResourceProvider implements RealmResourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.TEXT_PLAIN)
|
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||||
@Path("/admin/k_logout")
|
@Path("/admin/k_logout")
|
||||||
public void adminLogout(String data) throws JWSInputException {
|
public void adminLogout(String data) throws JWSInputException {
|
||||||
adminLogoutActions.add(new JWSInput(data).readJsonContent(LogoutAction.class));
|
adminLogoutActions.add(new JWSInput(data).readJsonContent(LogoutAction.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.TEXT_PLAIN)
|
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||||
@Path("/admin/k_push_not_before")
|
@Path("/admin/k_push_not_before")
|
||||||
public void adminPushNotBefore(String data) throws JWSInputException {
|
public void adminPushNotBefore(String data) throws JWSInputException {
|
||||||
adminPushNotBeforeActions.add(new JWSInput(data).readJsonContent(PushNotBeforeAction.class));
|
adminPushNotBeforeActions.add(new JWSInput(data).readJsonContent(PushNotBeforeAction.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.TEXT_PLAIN)
|
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||||
@Path("/admin/k_test_available")
|
@Path("/admin/k_test_available")
|
||||||
public void testAvailable(String data) throws JWSInputException {
|
public void testAvailable(String data) throws JWSInputException {
|
||||||
adminTestAvailabilityAction.add(new JWSInput(data).readJsonContent(TestAvailabilityAction.class));
|
adminTestAvailabilityAction.add(new JWSInput(data).readJsonContent(TestAvailabilityAction.class));
|
||||||
|
@ -119,7 +118,7 @@ public class TestApplicationResourceProvider implements RealmResourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML_UTF_8)
|
||||||
@Path("/{action}")
|
@Path("/{action}")
|
||||||
public String post(@PathParam("action") String action) {
|
public String post(@PathParam("action") String action) {
|
||||||
String title = "APP_REQUEST";
|
String title = "APP_REQUEST";
|
||||||
|
@ -148,7 +147,7 @@ public class TestApplicationResourceProvider implements RealmResourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML_UTF_8)
|
||||||
@Path("/{action}")
|
@Path("/{action}")
|
||||||
public String get(@PathParam("action") String action) {
|
public String get(@PathParam("action") String action) {
|
||||||
//String requestUri = session.getContext().getUri().getRequestUri().toString();
|
//String requestUri = session.getContext().getUri().getRequestUri().toString();
|
||||||
|
@ -171,7 +170,7 @@ public class TestApplicationResourceProvider implements RealmResourceProvider {
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML_UTF_8)
|
||||||
@Path("/get-account-profile")
|
@Path("/get-account-profile")
|
||||||
public String getAccountProfile(@QueryParam("token") String token, @QueryParam("account-uri") String accountUri) {
|
public String getAccountProfile(@QueryParam("token") String token, @QueryParam("account-uri") String accountUri) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
|
@ -27,18 +27,17 @@ import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
|
|
||||||
import org.infinispan.Cache;
|
import org.infinispan.Cache;
|
||||||
import org.infinispan.client.hotrod.RemoteCache;
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
import org.infinispan.remoting.transport.Transport;
|
import org.infinispan.remoting.transport.Transport;
|
||||||
import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
|
|
||||||
import org.jgroups.JChannel;
|
import org.jgroups.JChannel;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
||||||
import org.keycloak.testsuite.rest.representation.JGroupsStats;
|
import org.keycloak.testsuite.rest.representation.JGroupsStats;
|
||||||
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -82,7 +81,7 @@ public class TestCacheResource {
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/clear")
|
@Path("/clear")
|
||||||
@Consumes(MediaType.TEXT_PLAIN)
|
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||||
public void clear() {
|
public void clear() {
|
||||||
cache.clear();
|
cache.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite.adapter.page;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
|
||||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
|
||||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author tkyjovsk
|
|
||||||
*/
|
|
||||||
public class BasicAuthExample extends AbstractPageWithInjectedUrl {
|
|
||||||
|
|
||||||
public static final String DEPLOYMENT_NAME = "basic-auth-example";
|
|
||||||
|
|
||||||
@ArquillianResource
|
|
||||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
|
||||||
private URL url;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getInjectedUrl() {
|
|
||||||
//EAP6 URL fix
|
|
||||||
URL fixedUrl = createInjectedURL("basicauth");
|
|
||||||
return fixedUrl != null ? fixedUrl : url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UriBuilder createUriBuilder() {
|
|
||||||
return super.createUriBuilder()
|
|
||||||
.userInfo("{user}:{password}")
|
|
||||||
.path("service/echo")
|
|
||||||
.queryParam("value", "{value}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public BasicAuthExample setTemplateValues(String user, String password, String value) {
|
|
||||||
setUriParameter("user", user);
|
|
||||||
setUriParameter("password", password);
|
|
||||||
setUriParameter("value", value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite.adapter.page;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
|
||||||
import org.jboss.arquillian.graphene.findby.FindByJQuery;
|
|
||||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
|
||||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author tkyjovsk
|
|
||||||
*/
|
|
||||||
public class CustomerPortalExample extends AbstractPageWithInjectedUrl {
|
|
||||||
|
|
||||||
public static final String DEPLOYMENT_NAME = "customer-portal-example";
|
|
||||||
|
|
||||||
@ArquillianResource
|
|
||||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
|
||||||
private URL url;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getInjectedUrl() {
|
|
||||||
//EAP6 URL fix
|
|
||||||
URL fixedUrl = createInjectedURL("customer-portal");
|
|
||||||
return fixedUrl != null ? fixedUrl : url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FindByJQuery("h1:contains('Customer Portal')")
|
|
||||||
private WebElement title;
|
|
||||||
|
|
||||||
@FindByJQuery("a:contains('Customer Listing')")
|
|
||||||
private WebElement customerListingLink;
|
|
||||||
@FindByJQuery("h1:contains('Customer Listing')")
|
|
||||||
private WebElement customerListingHeader;
|
|
||||||
|
|
||||||
@FindByJQuery("h1:contains('Customer Session')")
|
|
||||||
private WebElement customerSessionHeader;
|
|
||||||
|
|
||||||
@FindByJQuery("a:contains('Customer Admin Interface')")
|
|
||||||
private WebElement customerAdminInterfaceLink;
|
|
||||||
|
|
||||||
@FindByJQuery("a:contains('Customer Session')")
|
|
||||||
private WebElement customerSessionLink;
|
|
||||||
|
|
||||||
@FindByJQuery("a:contains('products')")
|
|
||||||
private WebElement productsLink;
|
|
||||||
|
|
||||||
@FindByJQuery("a:contains('logout')")
|
|
||||||
private WebElement logOutButton;
|
|
||||||
|
|
||||||
public void goToProducts() {
|
|
||||||
productsLink.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void customerListing() {
|
|
||||||
customerListingLink.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void customerAdminInterface() {
|
|
||||||
customerAdminInterfaceLink.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void customerSession() {
|
|
||||||
waitUntilElement(customerSessionLink).is().present();
|
|
||||||
customerSessionLink.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logOut() {
|
|
||||||
logOutButton.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitForCustomerListingHeader() {
|
|
||||||
waitUntilElement(customerListingHeader).is().not().present();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitForCustomerSessionHeader() {
|
|
||||||
waitUntilElement(customerSessionHeader).is().not().present();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite.adapter.page;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
|
||||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
|
||||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author tkyjovsk
|
|
||||||
*/
|
|
||||||
public class DatabaseServiceExample extends AbstractPageWithInjectedUrl {
|
|
||||||
|
|
||||||
public static final String DEPLOYMENT_NAME = "database-service-example";
|
|
||||||
|
|
||||||
@ArquillianResource
|
|
||||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
|
||||||
private URL url;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getInjectedUrl() {
|
|
||||||
//EAP6 URL fix
|
|
||||||
URL fixedUrl = createInjectedURL("database");
|
|
||||||
return fixedUrl != null ? fixedUrl : url;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite.adapter.page;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
|
||||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
|
||||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author tkyjovsk
|
|
||||||
*/
|
|
||||||
public class MultiTenantExample extends AbstractPageWithInjectedUrl {
|
|
||||||
|
|
||||||
public static final String DEPLOYMENT_NAME = "multi-tenant-example";
|
|
||||||
|
|
||||||
@ArquillianResource
|
|
||||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
|
||||||
private URL url;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getInjectedUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UriBuilder createUriBuilder() {
|
|
||||||
return super.createUriBuilder().path("{tenantRealm}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public URL getTenantRealmUrl(String realm) {
|
|
||||||
try {
|
|
||||||
return getUriBuilder().build(realm).toURL();
|
|
||||||
} catch (MalformedURLException ex) {
|
|
||||||
throw new IllegalStateException("Page URL is malformed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void navigateToRealm(String realm) {
|
|
||||||
URL u = getTenantRealmUrl(realm);
|
|
||||||
log.info("navigate to "+u.toExternalForm());
|
|
||||||
driver.navigate().to(u.toExternalForm());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite.adapter.page;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
|
||||||
import org.jboss.arquillian.graphene.findby.FindByJQuery;
|
|
||||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
|
||||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author tkyjovsk
|
|
||||||
*/
|
|
||||||
public class ProductPortalExample extends AbstractPageWithInjectedUrl {
|
|
||||||
|
|
||||||
public static final String DEPLOYMENT_NAME = "product-portal-example";
|
|
||||||
|
|
||||||
@ArquillianResource
|
|
||||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
|
||||||
private URL url;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getInjectedUrl() {
|
|
||||||
//EAP6 URL fix
|
|
||||||
URL fixedUrl = createInjectedURL("product-portal");
|
|
||||||
return fixedUrl != null ? fixedUrl : url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FindByJQuery("h1:contains('Product Portal')")
|
|
||||||
private WebElement title;
|
|
||||||
|
|
||||||
@FindByJQuery("a:contains('Product Listing')")
|
|
||||||
private WebElement productListingLink;
|
|
||||||
@FindByJQuery("h1:contains('Product Listing')")
|
|
||||||
private WebElement productListingHeader;
|
|
||||||
|
|
||||||
@FindByJQuery("a:contains('customers')")
|
|
||||||
private WebElement customersLink;
|
|
||||||
|
|
||||||
@FindByJQuery("a:contains('logout')")
|
|
||||||
private WebElement logOutButton;
|
|
||||||
|
|
||||||
public void productListing() {
|
|
||||||
productListingLink.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void goToCustomers() {
|
|
||||||
customersLink.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitForProductListingHeader() {
|
|
||||||
waitUntilElement(productListingHeader).is().not().present();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logOut() {
|
|
||||||
logOutButton.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite.adapter.page;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
|
||||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
|
||||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
import org.openqa.selenium.support.FindBy;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author mhajas
|
|
||||||
*/
|
|
||||||
public class SAMLPostEncExample extends AbstractPageWithInjectedUrl {
|
|
||||||
public static final String DEPLOYMENT_NAME = "saml-post-encryption";
|
|
||||||
|
|
||||||
@ArquillianResource
|
|
||||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
|
||||||
private URL url;
|
|
||||||
|
|
||||||
@FindBy(tagName = "a")
|
|
||||||
WebElement logoutButton;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getInjectedUrl() {
|
|
||||||
//EAP6 URL fix
|
|
||||||
URL fixedUrl = createInjectedURL("sales-post-enc");
|
|
||||||
return fixedUrl != null ? fixedUrl : url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logout() {
|
|
||||||
logoutButton.click();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite.adapter.page;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
|
||||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
|
||||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
import org.openqa.selenium.support.FindBy;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author mhajas
|
|
||||||
*/
|
|
||||||
public class SAMLPostSigExample extends AbstractPageWithInjectedUrl {
|
|
||||||
public static final String DEPLOYMENT_NAME = "sales-post-sig";
|
|
||||||
|
|
||||||
@ArquillianResource
|
|
||||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
|
||||||
private URL url;
|
|
||||||
|
|
||||||
@FindBy(tagName = "a")
|
|
||||||
WebElement logoutButton;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getInjectedUrl() {
|
|
||||||
//EAP6 URL fix
|
|
||||||
URL fixedUrl = createInjectedURL("sales-post-sig");
|
|
||||||
return fixedUrl != null ? fixedUrl : url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logout() {
|
|
||||||
logoutButton.click();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite.adapter.page;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
|
||||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
|
||||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
import org.openqa.selenium.support.FindBy;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author mhajas
|
|
||||||
*/
|
|
||||||
public class SAMLRedirectSigExample extends AbstractPageWithInjectedUrl {
|
|
||||||
public static final String DEPLOYMENT_NAME = "saml-redirect-signatures";
|
|
||||||
|
|
||||||
@ArquillianResource
|
|
||||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
|
||||||
private URL url;
|
|
||||||
|
|
||||||
@FindBy(tagName = "a")
|
|
||||||
WebElement logoutButton;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getInjectedUrl() {
|
|
||||||
//EAP6 URL fix
|
|
||||||
URL fixedUrl = createInjectedURL("employee-sig");
|
|
||||||
return fixedUrl != null ? fixedUrl : url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logout() {
|
|
||||||
logoutButton.click();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,18 +17,16 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.client.resources;
|
package org.keycloak.testsuite.client.resources;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
|
||||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||||
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
|
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
|
||||||
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
|
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
|
||||||
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -56,7 +54,7 @@ public interface TestApplicationResource {
|
||||||
void clearAdminActions();
|
void clearAdminActions();
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML_UTF_8)
|
||||||
@Path("/get-account-profile")
|
@Path("/get-account-profile")
|
||||||
String getAccountProfile(@QueryParam("token") String token, @QueryParam("account-uri") String accountUri);
|
String getAccountProfile(@QueryParam("token") String token, @QueryParam("account-uri") String accountUri);
|
||||||
|
|
||||||
|
|
|
@ -17,18 +17,16 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.client.resources;
|
package org.keycloak.testsuite.client.resources;
|
||||||
|
|
||||||
import java.util.Map;
|
import org.keycloak.testsuite.rest.representation.JGroupsStats;
|
||||||
import java.util.Set;
|
import org.keycloak.testsuite.rest.representation.RemoteCacheStats;
|
||||||
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.MediaType;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.keycloak.testsuite.rest.representation.JGroupsStats;
|
|
||||||
import org.keycloak.testsuite.rest.representation.RemoteCacheStats;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -55,7 +53,7 @@ public interface TestingCacheResource {
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/clear")
|
@Path("/clear")
|
||||||
@Consumes(MediaType.TEXT_PLAIN)
|
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
|
|
@ -18,14 +18,13 @@
|
||||||
package org.keycloak.testsuite.client.resources;
|
package org.keycloak.testsuite.client.resources;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
|
||||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.components.TestProvider;
|
import org.keycloak.testsuite.components.TestProvider;
|
||||||
import org.keycloak.testsuite.rest.representation.AuthenticatorState;
|
import org.keycloak.testsuite.rest.representation.AuthenticatorState;
|
||||||
import org.keycloak.testsuite.rest.resource.TestCacheResource;
|
import org.keycloak.utils.MediaType;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
@ -35,8 +34,6 @@ import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -259,8 +256,8 @@ public interface TestingResource {
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/run-on-server")
|
@Path("/run-on-server")
|
||||||
@Consumes(MediaType.TEXT_PLAIN)
|
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||||
@Produces(MediaType.TEXT_PLAIN)
|
@Produces(MediaType.TEXT_PLAIN_UTF_8)
|
||||||
String runOnServer(String runOnServer);
|
String runOnServer(String runOnServer);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 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.testsuite.pages;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||||
|
|
||||||
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.keycloak.testsuite.adapter.page.AppServerContextRoot;
|
||||||
|
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
|
||||||
|
import org.keycloak.testsuite.util.URLUtils;
|
||||||
|
import org.keycloak.testsuite.util.WaitUtils;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public class AppServerWelcomePage extends AppServerContextRoot {
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected OIDCLogin loginPage;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//a[text() = 'Access Control']")
|
||||||
|
private WebElement accessControlLink;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//a[text() = 'Manage user profile']")
|
||||||
|
private WebElement manageProfileLink;
|
||||||
|
|
||||||
|
@FindBy(xpath = "//div[text() = 'Logout']")
|
||||||
|
private WebElement logoutLink;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCurrent() {
|
||||||
|
return driver.getPageSource().contains("Access Control");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void navigateToConsole() {
|
||||||
|
WaitUtils.pause(2000);
|
||||||
|
URLUtils.navigateToUri(driver, getInjectedUrl().toString() + "/console", true);
|
||||||
|
waitForPageToLoad(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void login(String username, String password) {
|
||||||
|
loginPage.form().waitForLoginButtonPresent();
|
||||||
|
loginPage.form().login(username, password);
|
||||||
|
waitForPageToLoad(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void navigateToAccessControl() {
|
||||||
|
accessControlLink.click();
|
||||||
|
waitForPageToLoad(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void navigateManageProfile() {
|
||||||
|
manageProfileLink.click();
|
||||||
|
waitForPageToLoad(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void logout() {
|
||||||
|
logoutLink.click();
|
||||||
|
waitForPageToLoad(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoginPage() {
|
||||||
|
return loginPage.isCurrent();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue