diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java index 9efa614573..c8bce94592 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathMatcher.java @@ -89,7 +89,7 @@ class PathMatcher { pathString = "/"; } - if (matchingUri.equals(targetUri)) { + if (matchingUri.equals(targetUri) || pathString.equals(targetUri)) { cache.put(targetUri, entry); return entry; } diff --git a/adapters/oidc/js/pom.xml b/adapters/oidc/js/pom.xml index 8b4cc6789c..f1b83c17ad 100755 --- a/adapters/oidc/js/pom.xml +++ b/adapters/oidc/js/pom.xml @@ -38,6 +38,11 @@ com.samaxes.maven minify-maven-plugin + + CLOSURE + ECMASCRIPT5 + true + min-js diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js index a784936c78..ab6f78826a 100755 --- a/adapters/oidc/js/src/main/resources/keycloak.js +++ b/adapters/oidc/js/src/main/resources/keycloak.js @@ -221,7 +221,7 @@ var callbackState = { state: state, nonce: nonce, - redirectUri: encodeURIComponent(redirectUri), + redirectUri: encodeURIComponent(redirectUri) } if (options && options.prompt) { @@ -843,6 +843,7 @@ } iframe.setAttribute('src', src ); + iframe.setAttribute('title', 'keycloak-session-iframe' ); iframe.style.display = 'none'; document.body.appendChild(iframe); diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java index 4472af75f9..4941275f1e 100644 --- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java +++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java @@ -50,7 +50,9 @@ import java.net.InetSocketAddress; import java.net.URI; import java.net.URLDecoder; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Consumer; /** @@ -66,6 +68,7 @@ class ElytronHttpFacade implements OIDCHttpFacade { private ElytronAccount account; private SecurityIdentity securityIdentity; private boolean restored; + private final Map headers = new HashMap<>(); public ElytronHttpFacade(HttpServerRequest request, AdapterDeploymentContext deploymentContext, CallbackHandler handler) { this.request = request; @@ -261,6 +264,7 @@ class ElytronHttpFacade implements OIDCHttpFacade { @Override public Response getResponse() { return new Response() { + @Override public void setStatus(final int status) { responseConsumer = responseConsumer.andThen(response -> response.setStatusCode(status)); @@ -268,7 +272,17 @@ class ElytronHttpFacade implements OIDCHttpFacade { @Override 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() { + @Override + public void accept(HttpServerResponse response) { + String latestValue = headers.get(name); + + if (latestValue.equals(value)) { + response.addResponseHeader(name, latestValue); + } + } + }); } @Override diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakSecurityRealm.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakSecurityRealm.java index 6042ec82d1..eef2b26942 100644 --- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakSecurityRealm.java +++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakSecurityRealm.java @@ -17,6 +17,7 @@ package org.keycloak.adapters.elytron; import java.security.Principal; +import java.security.spec.AlgorithmParameterSpec; import java.util.Set; import org.keycloak.KeycloakPrincipal; @@ -54,7 +55,7 @@ public class KeycloakSecurityRealm implements SecurityRealm { } @Override - public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName) throws RealmUnavailableException { + public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException { return SupportLevel.UNSUPPORTED; } @@ -92,7 +93,7 @@ public class KeycloakSecurityRealm implements SecurityRealm { } @Override - public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName) throws RealmUnavailableException { + public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException { return SupportLevel.UNSUPPORTED; } diff --git a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml index 1d8e6cf916..8c18a39a74 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml +++ b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml @@ -68,6 +68,10 @@ wildfly-web-common provided + + org.wildfly.security + wildfly-elytron + org.jboss.logging jboss-logging-annotations @@ -101,5 +105,9 @@ keycloak-wildfly-adapter ${project.version} + + org.keycloak + keycloak-wildfly-elytron-oidc-adapter + diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentAddHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationAddHandler.java similarity index 58% rename from adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentAddHandler.java rename to adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationAddHandler.java index 110d385a75..55c83ca96b 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentAddHandler.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationAddHandler.java @@ -17,41 +17,32 @@ package org.keycloak.subsystem.adapter.extension; +import java.util.List; + import org.jboss.as.controller.AbstractAddStepHandler; -import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.OperationContext; 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 static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; -import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; - /** * Add a deployment to a realm. * * @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() {} - - @Override - 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); - } + AbstractAdapterConfigurationAddHandler(RuntimeCapability runtimeCapability, List attributes) { + super(runtimeCapability, attributes); + elytronEnabled = runtimeCapability != null; } @Override protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance(); - ckService.addSecureDeployment(operation, context.resolveExpressions(model)); + ckService.addSecureDeployment(operation, context.resolveExpressions(model), elytronEnabled); } } diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationDefinition.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationDefinition.java new file mode 100755 index 0000000000..613c946f4f --- /dev/null +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationDefinition.java @@ -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 DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList(); + + 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 ALL_ATTRIBUTES = new ArrayList(); + + static { + ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES); + ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES); + } + + static final Map XML_ATTRIBUTES = new HashMap(); + + static { + for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) { + XML_ATTRIBUTES.put(def.getXmlName(), def); + } + } + + private static final Map DEFINITION_LOOKUP = new HashMap(); + static { + for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) { + DEFINITION_LOOKUP.put(def.getXmlName(), def); + } + } + + private final AbstractAdapterConfigurationWriteAttributeHandler attrWriteHandler; + private final List attributes; + + protected AbstractAdapterConfigurationDefinition(String name, List 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); + } +} diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentRemoveHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationRemoveHandler.java similarity index 85% rename from adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentRemoveHandler.java rename to adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationRemoveHandler.java index 7088ec0cf9..2066aed3f4 100644 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentRemoveHandler.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationRemoveHandler.java @@ -27,11 +27,7 @@ import org.jboss.dmr.ModelNode; * * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. */ -public final class SecureDeploymentRemoveHandler extends AbstractRemoveStepHandler { - - public static SecureDeploymentRemoveHandler INSTANCE = new SecureDeploymentRemoveHandler(); - - private SecureDeploymentRemoveHandler() {} +abstract class AbstractAdapterConfigurationRemoveHandler extends AbstractRemoveStepHandler { @Override protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentWriteAttributeHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationWriteAttributeHandler.java similarity index 83% rename from adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentWriteAttributeHandler.java rename to adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationWriteAttributeHandler.java index 908526c828..127ea538f1 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentWriteAttributeHandler.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/AbstractAdapterConfigurationWriteAttributeHandler.java @@ -31,14 +31,10 @@ import java.util.List; * * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. */ -public class SecureDeploymentWriteAttributeHandler extends AbstractWriteAttributeHandler { +abstract class AbstractAdapterConfigurationWriteAttributeHandler extends AbstractWriteAttributeHandler { - public SecureDeploymentWriteAttributeHandler(List definitions) { - this(definitions.toArray(new AttributeDefinition[definitions.size()])); - } - - public SecureDeploymentWriteAttributeHandler(AttributeDefinition... definitions) { - super(definitions); + AbstractAdapterConfigurationWriteAttributeHandler(List definitions) { + super(definitions.toArray(new AttributeDefinition[definitions.size()])); } @Override diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigDeploymentProcessor.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigDeploymentProcessor.java index 24e9ac0b74..1500808e32 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigDeploymentProcessor.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigDeploymentProcessor.java @@ -25,7 +25,9 @@ import org.jboss.as.web.common.WarMetaData; import org.jboss.logging.Logger; import org.jboss.metadata.javaee.spec.ParamValueMetaData; import org.jboss.metadata.web.jboss.JBossWebMetaData; +import org.jboss.metadata.web.spec.ListenerMetaData; import org.jboss.metadata.web.spec.LoginConfigMetaData; +import org.keycloak.adapters.elytron.KeycloakConfigurationServletListener; import org.keycloak.subsystem.adapter.logging.KeycloakLogger; import java.util.ArrayList; @@ -69,6 +71,9 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance(); if (service.isSecureDeployment(deploymentUnit) && service.isDeploymentConfigured(deploymentUnit)) { 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 @@ -99,6 +104,10 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP loginConfig.setAuthMethod("KEYCLOAK"); loginConfig.setRealmName(service.getRealmName(deploymentUnit)); KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName()); + + if (service.isElytronEnabled(deploymentUnit)) { + addConfigurationListener(warMetaData); + } } private void addJSONData(String json, WarMetaData warMetaData) { @@ -121,6 +130,31 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP 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 public void undeploy(DeploymentUnit du) { diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java index 5a71e615a8..496c311982 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java @@ -23,8 +23,11 @@ import org.jboss.dmr.ModelNode; import org.jboss.dmr.Property; import org.jboss.metadata.web.jboss.JBossWebMetaData; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; @@ -50,6 +53,7 @@ public final class KeycloakAdapterConfigService { // keycloak-secured deployments private final Map secureDeployments = new HashMap(); + private final Set elytronEnabledDeployments = new HashSet<>(); private KeycloakAdapterConfigService() { @@ -68,9 +72,13 @@ public final class KeycloakAdapterConfigService { 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(); - 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) { @@ -79,7 +87,9 @@ public final class KeycloakAdapterConfigService { } 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) { @@ -187,7 +197,19 @@ public final class KeycloakAdapterConfigService { } 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) { @@ -199,9 +221,7 @@ public final class KeycloakAdapterConfigService { } private String valueFromOpAddress(String addrElement, ModelNode operation) { - String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement); - if (deploymentName == null) throw new RuntimeException("Can't find '" + addrElement + "' in address " + operation.toString()); - return deploymentName; + return getValueOfAddrElement(operation.get(ADDRESS), addrElement); } private String getValueOfAddrElement(ModelNode address, String elementName) { @@ -241,8 +261,22 @@ public final class KeycloakAdapterConfigService { 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) { - for (Property prop : values.asPropertyList()) { + for (Property prop : new ArrayList<>(values.asPropertyList())) { String name = prop.getName(); ModelNode value = prop.getValue(); if (value.isDefined()) { @@ -258,6 +292,10 @@ public final class KeycloakAdapterConfigService { return this.secureDeployments.containsKey(deploymentName); } + public boolean isElytronEnabled(DeploymentUnit deploymentUnit) { + return elytronEnabledDeployments.contains(preferredDeploymentName(deploymentUnit)); + } + private ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) { String deploymentName = preferredDeploymentName(deploymentUnit); return this.secureDeployments.containsKey(deploymentName) diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java index e306a7f630..61d670c8cd 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java @@ -29,13 +29,14 @@ import org.jboss.modules.ModuleLoader; */ 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_UNDERTOW_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-undertow-adapter"); @Override 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_UNDERTOW_ADAPTER, false, false, false, false)); + moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_ELYTRON_ADAPTER, true, false, false, false)); } } diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java index d04e72d403..52113c0825 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java @@ -47,8 +47,9 @@ public class KeycloakExtension implements Extension { private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition(); static final RealmDefinition REALM_DEFINITION = new RealmDefinition(); 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 RedirecRewritetRuleDefinition REDIRECT_RULE_DEFINITON = new RedirecRewritetRuleDefinition(); + static final RedirecRewritetRuleDefinition REDIRECT_RULE_DEFINITON = new RedirecRewritetRuleDefinition(); public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) { StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME); @@ -80,6 +81,10 @@ public class KeycloakExtension implements Extension { secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION); secureDeploymentRegistration.registerSubModel(REDIRECT_RULE_DEFINITON); + ManagementResourceRegistration secureServerRegistration = registration.registerSubModel(SECURE_SERVER_DEFINITION); + secureServerRegistration.registerSubModel(CREDENTIAL_DEFINITION); + secureServerRegistration.registerSubModel(REDIRECT_RULE_DEFINITON); + subsystem.registerXMLElementWriter(PARSER); } } diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakHttpAuthenticationFactoryService.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakHttpAuthenticationFactoryService.java new file mode 100644 index 0000000000..94fb8e566d --- /dev/null +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakHttpAuthenticationFactoryService.java @@ -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 Pedro Igor + */ +public class KeycloakHttpAuthenticationFactoryService implements Service { + + 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))); + } +} diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakHttpServerAuthenticationMechanismFactoryDefinition.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakHttpServerAuthenticationMechanismFactoryDefinition.java new file mode 100644 index 0000000000..1e177a49f4 --- /dev/null +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakHttpServerAuthenticationMechanismFactoryDefinition.java @@ -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 Pedro Igor + */ +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 Pedro Igor + */ + static final class KeycloakHttpServerAuthenticationMechanismFactoryAddHandler extends AbstractAdapterConfigurationAddHandler { + + static final String HTTP_SERVER_AUTHENTICATION_CAPABILITY = "org.wildfly.security.http-server-mechanism-factory"; + static final RuntimeCapability 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 Pedro Igor + */ + 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 Pedro Igor + */ + static final class KeycloakHttpServerAuthenticationMechanismFactoryWriteHandler extends AbstractAdapterConfigurationWriteAttributeHandler { + KeycloakHttpServerAuthenticationMechanismFactoryWriteHandler() { + super(ALL_ATTRIBUTES); + } + } +} diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java index 79555e3b7b..a8d50aeb3d 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java @@ -62,6 +62,9 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader
  • resourcesToAdd) throws XMLStreamException { + readSecureResource(KeycloakExtension.SECURE_DEPLOYMENT_DEFINITION.TAG_NAME, KeycloakExtension.SECURE_DEPLOYMENT_DEFINITION, reader, resourcesToAdd); + } + + private void readSecureServer(XMLExtendedStreamReader reader, List 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 resourcesToAdd) throws XMLStreamException { String name = readNameAttribute(reader); ModelNode addSecureDeployment = new ModelNode(); addSecureDeployment.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); 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()); List credentialsToAdd = new ArrayList(); List redirectRulesToAdd = new ArrayList(); while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { - String tagName = reader.getLocalName(); - if (tagName.equals(CredentialDefinition.TAG_NAME)) { + String localName = reader.getLocalName(); + if (localName.equals(CredentialDefinition.TAG_NAME)) { readCredential(reader, addr, credentialsToAdd); continue; } - if (tagName.equals(RedirecRewritetRuleDefinition.TAG_NAME)) { + if (localName.equals(RedirecRewritetRuleDefinition.TAG_NAME)) { readRewriteRule(reader, addr, redirectRulesToAdd); continue; } - SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName); - if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName); + SimpleAttributeDefinition def = resource.lookup(localName); + if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + localName); def.parseAndSetParameter(reader.getElementText(), addSecureDeployment, reader); } @@ -236,6 +247,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader
  • attributes, XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException { + if (!context.getModelNode().get(tagName).isDefined()) { return; } - for (Property deployment : context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).asPropertyList()) { - writer.writeStartElement(SecureDeploymentDefinition.TAG_NAME); + for (Property deployment : context.getModelNode().get(tagName).asPropertyList()) { + writer.writeStartElement(tagName); writer.writeAttribute("name", deployment.getName()); ModelNode deploymentElements = deployment.getValue(); - for (AttributeDefinition element : SecureDeploymentDefinition.ALL_ATTRIBUTES) { + for (AttributeDefinition element : attributes) { element.marshallAsElement(deploymentElements, writer); } @@ -271,7 +291,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader
  • DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList(); - 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 ALL_ATTRIBUTES = new ArrayList(); - static { - ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES); - ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES); - } - - private static final Map DEFINITION_LOOKUP = new HashMap(); - static { - for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) { - DEFINITION_LOOKUP.put(def.getXmlName(), def); - } - } - - private static SecureDeploymentWriteAttributeHandler attrHandler = new SecureDeploymentWriteAttributeHandler(ALL_ATTRIBUTES); + static final String TAG_NAME = "secure-deployment"; public SecureDeploymentDefinition() { - super(PathElement.pathElement(TAG_NAME), - KeycloakExtension.getResourceDescriptionResolver(TAG_NAME), - SecureDeploymentAddHandler.INSTANCE, - SecureDeploymentRemoveHandler.INSTANCE); + super(TAG_NAME, ALL_ATTRIBUTES, new SecureDeploymentAddHandler(), new SecureDeploymentRemoveHandler(), new SecureDeploymentWriteAttributeHandler()); } - @Override - public void registerOperations(ManagementResourceRegistration resourceRegistration) { - super.registerOperations(resourceRegistration); - resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); - } + /** + * Add a deployment to a realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ + static final class SecureDeploymentAddHandler extends AbstractAdapterConfigurationAddHandler { - @Override - public void registerAttributes(ManagementResourceRegistration resourceRegistration) { - super.registerAttributes(resourceRegistration); - for (AttributeDefinition attrDef : ALL_ATTRIBUTES) { - resourceRegistration.registerReadWriteAttribute(attrDef, null, attrHandler); + static final String HTTP_SERVER_AUTHENTICATION_CAPABILITY = "org.wildfly.security.http-server-mechanism-factory"; + static RuntimeCapability 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 + } + } + + 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); + } } } diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureServerDefinition.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureServerDefinition.java new file mode 100755 index 0000000000..7d8fd05dfd --- /dev/null +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureServerDefinition.java @@ -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 Pedro Igor + */ + 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 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 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 Pedro Igor + */ + 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 Pedro Igor + */ + static final class SecureServerWriteHandler extends AbstractAdapterConfigurationWriteAttributeHandler { + SecureServerWriteHandler() { + super(ALL_ATTRIBUTES); + } + } + + private static Service createHttpManagementConfigContextService(final String factoryName, final InjectedValue httpConfigContext) { + final String contextName = "/keycloak/adapter/" + factoryName + "/"; + return new Service() { + 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 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; + } + }; + } +} diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties index c9cea77787..f6097ae5cf 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties @@ -20,6 +20,8 @@ keycloak.subsystem.add=Operation Adds Keycloak adapter subsystem keycloak.subsystem.remove=Operation removes Keycloak adapter subsystem keycloak.subsystem.realm=A Keycloak realm. 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.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.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-server.credential=Credential value keycloak.credential=Credential keycloak.credential.value=Credential value diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd index d8f5bc3d74..caa147d821 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd @@ -38,6 +38,7 @@ + @@ -84,7 +85,7 @@ - + @@ -99,9 +100,9 @@ - - - + + + diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java b/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java index 9d5f87ab39..4adad9f21a 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java @@ -73,7 +73,7 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest { ModelNode deployment = new ModelNode(); deployment.get("realm").set("demo"); deployment.get("resource").set("customer-portal"); - service.addSecureDeployment(deploymentOp, deployment); + service.addSecureDeployment(deploymentOp, deployment, false); addCredential(addr, service, "secret", "secret1"); addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks"); diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml b/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml index 246d76855f..fce5c41dae 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml @@ -40,6 +40,10 @@ session sub + + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqKoq+a9MgXepmsPJDmo45qswuChW9pWjanX68oIBuI4hGvhQxFHryCow230A+sr7tFdMQMt8f1l/ysmV/fYAuW29WaoY4kI4Ou1yYPuwywKSsxT6PooTs83hKyZ1h4LZMj5DkLGDDDyVRHob2WmPaYg9RGVRw3iGGsD/p+Yb+L/gnBYQnZZ7lYqmN7h36p5CkzzlgXQA1Ha8sQxL+rJNH8+sZm0vBrKsoII3Of7TqHGsm1RwFV3XCuGJ7S61AbjJMXL5DQgJl9Z5scvxGAyoRLKC294UgMnQdzyBTMPw2GybxkRKmiK2KjQKmcopmrJp/Bt6fBR6ZkGSs9qUlxGHgwIDAQAB + http://localhost:8180/auth + master web-console @@ -69,4 +73,16 @@ /api/$1/ + + jboss-infra + wildfly-management + true + EXTERNAL + preferred_username + + + jboss-infra + wildfly-console + true + \ No newline at end of file diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakSecurityRealm.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakSecurityRealm.java index 3207835360..f79b60d25e 100644 --- a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakSecurityRealm.java +++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakSecurityRealm.java @@ -17,6 +17,7 @@ package org.keycloak.adapters.saml.elytron; import java.security.Principal; +import java.security.spec.AlgorithmParameterSpec; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -53,7 +54,7 @@ public class KeycloakSecurityRealm implements SecurityRealm { } @Override - public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName) throws RealmUnavailableException { + public SupportLevel getCredentialAcquireSupport(Class aClass, String s, AlgorithmParameterSpec algorithmParameterSpec) throws RealmUnavailableException { return SupportLevel.UNSUPPORTED; } @@ -90,7 +91,7 @@ public class KeycloakSecurityRealm implements SecurityRealm { } @Override - public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName) throws RealmUnavailableException { + public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException { return SupportLevel.UNSUPPORTED; } diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java index 4dcea953df..44458fd7e9 100755 --- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java @@ -55,6 +55,7 @@ public class UserRepresentation { protected List realmRoles; protected Map> clientRoles; protected List clientConsents; + protected Integer notBefore; @Deprecated protected Map> applicationRoles; @@ -216,6 +217,14 @@ public class UserRepresentation { this.clientConsents = clientConsents; } + public Integer getNotBefore() { + return notBefore; + } + + public void setNotBefore(Integer notBefore) { + this.notBefore = notBefore; + } + @Deprecated public Map> getApplicationRoles() { return applicationRoles; diff --git a/distribution/adapters/js-adapter-zip/assembly.xml b/distribution/adapters/js-adapter-zip/assembly.xml index 14e0cc00b6..8111f783ea 100755 --- a/distribution/adapters/js-adapter-zip/assembly.xml +++ b/distribution/adapters/js-adapter-zip/assembly.xml @@ -30,6 +30,7 @@ **/*.js + **/*.map **/*.d.ts **/*.html diff --git a/distribution/adapters/js-adapter-zip/pom.xml b/distribution/adapters/js-adapter-zip/pom.xml index be37dc8729..8dcee562ae 100755 --- a/distribution/adapters/js-adapter-zip/pom.xml +++ b/distribution/adapters/js-adapter-zip/pom.xml @@ -50,7 +50,7 @@ org.keycloak keycloak-js-adapter ${project.build.directory}/unpacked/js-adapter - *.js,*.d.ts + *.js,*.map,*.d.ts **/welcome-content/* diff --git a/distribution/adapters/shared-cli/adapter-elytron-install.cli b/distribution/adapters/shared-cli/adapter-elytron-install.cli index 16f17ce9a8..6ef26d01e5 100644 --- a/distribution/adapters/shared-cli/adapter-elytron-install.cli +++ b/distribution/adapters/shared-cli/adapter-elytron-install.cli @@ -36,10 +36,10 @@ else end-if 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 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 diff --git a/distribution/adapters/wildfly-adapter/cli/adapter-elytron-install-offline.cli b/distribution/adapters/wildfly-adapter/cli/adapter-elytron-install-offline.cli index 8e0335ac02..76ec08cb17 100644 --- a/distribution/adapters/wildfly-adapter/cli/adapter-elytron-install-offline.cli +++ b/distribution/adapters/wildfly-adapter/cli/adapter-elytron-install-offline.cli @@ -38,10 +38,10 @@ else end-if 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 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 diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl index 5ea7e93ce0..f800ae2359 100755 --- a/distribution/demo-dist/src/main/xslt/standalone.xsl +++ b/distribution/demo-dist/src/main/xslt/standalone.xsl @@ -17,10 +17,10 @@ diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-wildfly-subsystem/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-wildfly-subsystem/main/module.xml index 025f15271b..32849fc6f1 100755 --- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-wildfly-subsystem/main/module.xml +++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-wildfly-subsystem/main/module.xml @@ -38,5 +38,9 @@ + + + + diff --git a/distribution/saml-adapters/shared-cli/adapter-elytron-install-saml.cli b/distribution/saml-adapters/shared-cli/adapter-elytron-install-saml.cli index 1f24085459..a76109b809 100755 --- a/distribution/saml-adapters/shared-cli/adapter-elytron-install-saml.cli +++ b/distribution/saml-adapters/shared-cli/adapter-elytron-install-saml.cli @@ -36,10 +36,10 @@ else end-if 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 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 if (outcome != success) of /subsystem=elytron/http-authentication-factory=keycloak-http-authentication:read-resource diff --git a/distribution/server-overlay/src/main/modules/layers.conf b/distribution/server-overlay/src/main/modules/layers.conf new file mode 100644 index 0000000000..74f44850c7 --- /dev/null +++ b/distribution/server-overlay/src/main/modules/layers.conf @@ -0,0 +1 @@ +layers=keycloak \ No newline at end of file diff --git a/examples/providers/rest/src/main/java/org/keycloak/examples/rest/HelloResourceProvider.java b/examples/providers/rest/src/main/java/org/keycloak/examples/rest/HelloResourceProvider.java index aebd677221..ebd8d1c991 100644 --- a/examples/providers/rest/src/main/java/org/keycloak/examples/rest/HelloResourceProvider.java +++ b/examples/providers/rest/src/main/java/org/keycloak/examples/rest/HelloResourceProvider.java @@ -22,7 +22,6 @@ import org.keycloak.services.resource.RealmResourceProvider; import javax.ws.rs.GET; import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; /** * @author Stian Thorgersen @@ -41,7 +40,7 @@ public class HelloResourceProvider implements RealmResourceProvider { } @GET - @Produces(MediaType.TEXT_PLAIN) + @Produces("text/plain; charset=utf-8") public String get() { String name = session.getContext().getRealm().getDisplayName(); if (name == null) { diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java index 86f607456f..a17e7240f5 100755 --- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java @@ -262,7 +262,14 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon sessionCacheConfiguration = sessionConfigBuilder.build(); 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); // Retrieve caches to enforce rebalance diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java index 0d971f70b9..390c25c158 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java @@ -334,6 +334,8 @@ public class UserCacheSession implements UserCache { } protected UserModel cacheUser(RealmModel realm, UserModel delegate, Long revision) { + int notBefore = getDelegate().getNotBeforeOfUser(realm, delegate); + StorageId storageId = new StorageId(delegate.getId()); CachedUser cached = null; if (!storageId.isLocal()) { @@ -343,7 +345,7 @@ public class UserCacheSession implements UserCache { if (policy != null && policy == UserStorageProviderModel.CachePolicy.NO_CACHE) { return delegate; } - cached = new CachedUser(revision, realm, delegate); + cached = new CachedUser(revision, realm, delegate, notBefore); if (policy == null || policy == UserStorageProviderModel.CachePolicy.DEFAULT) { cache.addRevisioned(cached, startupRevision); } else { @@ -366,7 +368,7 @@ public class UserCacheSession implements UserCache { } } } else { - cached = new CachedUser(revision, realm, delegate); + cached = new CachedUser(revision, realm, delegate, notBefore); cache.addRevisioned(cached, startupRevision); } UserAdapter adapter = new UserAdapter(cached, this, session, realm); @@ -765,6 +767,32 @@ public class UserCacheSession implements UserCache { 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 public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java index dc721b7112..5e9b7d3431 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java @@ -46,7 +46,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public Resource getDelegateForUpdate() { 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()); if (updated == null) throw new IllegalStateException("Not found in database"); } @@ -95,7 +95,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void setName(String name) { 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); } @@ -127,7 +127,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void setUri(String uri) { 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); } @@ -140,7 +140,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void setType(String type) { 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); } @@ -168,7 +168,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void updateScopes(Set scopes) { 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); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java index 63eb8a73da..e9853d68ba 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java @@ -75,9 +75,10 @@ public class StoreFactoryCacheManager extends CacheManager { addInvalidations(InScopePredicate.create().scope(id), invalidations); } - public void resourceUpdated(String id, String name, String type, String uri, Set scopes, String serverId, Set invalidations) { + public void resourceUpdated(String id, String name, String type, String uri, Set scopes, String serverId, String owner, Set invalidations) { invalidations.add(id); invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, serverId)); + invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId)); if (type != null) { 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 scopes, String serverId, Set invalidations) { - resourceUpdated(id, name, type, uri, scopes, serverId, invalidations); - invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId)); + resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations); addInvalidations(InResourcePredicate.create().resource(id), invalidations); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java index a169235643..27bacc45b0 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java @@ -245,12 +245,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { invalidationEvents.add(ScopeUpdatedEvent.create(id, name, serverId)); } - public void registerResourceInvalidation(String id, String name, String type, String uri, Set scopes, String serverId) { - cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations); + public void registerResourceInvalidation(String id, String name, String type, String uri, Set scopes, String serverId, String owner) { + cache.resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations); ResourceAdapter adapter = managedResources.get(id); 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 resources, Set scopes, String serverId) { @@ -509,7 +509,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { @Override public Resource create(String name, ResourceServer resourceServer, String 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; } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceUpdatedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceUpdatedEvent.java index cc30f230d9..3f85a6d5b0 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceUpdatedEvent.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceUpdatedEvent.java @@ -33,8 +33,9 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza private String type; private String uri; private Set scopes; + private String owner; - public static ResourceUpdatedEvent create(String id, String name, String type, String uri, Set scopes, String serverId) { + public static ResourceUpdatedEvent create(String id, String name, String type, String uri, Set scopes, String serverId, String owner) { ResourceUpdatedEvent event = new ResourceUpdatedEvent(); event.id = id; event.name = name; @@ -42,6 +43,7 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza event.uri = uri; event.scopes = scopes; event.serverId = serverId; + event.owner = owner; return event; } @@ -57,6 +59,6 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza @Override public void addInvalidations(StoreFactoryCacheManager cache, Set invalidations) { - cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations); + cache.resourceUpdated(id, name, type, uri, scopes, serverId, owner, invalidations); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java index 1bf6e426a0..68dfc372cb 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java @@ -45,10 +45,11 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm private Set requiredActions = new HashSet<>(); private Set roleMappings = new HashSet<>(); private Set 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()); this.realm = realm.getId(); this.username = user.getUsername(); @@ -71,6 +72,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm groups.add(group.getId()); } } + this.notBefore = notBefore; } public String getRealm() { @@ -129,4 +131,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm return groups; } + public int getNotBefore() { + return notBefore; + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index 1eb58b5bd7..48d1965fca 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java @@ -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.LoginFailureKey; 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.RemoveAllUserLoginFailuresEvent; import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent; @@ -76,11 +75,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { protected final Cache> sessionCache; protected final Cache> offlineSessionCache; - protected final Cache loginFailureCache; + protected final Cache> loginFailureCache; - protected final InfinispanChangelogBasedTransaction sessionTx; - protected final InfinispanChangelogBasedTransaction offlineSessionTx; - protected final InfinispanKeycloakTransaction tx; + protected final InfinispanChangelogBasedTransaction sessionTx; + protected final InfinispanChangelogBasedTransaction offlineSessionTx; + protected final InfinispanChangelogBasedTransaction loginFailuresTx; protected final SessionEventsSenderTransaction clusterEventsSenderTx; @@ -93,7 +92,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { LastSessionRefreshStore offlineLastSessionRefreshStore, Cache> sessionCache, Cache> offlineSessionCache, - Cache loginFailureCache) { + Cache> loginFailureCache) { this.session = session; this.sessionCache = sessionCache; @@ -103,24 +102,24 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { this.sessionTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCache, 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.lastSessionRefreshStore = lastSessionRefreshStore; this.offlineLastSessionRefreshStore = offlineLastSessionRefreshStore; - session.getTransactionManager().enlistAfterCompletion(tx); session.getTransactionManager().enlistAfterCompletion(clusterEventsSenderTx); session.getTransactionManager().enlistAfterCompletion(sessionTx); session.getTransactionManager().enlistAfterCompletion(offlineSessionTx); + session.getTransactionManager().enlistAfterCompletion(loginFailuresTx); } protected Cache> getCache(boolean offline) { return offline ? offlineSessionCache : sessionCache; } - protected InfinispanChangelogBasedTransaction getTransaction(boolean offline) { + protected InfinispanChangelogBasedTransaction getTransaction(boolean offline) { return offline ? offlineSessionTx : sessionTx; } @@ -136,7 +135,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) { AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(); - InfinispanChangelogBasedTransaction updateTx = getTransaction(false); + InfinispanChangelogBasedTransaction updateTx = getTransaction(false); AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession, this, updateTx); adapter.setUserSession(userSession); return adapter; @@ -202,7 +201,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { } private UserSessionEntity getUserSessionEntity(String id, boolean offline) { - InfinispanChangelogBasedTransaction tx = getTransaction(offline); + InfinispanChangelogBasedTransaction tx = getTransaction(offline); SessionEntityWrapper entityWrapper = tx.get(id); return entityWrapper==null ? null : entityWrapper.getEntity(); } @@ -311,7 +310,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { UserSessionModel remoteSessionAdapter = wrap(realm, remoteSessionEntity, offline); if (predicate.test(remoteSessionAdapter)) { - InfinispanChangelogBasedTransaction tx = getTransaction(offline); + InfinispanChangelogBasedTransaction tx = getTransaction(offline); // Remote entity contains our predicate. Update local cache with the remote entity SessionEntityWrapper sessionWrapper = remoteSessionEntity.mergeRemoteEntityWithLocalEntity(tx.get(id)); @@ -511,7 +510,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { @Override public UserLoginFailureModel getUserLoginFailure(RealmModel realm, String 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 tx = getLoginFailuresTx(); + SessionEntityWrapper entityWrapper = tx.get(key); + return entityWrapper==null ? null : entityWrapper.getEntity(); } @Override @@ -520,13 +526,53 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { LoginFailureEntity entity = new LoginFailureEntity(); entity.setRealm(realm.getId()); entity.setUserId(userId); - tx.put(loginFailureCache, key, entity); + + SessionUpdateTask createLoginFailureTask = new SessionUpdateTask() { + + @Override + public void runUpdate(LoginFailureEntity session) { + + } + + @Override + public CacheOperation getOperation(LoginFailureEntity session) { + return CacheOperation.ADD_IF_ABSENT; + } + + @Override + public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper sessionWrapper) { + return CrossDCMessageStatus.SYNC; + } + + }; + + loginFailuresTx.addTask(key, createLoginFailureTask, entity); + return wrap(key, entity); } @Override public void removeUserLoginFailure(RealmModel realm, String userId) { - tx.remove(loginFailureCache, new LoginFailureKey(realm.getId(), userId)); + SessionUpdateTask removeTask = new SessionUpdateTask() { + + @Override + public void runUpdate(LoginFailureEntity entity) { + + } + + @Override + public CacheOperation getOperation(LoginFailureEntity entity) { + return CacheOperation.REMOVE; + } + + @Override + public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper sessionWrapper) { + return CrossDCMessageStatus.SYNC; + } + + }; + + loginFailuresTx.addTask(new LoginFailureKey(realm.getId(), userId), removeTask); } @Override @@ -543,9 +589,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { private void removeAllLocalUserLoginFailuresEvent(String realmId) { FuturesHelper futures = new FuturesHelper(); - Cache localCache = CacheDecorators.localCache(loginFailureCache); + Cache> localCache = CacheDecorators.localCache(loginFailureCache); - Cache localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache); + Cache> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache); localCacheStoreIgnore .entrySet() @@ -593,8 +639,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { removeUserSessions(realm, user, true); removeUserSessions(realm, user, false); - loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getUsername())); - loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail())); + removeUserLoginFailure(realm, user.getId()); } @Override @@ -602,7 +647,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { } protected void removeUserSession(UserSessionEntity sessionEntity, boolean offline) { - InfinispanChangelogBasedTransaction tx = getTransaction(offline); + InfinispanChangelogBasedTransaction tx = getTransaction(offline); SessionUpdateTask removeTask = new SessionUpdateTask() { @@ -626,17 +671,17 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { tx.addTask(sessionEntity.getId(), removeTask); } - InfinispanKeycloakTransaction getTx() { - return tx; + InfinispanChangelogBasedTransaction getLoginFailuresTx() { + return loginFailuresTx; } UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) { - InfinispanChangelogBasedTransaction tx = getTransaction(offline); + InfinispanChangelogBasedTransaction tx = getTransaction(offline); return entity != null ? new UserSessionAdapter(session, this, tx, realm, entity, offline) : null; } 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) { @@ -739,7 +784,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { entity.setLastSessionRefresh(userSession.getLastSessionRefresh()); - InfinispanChangelogBasedTransaction tx = getTransaction(offline); + InfinispanChangelogBasedTransaction tx = getTransaction(offline); SessionUpdateTask importTask = new SessionUpdateTask() { @@ -775,7 +820,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter importedUserSession, AuthenticatedClientSessionModel clientSession, - InfinispanChangelogBasedTransaction updateTx) { + InfinispanChangelogBasedTransaction updateTx) { AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(); entity.setAction(clientSession.getAction()); diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java index 489dd60d6d..df30b4e927 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java @@ -85,7 +85,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class); Cache> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME); - Cache loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME); + Cache> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME); return new InfinispanUserSessionProvider(session, remoteCacheInvoker, lastSessionRefreshStore, offlineLastSessionRefreshStore, cache, offlineSessionsCache, loginFailures); } @@ -224,6 +224,11 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider if (offlineSessionsRemoteCache) { 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) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserLoginFailureAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserLoginFailureAdapter.java index a41777dc43..0971c26212 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserLoginFailureAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserLoginFailureAdapter.java @@ -17,8 +17,8 @@ package org.keycloak.models.sessions.infinispan; -import org.infinispan.Cache; 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.LoginFailureKey; @@ -28,13 +28,11 @@ import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey; public class UserLoginFailureAdapter implements UserLoginFailureModel { private InfinispanUserSessionProvider provider; - private Cache cache; private LoginFailureKey key; private LoginFailureEntity entity; - public UserLoginFailureAdapter(InfinispanUserSessionProvider provider, Cache cache, LoginFailureKey key, LoginFailureEntity entity) { + public UserLoginFailureAdapter(InfinispanUserSessionProvider provider, LoginFailureKey key, LoginFailureEntity entity) { this.provider = provider; - this.cache = cache; this.key = key; this.entity = entity; } @@ -51,8 +49,16 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel { @Override public void setFailedLoginNotBefore(int notBefore) { - entity.setFailedLoginNotBefore(notBefore); - update(); + LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() { + + @Override + public void runUpdate(LoginFailureEntity entity) { + entity.setFailedLoginNotBefore(notBefore); + } + + }; + + update(task); } @Override @@ -62,14 +68,30 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel { @Override public void incrementFailures() { - entity.setNumFailures(getNumFailures() + 1); - update(); + LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() { + + @Override + public void runUpdate(LoginFailureEntity entity) { + entity.setNumFailures(entity.getNumFailures() + 1); + } + + }; + + update(task); } @Override public void clearFailures() { - entity.clearFailures(); - update(); + LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() { + + @Override + public void runUpdate(LoginFailureEntity entity) { + entity.clearFailures(); + } + + }; + + update(task); } @Override @@ -79,8 +101,16 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel { @Override public void setLastFailure(long lastFailure) { - entity.setLastFailure(lastFailure); - update(); + LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() { + + @Override + public void runUpdate(LoginFailureEntity entity) { + entity.setLastFailure(lastFailure); + } + + }; + + update(task); } @Override @@ -90,12 +120,20 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel { @Override public void setLastIPFailure(String ip) { - entity.setLastIPFailure(ip); - update(); + LoginFailuresUpdateTask task = new LoginFailuresUpdateTask() { + + @Override + public void runUpdate(LoginFailureEntity entity) { + entity.setLastIPFailure(ip); + } + + }; + + update(task); } - void update() { - provider.getTx().replace(cache, key, entity); + void update(LoginFailuresUpdateTask task) { + provider.getLoginFailuresTx().addTask(key, task); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/InfinispanChangelogBasedTransaction.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/InfinispanChangelogBasedTransaction.java index 43bb2b3929..e5a7a47c78 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/InfinispanChangelogBasedTransaction.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/InfinispanChangelogBasedTransaction.java @@ -33,18 +33,18 @@ import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker; /** * @author Marek Posolda */ -public class InfinispanChangelogBasedTransaction extends AbstractKeycloakTransaction { +public class InfinispanChangelogBasedTransaction extends AbstractKeycloakTransaction { public static final Logger logger = Logger.getLogger(InfinispanChangelogBasedTransaction.class); private final KeycloakSession kcSession; private final String cacheName; - private final Cache> cache; + private final Cache> cache; private final RemoteCacheInvoker remoteCacheInvoker; - private final Map> updates = new HashMap<>(); + private final Map> updates = new HashMap<>(); - public InfinispanChangelogBasedTransaction(KeycloakSession kcSession, String cacheName, Cache> cache, RemoteCacheInvoker remoteCacheInvoker) { + public InfinispanChangelogBasedTransaction(KeycloakSession kcSession, String cacheName, Cache> cache, RemoteCacheInvoker remoteCacheInvoker) { this.kcSession = kcSession; this.cacheName = cacheName; this.cache = cache; @@ -52,11 +52,11 @@ public class InfinispanChangelogBasedTransaction extend } - public void addTask(String key, SessionUpdateTask task) { - SessionUpdatesList myUpdates = updates.get(key); + public void addTask(K key, SessionUpdateTask task) { + SessionUpdatesList myUpdates = updates.get(key); if (myUpdates == null) { // Lookup entity from cache - SessionEntityWrapper wrappedEntity = cache.get(key); + SessionEntityWrapper wrappedEntity = cache.get(key); if (wrappedEntity == null) { logger.warnf("Not present cache item for key %s", key); return; @@ -75,14 +75,14 @@ public class InfinispanChangelogBasedTransaction extend // Create entity and new version for it - public void addTask(String key, SessionUpdateTask task, S entity) { + public void addTask(K key, SessionUpdateTask task, V entity) { if (entity == null) { throw new IllegalArgumentException("Null entity not allowed"); } RealmModel realm = kcSession.realms().getRealm(entity.getRealm()); - SessionEntityWrapper wrappedEntity = new SessionEntityWrapper<>(entity); - SessionUpdatesList myUpdates = new SessionUpdatesList<>(realm, wrappedEntity); + SessionEntityWrapper wrappedEntity = new SessionEntityWrapper<>(entity); + SessionUpdatesList myUpdates = new SessionUpdatesList<>(realm, wrappedEntity); updates.put(key, myUpdates); // Run the update now, so reader in same transaction can see it @@ -91,19 +91,19 @@ public class InfinispanChangelogBasedTransaction extend } - public void reloadEntityInCurrentTransaction(RealmModel realm, String key, SessionEntityWrapper entity) { + public void reloadEntityInCurrentTransaction(RealmModel realm, K key, SessionEntityWrapper entity) { if (entity == null) { throw new IllegalArgumentException("Null entity not allowed"); } - SessionEntityWrapper latestEntity = cache.get(key); + SessionEntityWrapper latestEntity = cache.get(key); if (latestEntity == null) { return; } - SessionUpdatesList newUpdates = new SessionUpdatesList<>(realm, latestEntity); + SessionUpdatesList newUpdates = new SessionUpdatesList<>(realm, latestEntity); - SessionUpdatesList existingUpdates = updates.get(key); + SessionUpdatesList existingUpdates = updates.get(key); if (existingUpdates != null) { newUpdates.setUpdateTasks(existingUpdates.getUpdateTasks()); } @@ -112,10 +112,10 @@ public class InfinispanChangelogBasedTransaction extend } - public SessionEntityWrapper get(String key) { - SessionUpdatesList myUpdates = updates.get(key); + public SessionEntityWrapper get(K key) { + SessionUpdatesList myUpdates = updates.get(key); if (myUpdates == null) { - SessionEntityWrapper wrappedEntity = cache.get(key); + SessionEntityWrapper wrappedEntity = cache.get(key); if (wrappedEntity == null) { return null; } @@ -127,7 +127,7 @@ public class InfinispanChangelogBasedTransaction extend return wrappedEntity; } else { - S entity = myUpdates.getEntityWrapper().getEntity(); + V entity = myUpdates.getEntityWrapper().getEntity(); // If entity is scheduled for remove, we don't return it. boolean scheduledForRemove = myUpdates.getUpdateTasks().stream().filter((SessionUpdateTask task) -> { @@ -143,13 +143,13 @@ public class InfinispanChangelogBasedTransaction extend @Override protected void commitImpl() { - for (Map.Entry> entry : updates.entrySet()) { - SessionUpdatesList sessionUpdates = entry.getValue(); - SessionEntityWrapper sessionWrapper = sessionUpdates.getEntityWrapper(); + for (Map.Entry> entry : updates.entrySet()) { + SessionUpdatesList sessionUpdates = entry.getValue(); + SessionEntityWrapper sessionWrapper = sessionUpdates.getEntityWrapper(); RealmModel realm = sessionUpdates.getRealm(); - MergedUpdate merged = MergedUpdate.computeUpdate(sessionUpdates.getUpdateTasks(), sessionWrapper); + MergedUpdate merged = MergedUpdate.computeUpdate(sessionUpdates.getUpdateTasks(), sessionWrapper); if (merged != null) { // Now run the operation in our cluster @@ -162,8 +162,8 @@ public class InfinispanChangelogBasedTransaction extend } - private void runOperationInCluster(String key, MergedUpdate task, SessionEntityWrapper sessionWrapper) { - S session = sessionWrapper.getEntity(); + private void runOperationInCluster(K key, MergedUpdate task, SessionEntityWrapper sessionWrapper) { + V session = sessionWrapper.getEntity(); SessionUpdateTask.CacheOperation operation = task.getOperation(session); // Don't need to run update of underlying entity. Local updates were already run @@ -182,9 +182,14 @@ public class InfinispanChangelogBasedTransaction extend .put(key, sessionWrapper, task.getLifespanMs(), TimeUnit.MILLISECONDS); break; case ADD_IF_ABSENT: - SessionEntityWrapper existing = cache.putIfAbsent(key, sessionWrapper); + SessionEntityWrapper existing = cache.putIfAbsent(key, sessionWrapper); 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; case REPLACE: @@ -197,12 +202,12 @@ public class InfinispanChangelogBasedTransaction extend } - private void replace(String key, MergedUpdate task, SessionEntityWrapper oldVersionEntity) { + private void replace(K key, MergedUpdate task, SessionEntityWrapper oldVersionEntity) { boolean replaced = false; - S session = oldVersionEntity.getEntity(); + V session = oldVersionEntity.getEntity(); while (!replaced) { - SessionEntityWrapper newVersionEntity = generateNewVersionAndWrapEntity(session, oldVersionEntity.getLocalMetadata()); + SessionEntityWrapper newVersionEntity = generateNewVersionAndWrapEntity(session, oldVersionEntity.getLocalMetadata()); // Atomic cluster-aware replace replaced = cache.replace(key, oldVersionEntity, newVersionEntity); @@ -235,7 +240,7 @@ public class InfinispanChangelogBasedTransaction extend protected void rollbackImpl() { } - private SessionEntityWrapper generateNewVersionAndWrapEntity(S entity, Map localMetadata) { + private SessionEntityWrapper generateNewVersionAndWrapEntity(V entity, Map localMetadata) { return new SessionEntityWrapper<>(localMetadata, entity); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/LoginFailuresUpdateTask.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/LoginFailuresUpdateTask.java new file mode 100644 index 0000000000..04ed05a318 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/LoginFailuresUpdateTask.java @@ -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 Marek Posolda + */ +public abstract class LoginFailuresUpdateTask implements SessionUpdateTask { + + @Override + public CacheOperation getOperation(LoginFailureEntity session) { + return CacheOperation.REPLACE; + } + + @Override + public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper sessionWrapper) { + return CrossDCMessageStatus.SYNC; + } +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/MergedUpdate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/MergedUpdate.java index 1f24f84faf..8e0cf0ed82 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/MergedUpdate.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/MergedUpdate.java @@ -32,7 +32,7 @@ class MergedUpdate implements SessionUpdateTask { private CrossDCMessageStatus crossDCMessageStatus; - public MergedUpdate(CacheOperation operation, CrossDCMessageStatus crossDCMessageStatus) { + private MergedUpdate(CacheOperation operation, CrossDCMessageStatus crossDCMessageStatus) { this.operation = operation; this.crossDCMessageStatus = crossDCMessageStatus; } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionUpdateTask.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionUpdateTask.java index b79ea16b64..244a88bd99 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionUpdateTask.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionUpdateTask.java @@ -49,7 +49,7 @@ public interface SessionUpdateTask { if (this == ADD | this == 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; diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/UserSessionUpdateTask.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/UserSessionUpdateTask.java index 4fd4bbebf1..1db36a1c7e 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/UserSessionUpdateTask.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/UserSessionUpdateTask.java @@ -17,8 +17,6 @@ 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; /** diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/sessions/LastSessionRefreshChecker.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/sessions/LastSessionRefreshChecker.java index f9adf9b8cb..25e0df942f 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/sessions/LastSessionRefreshChecker.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/sessions/LastSessionRefreshChecker.java @@ -60,7 +60,7 @@ public class LastSessionRefreshChecker { Integer lsrr = sessionWrapper.getLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE); 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; } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java index 0b992254b2..cdb841e6f2 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import org.keycloak.sessions.AuthenticationSessionModel; @@ -29,6 +30,8 @@ import org.keycloak.sessions.AuthenticationSessionModel; */ public class AuthenticationSessionEntity extends SessionEntity { + private String id; + private String clientUuid; private String authUserId; @@ -46,6 +49,14 @@ public class AuthenticationSessionEntity extends SessionEntity { private Set requiredActions = new HashSet<>(); private Map userSessionNotes; + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + public String getClientUuid() { return clientUuid; } @@ -149,4 +160,26 @@ public class AuthenticationSessionEntity extends SessionEntity { public void setAuthNotes(Map 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()); + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java index d25f58b831..5b328ecc91 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java @@ -17,15 +17,12 @@ package org.keycloak.models.sessions.infinispan.entities; -import java.io.Serializable; - /** * @author Stian Thorgersen */ -public class LoginFailureEntity implements Serializable { +public class LoginFailureEntity extends SessionEntity { private String userId; - private String realm; private int failedLoginNotBefore; private int numFailures; private long lastFailure; @@ -39,14 +36,6 @@ public class LoginFailureEntity implements Serializable { this.userId = userId; } - public String getRealm() { - return realm; - } - - public void setRealm(String realm) { - this.realm = realm; - } - public int getFailedLoginNotBefore() { return failedLoginNotBefore; } @@ -85,4 +74,30 @@ public class LoginFailureEntity implements Serializable { this.lastFailure = 0; 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); + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java index c45237963b..318b1ba547 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureKey.java @@ -52,4 +52,9 @@ public class LoginFailureKey implements Serializable { return result; } + + @Override + public String toString() { + return String.format("LoginFailureKey [ realm=%s. userId=%s ]", realm, userId); + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java index 25ac2a4efe..37f8e081ce 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java @@ -26,17 +26,8 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper; */ public abstract class SessionEntity implements Serializable { - private String id; - private String realm; - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } public String getRealm() { return realm; @@ -46,26 +37,13 @@ public abstract class SessionEntity implements Serializable { 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) { - throw new IllegalStateException("Not yet implemented"); + if (localEntityWrapper == null) { + return new SessionEntityWrapper<>(this); + } else { + return new SessionEntityWrapper<>(localEntityWrapper.getLocalMetadata(), this); + } }; } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java index 5d0edb09fe..8ac85a17e4 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java @@ -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 public static final String LAST_SESSION_REFRESH_REMOTE = "lsrr"; + private String id; + private String user; private String brokerSessionId; @@ -62,6 +64,14 @@ public class UserSessionEntity extends SessionEntity { private UserSessionModel.State state; + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + private Map notes = new ConcurrentHashMap<>(); private Map authenticatedClientSessions = new ConcurrentHashMap<>(); @@ -162,6 +172,23 @@ public class UserSessionEntity extends SessionEntity { 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 public String toString() { return String.format("UserSessionEntity [id=%s, realm=%s, lastSessionRefresh=%d, clients=%s]", getId(), getRealm(), getLastSessionRefresh(), diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheInvoker.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheInvoker.java index 89fd215902..8891469f3b 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheInvoker.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheInvoker.java @@ -55,13 +55,13 @@ public class RemoteCacheInvoker { } - public void runTask(KeycloakSession kcSession, RealmModel realm, String cacheName, String key, SessionUpdateTask task, SessionEntityWrapper sessionWrapper) { + public void runTask(KeycloakSession kcSession, RealmModel realm, String cacheName, K key, SessionUpdateTask task, SessionEntityWrapper sessionWrapper) { RemoteCacheContext context = remoteCaches.get(cacheName); if (context == null) { return; } - S session = sessionWrapper.getEntity(); + V session = sessionWrapper.getEntity(); SessionUpdateTask.CacheOperation operation = task.getOperation(session); SessionUpdateTask.CrossDCMessageStatus status = task.getCrossDCMessageStatus(sessionWrapper); @@ -82,8 +82,8 @@ public class RemoteCacheInvoker { } - private void runOnRemoteCache(RemoteCache remoteCache, long maxIdleMs, String key, SessionUpdateTask task, SessionEntityWrapper sessionWrapper) { - S session = sessionWrapper.getEntity(); + private void runOnRemoteCache(RemoteCache remoteCache, long maxIdleMs, K key, SessionUpdateTask task, SessionEntityWrapper sessionWrapper) { + V session = sessionWrapper.getEntity(); SessionUpdateTask.CacheOperation operation = task.getOperation(session); switch (operation) { @@ -96,13 +96,16 @@ public class RemoteCacheInvoker { break; case ADD_IF_ABSENT: final int currentTime = Time.currentTime(); - SessionEntity existing = (SessionEntity) remoteCache + SessionEntity existing = remoteCache .withFlags(Flag.FORCE_RETURN_VALUE) .putIfAbsent(key, session, -1, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS); 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; case REPLACE: replace(remoteCache, task.getLifespanMs(), maxIdleMs, key, task); @@ -113,16 +116,16 @@ public class RemoteCacheInvoker { } - private void replace(RemoteCache remoteCache, long lifespanMs, long maxIdleMs, String key, SessionUpdateTask task) { + private void replace(RemoteCache remoteCache, long lifespanMs, long maxIdleMs, K key, SessionUpdateTask task) { boolean replaced = false; while (!replaced) { - VersionedValue versioned = remoteCache.getVersioned(key); + VersionedValue versioned = remoteCache.getVersioned(key); if (versioned == null) { logger.warnf("Not found entity to replace for key '%s'", key); return; } - S session = versioned.getValue(); + V session = versioned.getValue(); // Run task on the remote session task.runUpdate(session); diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionListener.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionListener.java index d29e2206ed..b186833867 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionListener.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionListener.java @@ -44,12 +44,12 @@ import org.infinispan.client.hotrod.VersionedValue; * @author Marek Posolda */ @ClientListener -public class RemoteCacheSessionListener { +public class RemoteCacheSessionListener { protected static final Logger logger = Logger.getLogger(RemoteCacheSessionListener.class); - private Cache cache; - private RemoteCache remoteCache; + private Cache> cache; + private RemoteCache remoteCache; private boolean distributed; private String myAddress; @@ -58,7 +58,7 @@ public class RemoteCacheSessionListener { } - protected void init(KeycloakSession session, Cache cache, RemoteCache remoteCache) { + protected void init(KeycloakSession session, Cache> cache, RemoteCache remoteCache) { this.cache = cache; this.remoteCache = remoteCache; @@ -73,7 +73,7 @@ public class RemoteCacheSessionListener { @ClientCacheEntryCreated public void created(ClientCacheEntryCreatedEvent event) { - String key = (String) event.getKey(); + K key = (K) event.getKey(); if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) { // Should load it from remoteStore @@ -84,7 +84,7 @@ public class RemoteCacheSessionListener { @ClientCacheEntryModified public void updated(ClientCacheEntryModifiedEvent event) { - String key = (String) event.getKey(); + K key = (K) event.getKey(); if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) { @@ -94,7 +94,7 @@ public class RemoteCacheSessionListener { 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? boolean replaced = false; int replaceRetries = 0; @@ -102,8 +102,8 @@ public class RemoteCacheSessionListener { do { replaceRetries++; - SessionEntityWrapper localEntityWrapper = cache.get(key); - VersionedValue remoteSessionVersioned = remoteCache.getVersioned(key); + SessionEntityWrapper localEntityWrapper = cache.get(key); + VersionedValue remoteSessionVersioned = remoteCache.getVersioned(key); if (remoteSessionVersioned == null || remoteSessionVersioned.getVersion() < eventVersion) { try { 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); - SessionEntityWrapper sessionWrapper = remoteSession.mergeRemoteEntityWithLocalEntity(localEntityWrapper); + SessionEntityWrapper sessionWrapper = remoteSession.mergeRemoteEntityWithLocalEntity(localEntityWrapper); // 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) @@ -135,7 +135,7 @@ public class RemoteCacheSessionListener { @ClientCacheEntryRemoved public void removed(ClientCacheEntryRemovedEvent event) { - String key = (String) event.getKey(); + K key = (K) event.getKey(); if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) { // 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 - protected boolean shouldUpdateLocalCache(ClientEvent.Type type, String key, boolean commandRetried) { + protected boolean shouldUpdateLocalCache(ClientEvent.Type type, K key, boolean commandRetried) { boolean result; // Case when cache is stopping or stopped already @@ -184,7 +184,7 @@ public class RemoteCacheSessionListener { } - public static RemoteCacheSessionListener createListener(KeycloakSession session, Cache cache, RemoteCache remoteCache) { + public static RemoteCacheSessionListener createListener(KeycloakSession session, Cache> cache, RemoteCache remoteCache) { /*boolean isCoordinator = InfinispanUtil.isCoordinator(cache); // Just cluster coordinator will fetch userSessions from remote cache. @@ -198,7 +198,7 @@ public class RemoteCacheSessionListener { listener = new DontFetchInitialStateCacheListener(); }*/ - RemoteCacheSessionListener listener = new RemoteCacheSessionListener(); + RemoteCacheSessionListener listener = new RemoteCacheSessionListener<>(); listener.init(session, cache, remoteCache); return listener; diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionsLoader.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionsLoader.java index 00c133a127..789fc160e8 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionsLoader.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionsLoader.java @@ -115,14 +115,14 @@ public class RemoteCacheSessionsLoader implements SessionLoader { for (Map.Entry entry : remoteObjects.entrySet()) { try { - String key = (String) marshaller.objectFromByteBuffer(entry.getKey()); + Object key = marshaller.objectFromByteBuffer(entry.getKey()); SessionEntity entity = (SessionEntity) marshaller.objectFromByteBuffer(entry.getValue()); SessionEntityWrapper entityWrapper = new SessionEntityWrapper(entity); decoratedCache.putAsync(key, entityWrapper); } catch (Exception e) { - log.warnf("Error loading session from remote cache", e); + log.warn("Error loading session from remote cache", e); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java index f75391cd61..815a54d4b7 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java @@ -48,7 +48,7 @@ public class Mappers { return new UserSessionEntityMapper(); } - public static Function, LoginFailureKey> loginFailureId() { + public static Function>, LoginFailureKey> loginFailureId() { return new LoginFailureIdMapper(); } @@ -103,9 +103,9 @@ public class Mappers { } - private static class LoginFailureIdMapper implements Function, LoginFailureKey>, Serializable { + private static class LoginFailureIdMapper implements Function>, LoginFailureKey>, Serializable { @Override - public LoginFailureKey apply(Map.Entry entry) { + public LoginFailureKey apply(Map.Entry> entry) { return entry.getKey(); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserLoginFailurePredicate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserLoginFailurePredicate.java index ae0b28d2b3..499600073e 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserLoginFailurePredicate.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserLoginFailurePredicate.java @@ -17,6 +17,7 @@ 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.LoginFailureKey; @@ -27,7 +28,7 @@ import java.util.function.Predicate; /** * @author Stian Thorgersen */ -public class UserLoginFailurePredicate implements Predicate>, Serializable { +public class UserLoginFailurePredicate implements Predicate>>, Serializable { private String realm; @@ -40,8 +41,8 @@ public class UserLoginFailurePredicate implements Predicate entry) { - LoginFailureEntity e = entry.getValue(); + public boolean test(Map.Entry> entry) { + LoginFailureEntity e = entry.getValue().getEntity(); return realm.equals(e.getRealm()); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index b9352c0588..b543d6b2f4 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -39,7 +39,6 @@ import org.keycloak.models.UserProvider; import org.keycloak.models.jpa.entities.CredentialAttributeEntity; import org.keycloak.models.jpa.entities.CredentialEntity; 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.UserConsentProtocolMapperEntity; 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 public void grantToAllUsers(RealmModel realm, RoleModel role) { int num = em.createNamedQuery("grantRoleToAllUsers") diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java index 50b77607a6..771487f754 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java @@ -103,6 +103,9 @@ public class UserEntity { @Column(name="SERVICE_ACCOUNT_CLIENT_LINK") protected String serviceAccountClientLink; + @Column(name="NOT_BEFORE") + protected int notBefore; + public String getId() { return id; } @@ -224,6 +227,14 @@ public class UserEntity { this.serviceAccountClientLink = serviceAccountClientLink; } + public int getNotBefore() { + return notBefore; + } + + public void setNotBefore(int notBefore) { + this.notBefore = notBefore; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java index cded4e997b..f6de4312a3 100644 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java @@ -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 attrs = getAttributes(realm, userId); + String notBeforeStr = attrs.getFirst("fedNotBefore"); + + return notBeforeStr==null ? 0 : Integer.parseInt(notBeforeStr); + } @Override public Set getGroups(RealmModel realm, String userId) { diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.3.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.3.0.xml new file mode 100644 index 0000000000..c6d201eb49 --- /dev/null +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.3.0.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml index ae7d98b4e4..96b9a18dd0 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml @@ -48,4 +48,5 @@ + diff --git a/pom.xml b/pom.xml index 7ae460f9bf..df441dcf25 100755 --- a/pom.xml +++ b/pom.xml @@ -49,8 +49,8 @@ 1.2.2.Final 3.0.0.Beta30 - 1.1.0.Beta32 - 1.0.0.Beta14 + 1.1.0.CR4 + 1.0.0.CR1 0.66.15 4.5 @@ -120,7 +120,7 @@ 7.5.Final 1.9.0 1.0.4 - 1.7.2 + 1.7.6 2.3.7 1.1.0.Final 1.6.5 diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java index 6a6c1ff3eb..f010dd3729 100644 --- a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java @@ -49,7 +49,12 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProvider { @Override 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 diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 6b7016ff5f..ef95c0ae6a 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -185,6 +185,8 @@ public class ModelToRepresentation { rep.setDisableableCredentialTypes(session.userCredentialManager().getDisableableCredentialTypes(realm, user)); rep.setFederationLink(user.getFederationLink()); + rep.setNotBefore(session.users().getNotBeforeOfUser(realm, user)); + List reqActions = new ArrayList(); Set requiredActions = user.getRequiredActions(); for (String ra : requiredActions){ diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index fe27fae666..3fdddde16a 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -1454,6 +1454,11 @@ public class RepresentationToModel { session.users().addConsent(newRealm, user.getId(), consentModel); } } + + if (userRep.getNotBefore() != null) { + session.users().setNotBeforeForUser(newRealm, user, userRep.getNotBefore()); + } + if (userRep.getServiceAccountClientId() != null) { String clientId = userRep.getServiceAccountClientId(); ClientModel client = newRealm.getClientByClientId(clientId); @@ -2378,6 +2383,9 @@ public class RepresentationToModel { federatedStorage.addConsent(newRealm, userRep.getId(), consentModel); } } + if (userRep.getNotBefore() != null) { + federatedStorage.setNotBeforeForUser(newRealm, userRep.getId(), userRep.getNotBefore()); + } } diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java index 46bb4c3117..c337f3ac87 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java @@ -51,6 +51,8 @@ public interface UserProvider extends Provider, void updateConsent(RealmModel realm, String userId, UserConsentModel consent); 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); List getUsers(RealmModel realm, boolean includeServiceAccounts); diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java index 1d12d3626f..da42c4c868 100755 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java @@ -36,6 +36,7 @@ public interface UserFederatedStorageProvider extends Provider, UserAttributeFederatedStorage, UserBrokerLinkFederatedStorage, UserConsentFederatedStorage, + UserNotBeforeFederatedStorage, UserGroupMembershipFederatedStorage, UserRequiredActionsFederatedStorage, UserRoleMappingsFederatedStorage, diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserNotBeforeFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserNotBeforeFederatedStorage.java new file mode 100644 index 0000000000..4b18026645 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserNotBeforeFederatedStorage.java @@ -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 Marek Posolda + */ +public interface UserNotBeforeFederatedStorage { + + void setNotBeforeForUser(RealmModel realm, String userId, int notBefore); + int getNotBeforeOfUser(RealmModel realm, String userId); +} diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index facce6ca2d..fa1e238b04 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -530,6 +530,10 @@ public class ExportUtils { userRep.setClientConsents(consentReps); } + // Not Before + int notBefore = session.users().getNotBeforeOfUser(realm, user); + userRep.setNotBefore(notBefore); + // Service account if (user.getServiceAccountClientLink() != null) { String clientInternalId = user.getServiceAccountClientLink(); @@ -717,6 +721,10 @@ public class ExportUtils { userRep.setClientConsents(consentReps); } + // Not Before + int notBefore = session.userFederatedStorage().getNotBeforeOfUser(realm, userRep.getId()); + userRep.setNotBefore(notBefore); + if (options.isGroupsAndRolesIncluded()) { List groups = new LinkedList<>(); for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) { diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java index c06caa0737..b854e52cd9 100755 --- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java +++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java @@ -180,9 +180,10 @@ public abstract class AuthorizationEndpointBase { return new AuthorizationEndpointChecks(authSession); } 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. - if (hasProcessedExecution(authSession)) { + if (shouldRestartAuthSession(authSession)) { logger.debug("New request from application received, but authentication session already exists. Restart existing authentication session"); authSession.restartSession(realm, client); } else { @@ -223,11 +224,18 @@ public abstract class AuthorizationEndpointBase { } + + protected boolean shouldRestartAuthSession(AuthenticationSessionModel authSession) { + return hasProcessedExecution(authSession); + } + + private boolean hasProcessedExecution(AuthenticationSessionModel authSession) { String lastProcessedExecution = authSession.getAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION); 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 private boolean shouldShowExpirePage(AuthenticationSessionModel authSession) { if (hasProcessedExecution(authSession)) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 6a26c692d5..769947a0b8 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -180,6 +180,9 @@ public class TokenManager { if (oldToken.getIssuedAt() < realm.getNotBefore()) { 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. @@ -207,9 +210,12 @@ public class TokenManager { if (!user.isEnabled()) { return false; } + if (token.getIssuedAt() < session.users().getNotBeforeOfUser(realm, user)) { + return false; + } ClientModel client = realm.getClientByClientId(token.getIssuedFor()); - if (client == null || !client.isEnabled()) { + if (client == null || !client.isEnabled() || token.getIssuedAt() < client.getNotBefore()) { return false; } @@ -816,9 +822,13 @@ public class TokenManager { res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime()); } } + int notBefore = realm.getNotBefore(); if (client.getNotBefore() > notBefore) notBefore = client.getNotBefore(); + int userNotBefore = session.users().getNotBeforeOfUser(realm, userSession.getUser()); + if (userNotBefore > notBefore) notBefore = userNotBefore; res.setNotBeforePolicy(notBefore); + return res; } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 38dbc8f5e4..f00c89237d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -49,6 +49,8 @@ import org.keycloak.util.TokenUtil; import javax.ws.rs.GET; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; + +import java.util.Objects; import java.util.regex.Matcher; 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 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); } diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java index 589dde3e6b..d63c98c144 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -58,6 +58,7 @@ import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.util.CacheControlUtil; +import org.keycloak.utils.MediaType; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; @@ -67,7 +68,6 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import java.io.IOException; @@ -594,7 +594,7 @@ public class SamlService extends AuthorizationEndpointBase { @GET @Path("clients/{client}") - @Produces(MediaType.TEXT_HTML) + @Produces(MediaType.TEXT_HTML_UTF_8) public Response idpInitiatedSSO(@PathParam("client") String clientUrlName, @QueryParam("RelayState") String relayState) { event.event(EventType.LOGIN); CacheControlUtil.noBackButtonCacheControlHeader(); diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index bc28fc4afd..02351204aa 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -817,6 +817,12 @@ public class AuthenticationManager { 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()); if (!isSessionValid(realm, userSession)) { // Check if accessToken was for the offline session. diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java index 3d71c2a663..dbb0e78c4b 100755 --- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java +++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java @@ -107,6 +107,8 @@ public class ResourceAdminManager { } public void logoutUser(URI requestUri, RealmModel realm, UserModel user, KeycloakSession keycloakSession) { + keycloakSession.users().setNotBeforeForUser(realm, user, Time.currentTime()); + List userSessions = keycloakSession.sessions().getUserSessions(realm, user); logoutUserSessions(requestUri, realm, userSessions); } diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index ac9bf807f6..9b26b18c07 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -18,6 +18,7 @@ package org.keycloak.services.resources; import org.jboss.logging.Logger; import org.keycloak.common.util.Base64Url; +import org.keycloak.common.util.Time; import org.keycloak.common.util.UriUtils; import org.keycloak.credential.CredentialModel; import org.keycloak.events.Details; @@ -59,9 +60,9 @@ import org.keycloak.services.validation.Validation; import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.storage.ReadOnlyException; import org.keycloak.util.JsonSerialization; +import org.keycloak.utils.MediaType; import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; @@ -252,33 +253,24 @@ public class AccountService extends AbstractSecuredLocalService { */ @Path("/") @GET - @Produces(MediaType.TEXT_HTML) 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); - /** - * Get account information. - * - * @return - */ - @Path("/") - @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 itr = rep.getAttributes().keySet().iterator(); - while (itr.hasNext()) { - if (itr.next().startsWith("keycloak.")) { - itr.remove(); + UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, auth.getUser()); + if (rep.getAttributes() != null) { + Iterator 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) { @@ -514,6 +506,11 @@ public class AccountService extends AbstractSecuredLocalService { csrfCheck(stateChecker); 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 userSessions = session.sessions().getUserSessions(realm, user); for (UserSessionModel userSession : userSessions) { AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 61f6254710..9503e02ea5 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -509,10 +509,13 @@ public class AuthenticationManagementResource { rep.setId(execution.getId()); if (factory.isConfigurable()) { - AuthenticatorConfigModel authenticatorConfig = realm.getAuthenticatorConfigById(execution.getAuthenticatorConfig()); + String authenticatorConfigId = execution.getAuthenticatorConfig(); + if(authenticatorConfigId != null) { + AuthenticatorConfigModel authenticatorConfig = realm.getAuthenticatorConfigById(authenticatorConfigId); - if (authenticatorConfig != null) { - rep.setAlias(authenticatorConfig.getAlias()); + if (authenticatorConfig != null) { + rep.setAlias(authenticatorConfig.getAlias()); + } } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java index b00f2e4607..7fd223fd30 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java @@ -146,7 +146,8 @@ public class RealmsAdminResource { return Response.created(location).build(); } 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"); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java index fbd318ae74..21943ccd79 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java @@ -508,6 +508,8 @@ public class UserResource { public void logout() { auth.users().requireManage(user); + session.users().setNotBeforeForUser(realm, user, Time.currentTime()); + List userSessions = session.sessions().getUserSessions(realm, user); for (UserSessionModel userSession : userSessions) { AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true); diff --git a/services/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java index 2baeb8ceb2..8c5b633f89 100755 --- a/services/src/main/java/org/keycloak/storage/UserStorageManager.java +++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -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. * diff --git a/services/src/main/java/org/keycloak/utils/MediaType.java b/services/src/main/java/org/keycloak/utils/MediaType.java index acb0b4134e..4842f384f3 100644 --- a/services/src/main/java/org/keycloak/utils/MediaType.java +++ b/services/src/main/java/org/keycloak/utils/MediaType.java @@ -40,4 +40,8 @@ public class MediaType { 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 String APPLICATION_XML = javax.ws.rs.core.MediaType.APPLICATION_XML; + + public static final String TEXT_XML = javax.ws.rs.core.MediaType.TEXT_XML; + } diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml index 0bcb2b899b..78a76480b1 100644 --- a/testsuite/integration-arquillian/pom.xml +++ b/testsuite/integration-arquillian/pom.xml @@ -45,9 +45,9 @@ 2.53.0 2.0.1.Final 2.1.0.Alpha3 - 2.1.0.Alpha2 + 2.1.0.Beta1 1.0.1.Final - 1.2.0.Beta2 + 1.2.0.Alpha2 2.2.2 1.0.0.Alpha2 diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java index f47061a305..988108809d 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java @@ -17,7 +17,6 @@ package org.keycloak.testsuite.rest; -import org.jboss.resteasy.annotations.Query; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.HttpRequest; 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.resources.RealmsResource; import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource; +import org.keycloak.utils.MediaType; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -38,7 +38,6 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; @@ -69,21 +68,21 @@ public class TestApplicationResourceProvider implements RealmResourceProvider { } @POST - @Consumes(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN_UTF_8) @Path("/admin/k_logout") public void adminLogout(String data) throws JWSInputException { adminLogoutActions.add(new JWSInput(data).readJsonContent(LogoutAction.class)); } @POST - @Consumes(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN_UTF_8) @Path("/admin/k_push_not_before") public void adminPushNotBefore(String data) throws JWSInputException { adminPushNotBeforeActions.add(new JWSInput(data).readJsonContent(PushNotBeforeAction.class)); } @POST - @Consumes(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN_UTF_8) @Path("/admin/k_test_available") public void testAvailable(String data) throws JWSInputException { adminTestAvailabilityAction.add(new JWSInput(data).readJsonContent(TestAvailabilityAction.class)); @@ -119,7 +118,7 @@ public class TestApplicationResourceProvider implements RealmResourceProvider { } @POST - @Produces(MediaType.TEXT_HTML) + @Produces(MediaType.TEXT_HTML_UTF_8) @Path("/{action}") public String post(@PathParam("action") String action) { String title = "APP_REQUEST"; @@ -148,7 +147,7 @@ public class TestApplicationResourceProvider implements RealmResourceProvider { } @GET - @Produces(MediaType.TEXT_HTML) + @Produces(MediaType.TEXT_HTML_UTF_8) @Path("/{action}") public String get(@PathParam("action") String action) { //String requestUri = session.getContext().getUri().getRequestUri().toString(); @@ -171,7 +170,7 @@ public class TestApplicationResourceProvider implements RealmResourceProvider { @GET @NoCache - @Produces(MediaType.TEXT_HTML) + @Produces(MediaType.TEXT_HTML_UTF_8) @Path("/get-account-profile") public String getAccountProfile(@QueryParam("token") String token, @QueryParam("account-uri") String accountUri) { StringBuilder sb = new StringBuilder(); diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java index 9847b27dcb..964e80da8b 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java @@ -27,18 +27,17 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; import org.infinispan.Cache; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.remoting.transport.Transport; -import org.infinispan.remoting.transport.jgroups.JGroupsTransport; import org.jgroups.JChannel; import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.util.InfinispanUtil; import org.keycloak.testsuite.rest.representation.JGroupsStats; +import org.keycloak.utils.MediaType; /** * @author Marek Posolda @@ -82,7 +81,7 @@ public class TestCacheResource { @GET @Path("/clear") - @Consumes(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN_UTF_8) public void clear() { cache.clear(); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BasicAuthExample.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BasicAuthExample.java deleted file mode 100644 index 09774b69d0..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BasicAuthExample.java +++ /dev/null @@ -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; - } - -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerPortalExample.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerPortalExample.java deleted file mode 100644 index 8f02dc609a..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerPortalExample.java +++ /dev/null @@ -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(); - } - -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/DatabaseServiceExample.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/DatabaseServiceExample.java deleted file mode 100644 index c856064a9d..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/DatabaseServiceExample.java +++ /dev/null @@ -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; - } - -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/MultiTenantExample.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/MultiTenantExample.java deleted file mode 100644 index 7cff7140d4..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/MultiTenantExample.java +++ /dev/null @@ -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()); - } - -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ProductPortalExample.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ProductPortalExample.java deleted file mode 100644 index 81c8b0a0cb..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ProductPortalExample.java +++ /dev/null @@ -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(); - } - - -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLPostEncExample.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLPostEncExample.java deleted file mode 100644 index 6772ea9d90..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLPostEncExample.java +++ /dev/null @@ -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(); - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLPostSigExample.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLPostSigExample.java deleted file mode 100644 index 7105bd1a64..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLPostSigExample.java +++ /dev/null @@ -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(); - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLRedirectSigExample.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLRedirectSigExample.java deleted file mode 100644 index 78e1316a33..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLRedirectSigExample.java +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java index afefde3833..96c3c8df19 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java @@ -17,18 +17,16 @@ 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.PushNotBeforeAction; import org.keycloak.representations.adapters.action.TestAvailabilityAction; +import org.keycloak.utils.MediaType; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; /** * @author Stian Thorgersen @@ -56,7 +54,7 @@ public interface TestApplicationResource { void clearAdminActions(); @GET - @Produces(MediaType.TEXT_HTML) + @Produces(MediaType.TEXT_HTML_UTF_8) @Path("/get-account-profile") String getAccountProfile(@QueryParam("token") String token, @QueryParam("account-uri") String accountUri); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java index e1aee2a374..1c362ea3a1 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java @@ -17,18 +17,16 @@ package org.keycloak.testsuite.client.resources; -import java.util.Map; -import java.util.Set; +import org.keycloak.testsuite.rest.representation.JGroupsStats; +import org.keycloak.testsuite.rest.representation.RemoteCacheStats; +import org.keycloak.utils.MediaType; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; - -import org.keycloak.testsuite.rest.representation.JGroupsStats; -import org.keycloak.testsuite.rest.representation.RemoteCacheStats; +import java.util.Set; /** * @author Marek Posolda @@ -55,7 +53,7 @@ public interface TestingCacheResource { @GET @Path("/clear") - @Consumes(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN_UTF_8) void clear(); @GET diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java index 2787c0e82f..080afcf769 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java @@ -18,14 +18,13 @@ package org.keycloak.testsuite.client.resources; import org.jboss.resteasy.annotations.cache.NoCache; -import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.components.TestProvider; 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.GET; @@ -35,8 +34,6 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import java.util.List; import java.util.Map; @@ -259,8 +256,8 @@ public interface TestingResource { @POST @Path("/run-on-server") - @Consumes(MediaType.TEXT_PLAIN) - @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN_UTF_8) + @Produces(MediaType.TEXT_PLAIN_UTF_8) String runOnServer(String runOnServer); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AppServerWelcomePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AppServerWelcomePage.java new file mode 100644 index 0000000000..d7056ee872 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AppServerWelcomePage.java @@ -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 Pedro Igor + */ +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(); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractBasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractBasicAuthExampleAdapterTest.java deleted file mode 100644 index 57868f28be..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractBasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.keycloak.testsuite.adapter.example; - -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.graphene.page.Page; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.Test; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest; -import org.keycloak.testsuite.adapter.page.BasicAuthExample; - -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.core.Response; -import java.io.File; -import java.io.IOException; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.keycloak.testsuite.auth.page.AuthRealm.EXAMPLE; -import static org.keycloak.testsuite.util.IOUtil.loadRealm; - -public abstract class AbstractBasicAuthExampleAdapterTest extends AbstractExampleAdapterTest { - - @Page - private BasicAuthExample basicAuthExample; - - @Deployment(name = BasicAuthExample.DEPLOYMENT_NAME) - private static WebArchive basicAuthExample() throws IOException { - return exampleDeployment("examples-basicauth"); - } - - @Override - public void addAdapterTestRealms(List testRealms) { - testRealms.add(loadRealm(new File(EXAMPLES_HOME_DIR + "/basic-auth/basicauthrealm.json"))); - } - - @Override - public void setDefaultPageUriParameters() { - super.setDefaultPageUriParameters(); - testRealmPage.setAuthRealm(EXAMPLE); - } - - @Test - public void testBasicAuthExample() { - String value = "hello"; - Client client = ClientBuilder.newClient(); - - Response response = client.target(basicAuthExample - .setTemplateValues("admin", "password", value).buildUri()).request().get(); - assertEquals(200, response.getStatus()); - assertEquals(value, response.readEntity(String.class)); - response.close(); - - response = client.target(basicAuthExample - .setTemplateValues("invalid-user", "password", value).buildUri()).request().get(); - assertEquals(401, response.getStatus()); - String readResponse = response.readEntity(String.class); - assertTrue(readResponse.contains("Unauthorized") || readResponse.contains("Status 401")); - response.close(); - - response = client.target(basicAuthExample - .setTemplateValues("admin", "invalid-password", value).buildUri()).request().get(); - assertEquals(401, response.getStatus()); - readResponse = response.readEntity(String.class); - assertTrue(readResponse.contains("Unauthorized") || readResponse.contains("Status 401")); - response.close(); - - client.close(); - } - -} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java deleted file mode 100644 index 4db4a3c46a..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java +++ /dev/null @@ -1,373 +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.example; - -import org.apache.commons.io.FileUtils; -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.graphene.page.Page; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.keycloak.admin.client.resource.ClientResource; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest; -import org.keycloak.testsuite.adapter.page.CustomerPortalExample; -import org.keycloak.testsuite.adapter.page.DatabaseServiceExample; -import org.keycloak.testsuite.adapter.page.ProductPortalExample; -import org.keycloak.testsuite.admin.ApiUtil; -import org.keycloak.testsuite.auth.page.account.Account; -import org.keycloak.testsuite.auth.page.account.Applications; -import org.keycloak.testsuite.auth.page.login.OAuthGrant; -import org.keycloak.testsuite.console.page.events.Config; -import org.keycloak.testsuite.console.page.events.LoginEvents; -import org.openqa.selenium.By; -import org.openqa.selenium.WebElement; - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO; -import static org.keycloak.testsuite.util.IOUtil.loadRealm; -import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith; - -public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdapterTest { - - @Page - private CustomerPortalExample customerPortalExamplePage; - - @Page - private ProductPortalExample productPortalExamplePage; - - @Page - private DatabaseServiceExample databaseServiceExamplePage; - - @Page - private Account testRealmAccountPage; - - @Page - private Config configPage; - - @Page - private LoginEvents loginEventsPage; - - @Page - private OAuthGrant oAuthGrantPage; - - @Page - private Applications applicationsPage; - - @Deployment(name = CustomerPortalExample.DEPLOYMENT_NAME) - private static WebArchive customerPortalExample() throws IOException { - return exampleDeployment(CustomerPortalExample.DEPLOYMENT_NAME); - } - - @Deployment(name = ProductPortalExample.DEPLOYMENT_NAME) - private static WebArchive productPortalExample() throws IOException { - return exampleDeployment(ProductPortalExample.DEPLOYMENT_NAME); - } - - @Deployment(name = DatabaseServiceExample.DEPLOYMENT_NAME) - private static WebArchive databaseServiceExample() throws IOException { - return exampleDeployment("database-service"); - } - - @Override - public void addAdapterTestRealms(List testRealms) { - testRealms.add( - loadRealm(new File(EXAMPLES_HOME_DIR + "/demo-template/testrealm.json"))); - } - - @Override - public void setDefaultPageUriParameters() { - super.setDefaultPageUriParameters(); - testRealmPage.setAuthRealm(DEMO); - testRealmLoginPage.setAuthRealm(DEMO); - testRealmAccountPage.setAuthRealm(DEMO); - configPage.setConsoleRealm(DEMO); - loginEventsPage.setConsoleRealm(DEMO); - applicationsPage.setAuthRealm(DEMO); - } - - @Before - public void beforeDemoExampleTest() { - customerPortalExamplePage.navigateTo(); - driver.manage().deleteAllCookies(); - productPortalExamplePage.navigateTo(); - driver.manage().deleteAllCookies(); - } - - @Test - public void customerPortalListingTest() { - - customerPortalExamplePage.navigateTo(); - customerPortalExamplePage.customerListing(); - - testRealmLoginPage.form().login("bburke@redhat.com", "password"); - - assertCurrentUrlStartsWith(customerPortalExamplePage); - customerPortalExamplePage.waitForCustomerListingHeader(); - - Assert.assertTrue(driver.getPageSource().contains("Username: bburke@redhat.com")); - Assert.assertTrue(driver.getPageSource().contains("Bill Burke")); - Assert.assertTrue(driver.getPageSource().contains("Stian Thorgersen")); - } - - @Test - public void customerPortalSessionTest() { - - customerPortalExamplePage.navigateTo(); - customerPortalExamplePage.customerSession(); - - testRealmLoginPage.form().login("bburke@redhat.com", "password"); - - assertCurrentUrlStartsWith(customerPortalExamplePage); - - customerPortalExamplePage.waitForCustomerSessionHeader(); - Assert.assertTrue(driver.getPageSource().contains("You visited this page")); - } - - @Test - public void productPortalListingTest() { - - productPortalExamplePage.navigateTo(); - productPortalExamplePage.productListing(); - - testRealmLoginPage.form().login("bburke@redhat.com", "password"); - - assertCurrentUrlStartsWith(productPortalExamplePage); - productPortalExamplePage.waitForProductListingHeader(); - - Assert.assertTrue(driver.getPageSource().contains("iphone")); - Assert.assertTrue(driver.getPageSource().contains("ipad")); - Assert.assertTrue(driver.getPageSource().contains("ipod")); - - productPortalExamplePage.goToCustomers(); - } - - @Test - public void goToProductPortalWithOneLoginTest() { - - productPortalExamplePage.navigateTo(); - productPortalExamplePage.productListing(); - - testRealmLoginPage.form().login("bburke@redhat.com", "password"); - - assertCurrentUrlStartsWith(productPortalExamplePage); - productPortalExamplePage.waitForProductListingHeader(); - productPortalExamplePage.goToCustomers(); - - assertCurrentUrlStartsWith(customerPortalExamplePage); - customerPortalExamplePage.customerListing(); - customerPortalExamplePage.goToProducts(); - assertCurrentUrlStartsWith(productPortalExamplePage); - } - - @Test - public void logoutFromAllAppsTest() { - - productPortalExamplePage.navigateTo(); - productPortalExamplePage.productListing(); - - testRealmLoginPage.form().login("bburke@redhat.com", "password"); - - assertCurrentUrlStartsWith(productPortalExamplePage); - productPortalExamplePage.waitForProductListingHeader(); - - if (isRelative()) { //KEYCLOAK-1546 - productPortalExamplePage.logOut(); - } else { - driver.navigate().to(testRealmPage.getOIDCLogoutUrl() + "?redirect_uri=" + productPortalExamplePage); - } - - assertCurrentUrlStartsWith(productPortalExamplePage); - productPortalExamplePage.productListing(); - - customerPortalExamplePage.navigateTo(); - customerPortalExamplePage.customerListing(); - testRealmLoginPage.form().login("bburke@redhat.com", "password"); - - customerPortalExamplePage.logOut(); - } - - @Test - public void grantServerBasedApp() { - ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "customer-portal"); - ClientRepresentation client = clientResource.toRepresentation(); - client.setConsentRequired(true); - clientResource.update(client); - - RealmRepresentation realm = testRealmResource().toRepresentation(); - realm.setEventsEnabled(true); - realm.setEnabledEventTypes(Arrays.asList("REVOKE_GRANT", "LOGIN")); - testRealmResource().update(realm); - - customerPortalExamplePage.navigateTo(); - customerPortalExamplePage.customerSession(); - - loginPage.form().login("bburke@redhat.com", "password"); - - assertTrue(oAuthGrantPage.isCurrent()); - - oAuthGrantPage.accept(); - - assertTrue(driver.getPageSource().contains("Your hostname:")); - assertTrue(driver.getPageSource().contains("You visited this page")); - - applicationsPage.navigateTo(); - applicationsPage.revokeGrantForApplication("customer-portal"); - - customerPortalExamplePage.navigateTo(); - customerPortalExamplePage.customerSession(); - - assertTrue(oAuthGrantPage.isCurrent()); - - loginEventsPage.navigateTo(); - if (!testContext.isAdminLoggedIn()) { - loginPage.form().login(adminUser); - testContext.setAdminLoggedIn(true); - } - loginEventsPage.table().filter(); - loginEventsPage.table().filterForm().addEventType("REVOKE_GRANT"); - loginEventsPage.table().update(); - - List resultList = loginEventsPage.table().rows(); - - assertEquals(1, resultList.size()); - - resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']")); - resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']")); - resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']")); - resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='customer-portal']")); - - loginEventsPage.table().reset(); - loginEventsPage.table().filterForm().addEventType("LOGIN"); - loginEventsPage.table().update(); - resultList = loginEventsPage.table().rows(); - - assertEquals(1, resultList.size()); - - resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']")); - resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']")); - resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']")); - resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']")); - resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']")); - } - - @Test - public void historyOfAccessResourceTest() throws IOException { - RealmRepresentation realm = testRealmResource().toRepresentation(); - realm.setEventsEnabled(true); - realm.setEnabledEventTypes(Arrays.asList("LOGIN", "LOGIN_ERROR", "LOGOUT", "CODE_TO_TOKEN")); - testRealmResource().update(realm); - - customerPortalExamplePage.navigateTo(); - customerPortalExamplePage.customerListing(); - - testRealmLoginPage.form().login("bburke@redhat.com", "password"); - - Assert.assertTrue(driver.getPageSource().contains("Username: bburke@redhat.com") - && driver.getPageSource().contains("Bill Burke") - && driver.getPageSource().contains("Stian Thorgersen") - ); - - if (isRelative()) { //KEYCLOAK-1546 - productPortalExamplePage.logOut(); - } else { - driver.navigate().to(testRealmPage.getOIDCLogoutUrl() + "?redirect_uri=" + productPortalExamplePage); - } - - loginEventsPage.navigateTo(); - - if (!testContext.isAdminLoggedIn()) { - loginPage.form().login(adminUser); - testContext.setAdminLoggedIn(true); - } - - loginEventsPage.table().filter(); - loginEventsPage.table().filterForm().addEventType("LOGOUT"); - loginEventsPage.table().update(); - - List resultList = loginEventsPage.table().rows(); - - assertEquals(1, resultList.size()); - - resultList.get(0).findElement(By.xpath(".//td[text()='LOGOUT']")); - resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='']")); - resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']")); - - loginEventsPage.table().reset(); - loginEventsPage.table().filterForm().addEventType("LOGIN"); - loginEventsPage.table().update(); - resultList = loginEventsPage.table().rows(); - - assertEquals(1, resultList.size()); - - resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']")); - resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']")); - resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']")); - resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']")); - - loginEventsPage.table().reset(); - loginEventsPage.table().filterForm().addEventType("CODE_TO_TOKEN"); - loginEventsPage.table().update(); - resultList = loginEventsPage.table().rows(); - - assertEquals(1, resultList.size()); - resultList.get(0).findElement(By.xpath(".//td[text()='CODE_TO_TOKEN']")); - resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']")); - resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']")); - resultList.get(0).findElement(By.xpath(".//td[text()='refresh_token_type']/../td[text()='Refresh']")); - - String serverLogPath = null; - - if (System.getProperty("app.server").equals("wildfly") || System.getProperty("app.server").equals("eap6") || System.getProperty("app.server").equals("eap")) { - serverLogPath = System.getProperty("app.server.home") + "/standalone/log/server.log"; - } - - String appServerUrl; - if (Boolean.parseBoolean(System.getProperty("app.server.ssl.required"))) { - appServerUrl = "https://localhost:" + System.getProperty("app.server.https.port", "8543") + "/"; - } else { - appServerUrl = "http://localhost:" + System.getProperty("app.server.http.port", "8280") + "/"; - } - - if (serverLogPath != null) { - log.info("Checking app server log at: " + serverLogPath); - File serverLog = new File(serverLogPath); - String serverLogContent = FileUtils.readFileToString(serverLog); - UserRepresentation bburke = ApiUtil.findUserByUsername(testRealmResource(), "bburke@redhat.com"); - - Pattern pattern = Pattern.compile("User '" + bburke.getId() + "' invoking '" + appServerUrl + "customer-portal\\/customers\\/view\\.jsp[^\\s]+' on client 'customer-portal'"); - Matcher matcher = pattern.matcher(serverLogContent); - - assertTrue(matcher.find()); - assertTrue(serverLogContent.contains("User '" + bburke.getId() + "' invoking '" + appServerUrl + "database/customers' on client 'database-service'")); - } else { - log.info("Checking app server log on app-server: \"" + System.getProperty("app.server") + "\" is not supported."); - } - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractSAMLExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractSAMLExampleAdapterTest.java deleted file mode 100644 index 3a0676e499..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractSAMLExampleAdapterTest.java +++ /dev/null @@ -1,126 +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.example; - -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.graphene.page.Page; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.Test; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest; -import org.keycloak.testsuite.adapter.page.SAMLPostEncExample; -import org.keycloak.testsuite.adapter.page.SAMLPostSigExample; -import org.keycloak.testsuite.adapter.page.SAMLRedirectSigExample; -import org.keycloak.testsuite.util.URLAssert; -import org.openqa.selenium.By; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -import static org.keycloak.testsuite.auth.page.AuthRealm.SAMLDEMO; -import static org.keycloak.testsuite.util.IOUtil.loadRealm; -import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; - -/** - * @author mhajas - */ -public abstract class AbstractSAMLExampleAdapterTest extends AbstractExampleAdapterTest { - - @Page - private SAMLPostSigExample samlPostSigExamplePage; - - @Page - private SAMLPostEncExample samlPostEncExamplePage; - - @Page - private SAMLRedirectSigExample samlRedirectSigExamplePage; - - @Override - public void addAdapterTestRealms(List testRealms) { - RealmRepresentation samlRealm = loadRealm(new File(EXAMPLES_HOME_DIR + "/saml/testsaml.json")); - testRealms.add(samlRealm); - } - - @Override - public void setDefaultPageUriParameters() { - super.setDefaultPageUriParameters(); - testRealmPage.setAuthRealm(SAMLDEMO); - testRealmSAMLRedirectLoginPage.setAuthRealm(SAMLDEMO); - testRealmSAMLPostLoginPage.setAuthRealm(SAMLDEMO); - } - - @Deployment(name = SAMLPostSigExample.DEPLOYMENT_NAME) - private static WebArchive samlPostSigExampleDeployment() throws IOException { - return exampleDeployment(SAMLPostSigExample.DEPLOYMENT_NAME); - } - - @Deployment(name = SAMLPostEncExample.DEPLOYMENT_NAME) - private static WebArchive samlPostEncExampleDeployment() throws IOException { - return exampleDeployment(SAMLPostEncExample.DEPLOYMENT_NAME); - } - - @Deployment(name = SAMLRedirectSigExample.DEPLOYMENT_NAME) - private static WebArchive samlRedirectSigExampleDeployment() throws IOException { - return exampleDeployment(SAMLRedirectSigExample.DEPLOYMENT_NAME); - } - - @Test - public void samlPostWithSignatureExampleTest() { - samlPostSigExamplePage.navigateTo(); - testRealmSAMLPostLoginPage.form().login(bburkeUser); - - waitUntilElement(By.xpath("//body")).text().contains("Welcome to the Sales Tool, " + bburkeUser.getUsername()); - - samlPostSigExamplePage.logout(); - waitUntilElement(By.xpath("//body")).text().contains("Logged out."); - - samlPostSigExamplePage.navigateTo(); - URLAssert.assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); - } - - @Test - public void samlPostWithEncryptionExampleTest() { - samlPostEncExamplePage.navigateTo(); - - testRealmSAMLPostLoginPage.form().login(bburkeUser); - - waitUntilElement(By.xpath("//body")).text().contains("Welcome to the Sales Tool, " + bburkeUser.getUsername()); - - samlPostEncExamplePage.logout(); - waitUntilElement(By.xpath("//body")).text().contains("Logged out."); - - samlPostEncExamplePage.navigateTo(); - URLAssert.assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); - } - - @Test - public void samlRedirectWithSignatureExampleTest() { - samlRedirectSigExamplePage.navigateTo(); - - testRealmSAMLRedirectLoginPage.form().login(bburkeUser); - - waitUntilElement(By.xpath("//body")).text().contains("Welcome to the Employee Tool,"); - - samlRedirectSigExamplePage.logout(); - URLAssert.assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage); - - samlRedirectSigExamplePage.navigateTo(); - URLAssert.assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage); - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java index 2f9fede0ac..7223b2abcf 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java @@ -88,13 +88,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd @Before public void beforePhotozExampleAdapterTest() throws FileNotFoundException { deleteAllCookiesForClientPage(); - - for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { - if ("Only Owner Policy".equals(policy.getName())) { - policy.getConfig().put("mavenArtifactVersion", System.getProperty("project.version")); - getAuthorizationResource().policies().policy(policy.getId()).update(policy); - } - } } @Override @@ -650,7 +643,13 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd } private void importResourceServerSettings() throws FileNotFoundException { - getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-restful-api-authz-service.json")), ResourceServerRepresentation.class)); + ResourceServerRepresentation authSettings = loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-restful-api-authz-service.json")), ResourceServerRepresentation.class); + + authSettings.getPolicies().stream() + .filter(x -> "Only Owner Policy".equals(x.getName())) + .forEach(x -> x.getConfig().put("mavenArtifactVersion", System.getProperty("project.version"))); + + getAuthorizationResource().importSettings(authSettings); } private AuthorizationResource getAuthorizationResource() throws FileNotFoundException { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/BruteForceCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/BruteForceCrossDCTest.java new file mode 100644 index 0000000000..ec572e9073 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/BruteForceCrossDCTest.java @@ -0,0 +1,231 @@ +/* + * 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.crossdc; + +import java.io.IOException; +import java.net.URISyntaxException; + +import javax.ws.rs.NotFoundException; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.connections.infinispan.InfinispanConnectionProvider; +import org.keycloak.models.Constants; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserLoginFailureModel; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.Retry; +import org.keycloak.testsuite.client.KeycloakTestingClient; +import org.keycloak.testsuite.util.ClientBuilder; +import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.RealmBuilder; +import org.keycloak.testsuite.util.UserBuilder; + +/** + * @author Marek Posolda + */ +public class BruteForceCrossDCTest extends AbstractAdminCrossDCTest { + + private static final String REALM_NAME = "brute-force-test"; + + @Before + public void beforeTest() { + try { + adminClient.realm(REALM_NAME).remove(); + } catch (NotFoundException ignore) { + } + + UserRepresentation user = UserBuilder.create() + .id("login-test-1") + .username("login-test-1") + .email("login-1@test.com") + .enabled(true) + .password("password") + .addRoles(Constants.OFFLINE_ACCESS_ROLE) + .build(); + + UserRepresentation user2 = UserBuilder.create() + .id("login-test-2") + .username("login-test-2") + .email("login-2@test.com") + .enabled(true) + .password("password") + .addRoles(Constants.OFFLINE_ACCESS_ROLE) + .build(); + + ClientRepresentation client = ClientBuilder.create() + .clientId("test-app") + .directAccessGrants() + .redirectUris("http://localhost:8180/auth/realms/master/app/*") + .addWebOrigin("http://localhost:8180") + .secret("password") + .build(); + + RealmRepresentation realmRep = RealmBuilder.create() + .name(REALM_NAME) + .user(user) + .user(user2) + .client(client) + .bruteForceProtected(true) + .build(); + + adminClient.realms().create(realmRep); + } + + + @Test + public void testBruteForceWithUserOperations() throws Exception { + // Enable 1st DC only + enableDcOnLoadBalancer(DC.FIRST); + enableDcOnLoadBalancer(DC.SECOND); + + // Clear all + adminClient.realms().realm(REALM_NAME).attackDetection().clearAllBruteForce(); + assertStatistics("After brute force cleared", 0, 0, 0); + + // Create 10 brute force statuses for user1. Assert available on both DC1 and DC2 + createBruteForceFailures(10, "login-test-1"); + assertStatistics("After brute force for user1 created", 10, 0, 1); + + // Create 10 brute force statuses for user2. Assert available on both DC1 and DC2createBruteForceFailures(10, "login-test-2");createBruteForceFailures(10, "login-test-2"); + createBruteForceFailures(10, "login-test-2"); + assertStatistics("After brute force for user2 created", 10, 10, 2); + + // Remove brute force for user1 + adminClient.realms().realm(REALM_NAME).attackDetection().clearBruteForceForUser("login-test-1"); + assertStatistics("After brute force for user1 cleared", 0, 10, 1); + + // Re-add 10 brute force statuses for user1 + createBruteForceFailures(10, "login-test-1"); + assertStatistics("After brute force for user1 re-created", 10, 10, 2); + + // Remove user1 + adminClient.realms().realm(REALM_NAME).users().get("login-test-1").remove(); + assertStatistics("After user1 removed", 0, 10, 1); + } + + + @Test + public void testBruteForceWithRealmOperations() throws Exception { + // Enable 1st DC only + enableDcOnLoadBalancer(DC.FIRST); + enableDcOnLoadBalancer(DC.SECOND); + + // Clear all + adminClient.realms().realm(REALM_NAME).attackDetection().clearAllBruteForce(); + assertStatistics("After brute force cleared", 0, 0, 0); + + // Create 10 brute force statuses for user1 and user2. + createBruteForceFailures(10, "login-test-1"); + createBruteForceFailures(10, "login-test-2"); + assertStatistics("After brute force for users created", 10, 10, 2); + + // Clear all + adminClient.realms().realm(REALM_NAME).attackDetection().clearAllBruteForce(); + assertStatistics("After brute force cleared for realm", 0, 0, 0); + + // Re-add 10 brute force statuses for users + createBruteForceFailures(10, "login-test-1"); + createBruteForceFailures(10, "login-test-2"); + assertStatistics("After brute force for users re-created", 10, 10, 2); + + // Remove realm + adminClient.realms().realm(REALM_NAME).remove(); + + Retry.execute(() -> { + int dc0CacheSize = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME).size(); + int dc1CacheSize = getTestingClientForStartedNodeInDc(1).testing().cache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME).size(); + Assert.assertEquals(0, dc0CacheSize); + Assert.assertEquals(0, dc1CacheSize); + }, 50, 50); + + } + + + @Test + public void testDuplicatedPutIfAbsentOperation() throws Exception { + // Enable 1st DC only + enableDcOnLoadBalancer(DC.FIRST); + enableDcOnLoadBalancer(DC.SECOND); + + // Clear all + adminClient.realms().realm(REALM_NAME).attackDetection().clearAllBruteForce(); + assertStatistics("After brute force cleared", 0, 0, 0); + + // create the entry manually in DC0 + addUserLoginFailure(getTestingClientForStartedNodeInDc(0)); + assertStatistics("After create entry1", 1, 0, 1); + + // try to create the entry manually in DC1 (not use real concurrency for now). It should still update the numFailures in existing entry rather then override it + addUserLoginFailure(getTestingClientForStartedNodeInDc(1)); + assertStatistics("After create entry2", 2, 0, 1); + + } + + + private void assertStatistics(String prefixMessage, int expectedUser1, int expectedUser2, int expectedCacheSize) { + Retry.execute(() -> { + int dc0user1 = (Integer) getAdminClientForStartedNodeInDc(0).realm(REALM_NAME).attackDetection().bruteForceUserStatus("login-test-1").get("numFailures"); + int dc1user1 = (Integer) getAdminClientForStartedNodeInDc(1).realm(REALM_NAME).attackDetection().bruteForceUserStatus("login-test-1").get("numFailures"); + int dc0user2 = (Integer) getAdminClientForStartedNodeInDc(0).realm(REALM_NAME).attackDetection().bruteForceUserStatus("login-test-2").get("numFailures"); + int dc1user2 = (Integer) getAdminClientForStartedNodeInDc(1).realm(REALM_NAME).attackDetection().bruteForceUserStatus("login-test-2").get("numFailures"); + + int dc0CacheSize = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME).size(); + int dc1CacheSize = getTestingClientForStartedNodeInDc(1).testing().cache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME).size(); + + log.infof("%s: dc0User1=%d, dc0user2=%d, dc1user1=%d, dc1user2=%d, dc0CacheSize=%d, dc1CacheSize=%d", prefixMessage, dc0user1, dc0user2, dc1user1, dc1user2, dc0CacheSize, dc1CacheSize); + + Assert.assertEquals(dc0user1, expectedUser1); + Assert.assertEquals(dc0user2, expectedUser2); + Assert.assertEquals(dc1user1, expectedUser1); + Assert.assertEquals(dc1user2, expectedUser2); + + Assert.assertEquals(expectedCacheSize, dc0CacheSize); + Assert.assertEquals(expectedCacheSize, dc1CacheSize); + }, 50, 50); + } + + + + + + private void createBruteForceFailures(int count, String username) throws Exception { + oauth.realm(REALM_NAME); + + for (int i=0 ; i { + RealmModel realm = session.realms().getRealmByName(REALM_NAME); + UserLoginFailureModel loginFailure = session.sessions().addUserLoginFailure(realm, "login-test-1"); + loginFailure.incrementFailures(); + }); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java index 19e45d1b62..a7f955924e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java @@ -18,6 +18,9 @@ package org.keycloak.testsuite.crossdc; +import java.util.ArrayList; +import java.util.List; + import javax.ws.rs.NotFoundException; import org.hamcrest.Matchers; @@ -101,7 +104,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest { @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { - createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics); + createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true); // log.infof("Sleeping!"); // Thread.sleep(10000000); @@ -118,7 +121,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest { // Return last used accessTokenResponse - private OAuthClient.AccessTokenResponse createInitialSessions(String cacheName, boolean offline, InfinispanStatistics cacheDc1Statistics, InfinispanStatistics cacheDc2Statistics) throws Exception { + private List createInitialSessions(String cacheName, boolean offline, InfinispanStatistics cacheDc1Statistics, InfinispanStatistics cacheDc2Statistics, boolean includeRemoteStats) throws Exception { // Enable second DC enableDcOnLoadBalancer(DC.SECOND); @@ -137,9 +140,9 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest { oauth.scope(OAuth2Constants.OFFLINE_ACCESS); } - OAuthClient.AccessTokenResponse lastAccessTokenResponse = null; + List responses = new ArrayList<>(); for (int i=0 ; i responses = createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, false); + + // Kill node2 now. Around 10 sessions (half of SESSIONS_COUNT) will be lost on Keycloak side. But not on infinispan side + stopBackendNode(DC.FIRST, 1); + + channelStatisticsCrossDc.reset(); + + // Increase offset a bit to ensure logout happens later then token issued time + setTimeOffset(10); + + // Logout user + ApiUtil.findUserByUsernameId(getAdminClient().realm(REALM_NAME), "login-test").logout(); + + // Assert it's not possible to refresh sessions. Works because user.notBefore + int i = 0; + for (OAuthClient.AccessTokenResponse response : responses) { + i++; + OAuthClient.AccessTokenResponse refreshTokenResponse = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password"); + Assert.assertNull("Failed in iteration " + i, refreshTokenResponse.getRefreshToken()); + Assert.assertNotNull("Failed in iteration " + i, refreshTokenResponse.getError()); + } + } + // AUTH SESSIONS diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/manual/SessionsPreloadCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/manual/SessionsPreloadCrossDCTest.java index a73d283039..80949cf801 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/manual/SessionsPreloadCrossDCTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/manual/SessionsPreloadCrossDCTest.java @@ -23,7 +23,9 @@ import java.util.List; import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.connections.infinispan.InfinispanConnectionProvider; +import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; import org.keycloak.testsuite.crossdc.AbstractAdminCrossDCTest; import org.keycloak.testsuite.crossdc.DC; @@ -187,6 +189,52 @@ public class SessionsPreloadCrossDCTest extends AbstractAdminCrossDCTest { } + @Test + public void loginFailuresPreloadTest() throws Exception { + // Enable brute force protector + RealmRepresentation realmRep = getAdminClientForStartedNodeInDc(0).realms().realm("test").toRepresentation(); + realmRep.setBruteForceProtected(true); + getAdminClientForStartedNodeInDc(0).realms().realm("test").update(realmRep); + + String userId = ApiUtil.findUserByUsername(getAdminClientForStartedNodeInDc(0).realms().realm("test"), "test-user@localhost").getId(); + + int loginFailuresBefore = (Integer) getAdminClientForStartedNodeInDc(0).realm("test").attackDetection().bruteForceUserStatus(userId).get("numFailures"); + log.infof("loginFailuresBefore: %d", loginFailuresBefore); + + // Create initial brute force records + for (int i=0 ; i createInitialSessions(boolean offline) throws Exception { if (offline) { oauth.scope(OAuth2Constants.OFFLINE_ACCESS); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java index 677430d23a..d1c70075c3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java @@ -154,6 +154,8 @@ public class ExportImportUtil { Assert.assertNull(realmRsc.users().get(wburke.getId()).roles().getAll().getRealmMappings()); + Assert.assertEquals((Object) 159, wburke.getNotBefore()); + UserRepresentation loginclient = findByUsername(realmRsc, "loginclient"); // user with creation timestamp as string in import Assert.assertEquals(new Long(123655), loginclient.getCreatedTimestamp()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java index 7fc3f743d0..a3a03cbe68 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java @@ -21,8 +21,11 @@ import org.junit.Rule; import org.junit.Test; import org.keycloak.events.Details; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.LoginPage; @@ -210,4 +213,23 @@ public class LogoutTest extends AbstractTestRealmKeycloakTest { events.expectLogout(sessionId2).removeDetail(Details.REDIRECT_URI).assertEvent(); } + @Test + public void logoutUserByAdmin() { + loginPage.open(); + loginPage.login("test-user@localhost", "password"); + assertTrue(appPage.isCurrent()); + String sessionId = events.expectLogin().assertEvent().getSessionId(); + + UserRepresentation user = ApiUtil.findUserByUsername(adminClient.realm("test"), "test-user@localhost"); + Assert.assertEquals((Object) 0, user.getNotBefore()); + + adminClient.realm("test").users().get(user.getId()).logout(); + + user = adminClient.realm("test").users().get(user.getId()).toRepresentation(); + Assert.assertTrue(user.getNotBefore() > 0); + + loginPage.open(); + loginPage.assertCurrent(); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java index 6763e7d20d..8b978a4058 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java @@ -45,6 +45,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.client.KeycloakTestingClient; +import org.keycloak.testsuite.pages.AccountUpdateProfilePage; import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.ErrorPage; @@ -67,6 +68,7 @@ import java.security.NoSuchAlgorithmException; import java.security.spec.KeySpec; import java.util.Map; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -78,6 +80,9 @@ import static org.junit.Assert.fail; */ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest { + @Page + private AccountUpdateProfilePage updateProfilePage; + @Deployment public static WebArchive deploy() { return RunOnServerDeployment.create(PasswordHashingTest.class, AbstractTestRealmKeycloakTest.class); @@ -147,6 +152,42 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest { assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 1); } + // KEYCLOAK-5282 + @Test + public void testPasswordNotRehasedUnchangedIterations() throws Exception { + setPasswordPolicy(""); + + String username = "testPasswordNotRehasedUnchangedIterations"; + createUser(username); + + CredentialModel credential = fetchCredentials(username); + String credentialId = credential.getId(); + byte[] salt = credential.getSalt(); + + setPasswordPolicy("hashIterations"); + + loginPage.open(); + loginPage.login(username, "password"); + + credential = fetchCredentials(username); + + assertEquals(credentialId, credential.getId()); + assertArrayEquals(salt, credential.getSalt()); + + setPasswordPolicy("hashIterations(" + Pbkdf2Sha256PasswordHashProviderFactory.DEFAULT_ITERATIONS + ")"); + + updateProfilePage.open(); + updateProfilePage.logout(); + + loginPage.open(); + loginPage.login(username, "password"); + + credential = fetchCredentials(username); + + assertEquals(credentialId, credential.getId()); + assertArrayEquals(salt, credential.getSalt()); + } + @Test public void testPbkdf2Sha1() throws Exception { setPasswordPolicy("hashAlgorithm(" + Pbkdf2PasswordHashProviderFactory.ID + ")"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java index 2d927198b6..ea3793a64d 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java @@ -81,6 +81,8 @@ public class LoginPageTest extends AbstractI18NTest { @Test public void languageDropdown() { + ProfileAssume.assumeCommunity(); + loginPage.open(); Assert.assertEquals("English", loginPage.getLanguageDropdownText()); @@ -143,6 +145,8 @@ public class LoginPageTest extends AbstractI18NTest { // KEYCLOAK-3887 @Test public void languageChangeRequiredActions() { + ProfileAssume.assumeCommunity(); + UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost"); UserRepresentation userRep = user.toRepresentation(); userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString())); @@ -168,6 +172,8 @@ public class LoginPageTest extends AbstractI18NTest { // KEYCLOAK-3887 @Test public void languageChangeConsentScreen() { + ProfileAssume.assumeCommunity(); + // Set client, which requires consent oauth.clientId("third-party"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json index fb1a7e0002..a0c5b3011e 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json @@ -120,6 +120,7 @@ "username": "wburke", "enabled": true, "createdTimestamp" : 123654, + "notBefore": 159, "attributes": { "email": "bburke@redhat.com" }, diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/wildfly-integration/wildfly-management-realm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/wildfly-integration/wildfly-management-realm.json new file mode 100644 index 0000000000..373ca9ab96 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/wildfly-integration/wildfly-management-realm.json @@ -0,0 +1,68 @@ +{ + "realm": "jboss-infra", + "enabled": true, + "sslRequired": "external", + "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", + "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "requiredCredentials": [ + "password" + ], + "users": [ + { + "username": "admin", + "enabled": true, + "email": "admin@admin.com", + "firstName": "Admin", + "lastName": "Istrator", + "credentials": [ + { + "type": "password", + "value": "admin" + } + ], + "realmRoles": [ + "Administrator" + ], + "clientRoles": { + "realm-management": [ + "realm-admin" + ], + "account": [ + "manage-account" + ] + } + } + ], + "roles": { + "realm": [ + { + "name": "Administrator", + "description": "Administrator privileges" + } + ] + }, + "clients": [ + { + "clientId": "wildfly-console", + "enabled": true, + "adminUrl": "http://localhost:10190", + "baseUrl": "http://localhost:10190", + "publicClient": true, + "redirectUris": [ + "http://localhost:10190/*" + ], + "webOrigins": ["http://localhost:10190"] + }, + { + "clientId": "wildfly-management", + "secret": "secret", + "enabled": true, + "baseUrl": "/photoz-restful-api", + "publicClient": false, + "redirectUris": [ + "/photoz-restful-api/*" + ], + "webOrigins" : ["*"] + } + ] +} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPBasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPBasicAuthExampleAdapterTest.java deleted file mode 100644 index 574ee16c4c..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPBasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-eap") -public class EAPBasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPDemoExampleAdapterTest.java deleted file mode 100644 index f461222e5e..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPDemoExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-eap") -public class EAPDemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPSAMLExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPSAMLExampleAdapterTest.java deleted file mode 100644 index b5431ff7b8..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPSAMLExampleAdapterTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * @author mhajas - */ -@AppServerContainer("app-server-eap") -public class EAPSAMLExampleAdapterTest extends AbstractSAMLExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.java index 8355037f1c..0ff9905fd8 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.java +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.java @@ -17,7 +17,6 @@ package org.keycloak.testsuite.adapter.example.authorization; import org.jboss.arquillian.container.test.api.RunAsClient; -import org.keycloak.testsuite.adapter.example.authorization.AbstractServletAuthzAdapterTest; import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; /** @@ -27,6 +26,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; @RunAsClient @AppServerContainer("app-server-eap") //@AdapterLibsLocationProperty("adapter.libs.wildfly") -public class EAPServletAuthzAdapterTest extends AbstractServletAuthzAdapterTest { +public class EAPServletAuthzAdapterTest extends AbstractServletAuthzFunctionalAdapterTest { } diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6BasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6BasicAuthExampleAdapterTest.java deleted file mode 100644 index 8299093dc2..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6BasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-eap6") -public class EAP6BasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6DemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6DemoExampleAdapterTest.java deleted file mode 100644 index d0c0c95e74..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6DemoExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-eap6") -public class EAP6DemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6SAMLExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6SAMLExampleAdapterTest.java deleted file mode 100644 index 208d43098e..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6SAMLExampleAdapterTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * @author mhajas - */ -@AppServerContainer("app-server-eap6") -public class EAP6SAMLExampleAdapterTest extends AbstractSAMLExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPBasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPBasicAuthExampleAdapterTest.java deleted file mode 100644 index 9d779502d2..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPBasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -/** - * - * @author tkyjovsk - */ -public class RelativeEAPBasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPCorsExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPCorsExampleAdapterTest.java deleted file mode 100644 index b96ee75e0f..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPCorsExampleAdapterTest.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -/** - * - * @author fkiss - */ -public class RelativeEAPCorsExampleAdapterTest extends AbstractCorsExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPDemoExampleAdapterTest.java deleted file mode 100644 index ef94eee5b3..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPDemoExampleAdapterTest.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -/** - * - * @author tkyjovsk - */ -public class RelativeEAPDemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPSAMLExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPSAMLExampleAdapterTest.java deleted file mode 100644 index 2afe2381e1..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPSAMLExampleAdapterTest.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -/** - * - * @author mhajas - */ -public class RelativeEAPSAMLExampleAdapterTest extends AbstractSAMLExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteBasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteBasicAuthExampleAdapterTest.java deleted file mode 100644 index 07b907e21a..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteBasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-remote") -public class RemoteBasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteDemoExampleAdapterTest.java deleted file mode 100644 index 90860d24e5..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteDemoExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-remote") -public class RemoteDemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteSAMLExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteSAMLExampleAdapterTest.java deleted file mode 100644 index 9dca78baf0..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteSAMLExampleAdapterTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * @author mhajas - */ -@AppServerContainer("app-server-remote") -public class RemoteSAMLExampleAdapterTest extends AbstractSAMLExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/pom.xml index 3a6e545cda..061e94e179 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/pom.xml @@ -42,7 +42,7 @@ org.wildfly.core wildfly-cli test - 2.2.0.Final + 3.0.0.Beta30 diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyBasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyBasicAuthExampleAdapterTest.java deleted file mode 100644 index 878f337cbe..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyBasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-wildfly") -public class WildflyBasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyDemoExampleAdapterTest.java deleted file mode 100644 index 588c28d82d..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyDemoExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-wildfly") -public class WildflyDemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflySAMLExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflySAMLExampleAdapterTest.java deleted file mode 100644 index e118da1f1b..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflySAMLExampleAdapterTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * @author mhajas - */ -@AppServerContainer("app-server-wildfly") -public class WildflySAMLExampleAdapterTest extends AbstractSAMLExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyManagementProtectionTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyManagementProtectionTest.java new file mode 100644 index 0000000000..88d4200ea8 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/authorization/WildflyManagementProtectionTest.java @@ -0,0 +1,109 @@ +/* + * 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.adapter.example.authorization; + +import static org.junit.Assert.assertTrue; +import static org.keycloak.testsuite.util.IOUtil.loadRealm; + +import java.util.List; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.adapter.AbstractAdapterTest; +import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; +import org.keycloak.testsuite.pages.AccountUpdateProfilePage; +import org.keycloak.testsuite.pages.AppServerWelcomePage; +import org.keycloak.testsuite.util.WaitUtils; +import org.wildfly.extras.creaper.core.ManagementClient; +import org.wildfly.extras.creaper.core.online.OnlineManagementClient; +import org.wildfly.extras.creaper.core.online.OnlineOptions; + +/** + * + * @author Pedro Igor + */ +@AppServerContainer("app-server-wildfly") +//@AdapterLibsLocationProperty("adapter.libs.wildfly") +public class WildflyManagementProtectionTest extends AbstractAdapterTest { + + @Page + protected AppServerWelcomePage appServerWelcomePage; + + @Page + protected AccountUpdateProfilePage accountUpdateProfilePage; + + @Override + public void addAdapterTestRealms(List testRealms) { + testRealms.add(loadRealm("/wildfly-integration/wildfly-management-realm.json")); + } + + @Before + public void beforeAuthTest() { + super.beforeAuthTest(); + + try { + OnlineManagementClient clientWorkerNodeClient = ManagementClient.online(OnlineOptions + .standalone() + .hostAndPort("localhost", 10190) + .build()); + + // Create a realm for both wildfly console and mgmt interface + clientWorkerNodeClient.execute("/subsystem=keycloak/realm=jboss-infra:add(auth-server-url=http://localhost:8180/auth,realm-public-key=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB)"); + + // Create a secure-deployment in order to protect mgmt interface + clientWorkerNodeClient.execute("/subsystem=keycloak/secure-deployment=wildfly-management:add(realm=jboss-infra,resource=wildfly-management,principal-attribute=preferred_username,bearer-only=true,ssl-required=EXTERNAL)"); + + // Protect HTTP mgmt interface with Keycloak adapter + clientWorkerNodeClient.execute("/core-service=management/management-interface=http-interface:undefine-attribute(name=security-realm)"); + clientWorkerNodeClient.execute("/subsystem=elytron/http-authentication-factory=keycloak-mgmt-http-authentication:add(security-domain=KeycloakDomain,http-server-mechanism-factory=wildfly-management,mechanism-configurations=[{mechanism-name=KEYCLOAK,mechanism-realm-configurations=[{realm-name=KeycloakOIDCRealm,realm-mapper=keycloak-oidc-realm-mapper}]}])"); + clientWorkerNodeClient.execute("/core-service=management/management-interface=http-interface:write-attribute(name=http-authentication-factory,value=keycloak-mgmt-http-authentication)"); + clientWorkerNodeClient.execute("/core-service=management/management-interface=http-interface:write-attribute(name=http-upgrade, value={enabled=true, sasl-authentication-factory=management-sasl-authentication})"); + + // Enable RBAC where roles are obtained from the identity + clientWorkerNodeClient.execute("/core-service=management/access=authorization:write-attribute(name=provider,value=rbac)"); + clientWorkerNodeClient.execute("/core-service=management/access=authorization:write-attribute(name=use-identity-roles,value=true)"); + + // Create a secure-server in order to publish the wildfly console configuration via mgmt interface + clientWorkerNodeClient.execute("/subsystem=keycloak/secure-server=wildfly-console:add(realm=jboss-infra,resource=wildfly-console,public-client=true)"); + + // reload + clientWorkerNodeClient.execute("reload"); + } catch (Exception cause) { + throw new RuntimeException("Failed to configure app server", cause); + } + } + + @Test + public void testLogin() throws InterruptedException { + appServerWelcomePage.navigateToConsole(); + appServerWelcomePage.login("admin", "admin"); + WaitUtils.pause(2000); + assertTrue(appServerWelcomePage.isCurrent()); + } + + @Test + public void testUserCanAccessAccountService() throws InterruptedException { + appServerWelcomePage.navigateToConsole(); + appServerWelcomePage.login("admin", "admin"); + WaitUtils.pause(2000); + appServerWelcomePage.navigateToAccessControl(); + appServerWelcomePage.navigateManageProfile(); + assertTrue(accountUpdateProfilePage.isCurrent()); + } +} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10BasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10BasicAuthExampleAdapterTest.java deleted file mode 100644 index f49dd19949..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10BasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-wildfly10") -public class Wildfly10BasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10DemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10DemoExampleAdapterTest.java deleted file mode 100644 index 6117bfd86e..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10DemoExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-wildfly10") -public class Wildfly10DemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10SAMLExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10SAMLExampleAdapterTest.java deleted file mode 100644 index d7a162c927..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10SAMLExampleAdapterTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * @author mhajas - */ -@AppServerContainer("app-server-wildfly10") -public class Wildfly10SAMLExampleAdapterTest extends AbstractSAMLExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly8BasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly8BasicAuthExampleAdapterTest.java deleted file mode 100644 index dc4e7cc76c..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly8BasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-wildfly8") -public class Wildfly8BasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly8DemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly8DemoExampleAdapterTest.java deleted file mode 100644 index 8bb20d8b67..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly8DemoExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-wildfly8") -public class Wildfly8DemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly9BasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly9BasicAuthExampleAdapterTest.java deleted file mode 100644 index 9d68c8a383..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly9BasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-wildfly9") -public class Wildfly9BasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly9DemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly9DemoExampleAdapterTest.java deleted file mode 100644 index 26ce953a62..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly9DemoExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-wildfly9") -public class Wildfly9DemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly9SAMLExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly9SAMLExampleAdapterTest.java deleted file mode 100644 index e5e5c27dab..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly9SAMLExampleAdapterTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * @author mhajas - */ -@AppServerContainer("app-server-wildfly9") -public class Wildfly9SAMLExampleAdapterTest extends AbstractSAMLExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml index 6cfc621f24..feaf52b7b7 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml @@ -68,6 +68,7 @@ 10399 + @@ -81,6 +82,7 @@ -Djava.security.krb5.conf=${project.build.directory}/dependency/kerberos/test-krb5.conf -Dkie.maven.settings.custom=${settings.path} -Drepo.url=${repo.url} + -Dmaven.repo.local=${maven.repo.local} ${containers.home}/app-server-${app.server} @@ -265,24 +267,6 @@ - - org.keycloak.example.demo - product-portal-example - ${project.version} - war - - - org.keycloak.example.demo - customer-portal-example - ${project.version} - war - - - org.keycloak.example.demo - database-service - ${project.version} - war - org.keycloak.testsuite integration-arquillian-test-apps-js-console @@ -295,48 +279,6 @@ ${project.version} war - - org.keycloak - examples-multitenant - ${project.version} - war - - - org.keycloak - examples-basicauth - ${project.version} - war - - - org.keycloak.example.demo - cors-angular-product-example - ${project.version} - war - - - org.keycloak.example.demo - cors-database-service - ${project.version} - war - - - org.keycloak - sales-post-sig - ${project.version} - war - - - org.keycloak - saml-post-encryption - ${project.version} - war - - - org.keycloak - saml-redirect-signatures - ${project.version} - war - org.keycloak.testsuite hello-world-authz-service diff --git a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat7/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat7BasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat7/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat7BasicAuthExampleAdapterTest.java deleted file mode 100644 index cd1e26b6b4..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat7/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat7BasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-tomcat7") -public class Tomcat7BasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat7/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat7DemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat7/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat7DemoExampleAdapterTest.java deleted file mode 100644 index cbfd42756e..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat7/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat7DemoExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-tomcat7") -public class Tomcat7DemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat8/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat8BasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat8/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat8BasicAuthExampleAdapterTest.java deleted file mode 100644 index 175ed9ef1a..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat8/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat8BasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-tomcat8") -public class Tomcat8BasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat8/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat8DemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat8/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat8DemoExampleAdapterTest.java deleted file mode 100644 index ef3d06d0a4..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat8/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat8DemoExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-tomcat8") -public class Tomcat8DemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat9/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat9BasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat9/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat9BasicAuthExampleAdapterTest.java deleted file mode 100644 index 221c006761..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat9/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat9BasicAuthExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-tomcat9") -public class Tomcat9BasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest { - -} diff --git a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat9/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat9DemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat9/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat9DemoExampleAdapterTest.java deleted file mode 100644 index 56ae537146..0000000000 --- a/testsuite/integration-arquillian/tests/other/adapters/tomcat/tomcat9/src/test/java/org/keycloak/testsuite/adapter/example/Tomcat9DemoExampleAdapterTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.adapter.example; - -import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; - -/** - * - * @author tkyjovsk - */ -@AppServerContainer("app-server-tomcat9") -public class Tomcat9DemoExampleAdapterTest extends AbstractDemoExampleAdapterTest { - -} \ No newline at end of file diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index e006820549..98215f7bfb 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -126,6 +126,14 @@ public class AdapterTestStrategy extends ExternalResource { protected void after() { super.after(); webRule.after(); + + // Revert notBefore + KeycloakSession session = keycloakRule.startSession(); + RealmModel realm = session.realms().getRealmByName("demo"); + UserModel user = session.users().getUserByUsername("bburke@redhat.com", realm); + session.users().setNotBeforeForUser(realm, user, 0); + session.getTransactionManager().commit(); + session.close(); } public void testSavedPostRequest() throws Exception { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java index 05d1afa116..a58b82f257 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java @@ -95,6 +95,29 @@ public class IdentityProviderHintTest { assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); } + + // KEYCLOAK-5260 + @Test + public void testSuccessfulRedirectToProviderAfterLoginPageShown() { + this.driver.navigate().to("http://localhost:8081/test-app"); + String loginPageUrl = driver.getCurrentUrl(); + assertTrue(loginPageUrl.startsWith("http://localhost:8081/auth/")); + + // Manually add "kc_idp_hint" to URL . Should redirect to provider + loginPageUrl = loginPageUrl + "&kc_idp_hint=kc-oidc-idp-hidden"; + this.driver.navigate().to(loginPageUrl); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); + + // Redirect from the app with the "kc_idp_hint". Should redirect to provider + this.driver.navigate().to("http://localhost:8081/test-app?kc_idp_hint=kc-oidc-idp-hidden"); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); + + // Now redirect should't happen + this.driver.navigate().to("http://localhost:8081/test-app"); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/")); + } + + @Test public void testInvalidIdentityProviderHint() { this.driver.navigate().to("http://localhost:8081/test-app?kc_idp_hint=invalid-idp-id"); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java index 8a1bf5dbeb..a1e4411359 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java @@ -137,7 +137,7 @@ public class FederatedStorageExportImportTest { Assert.assertEquals(1, session.userFederatedStorage().getStoredUsersCount(realm)); MultivaluedHashMap attributes = session.userFederatedStorage().getAttributes(realm, userId); - Assert.assertEquals(2, attributes.size()); + Assert.assertEquals(3, attributes.size()); Assert.assertEquals("value1", attributes.getFirst("single1")); Assert.assertTrue(attributes.getList("list1").contains("1")); Assert.assertTrue(attributes.getList("list1").contains("2")); @@ -174,6 +174,7 @@ public class FederatedStorageExportImportTest { session.userFederatedStorage().createCredential(realm, userId, credential); session.userFederatedStorage().grantRole(realm, userId, role); session.userFederatedStorage().joinGroup(realm, userId, group); + session.userFederatedStorage().setNotBeforeForUser(realm, userId, 50); keycloakRule.stopSession(session, true); @@ -203,13 +204,14 @@ public class FederatedStorageExportImportTest { Assert.assertEquals(1, session.userFederatedStorage().getStoredUsersCount(realm)); MultivaluedHashMap attributes = session.userFederatedStorage().getAttributes(realm, userId); - Assert.assertEquals(2, attributes.size()); + Assert.assertEquals(3, attributes.size()); Assert.assertEquals("value1", attributes.getFirst("single1")); Assert.assertTrue(attributes.getList("list1").contains("1")); Assert.assertTrue(attributes.getList("list1").contains("2")); Assert.assertTrue(session.userFederatedStorage().getRequiredActions(realm, userId).contains("UPDATE_PASSWORD")); Assert.assertTrue(session.userFederatedStorage().getRoleMappings(realm, userId).contains(role)); Assert.assertTrue(session.userFederatedStorage().getGroups(realm, userId).contains(group)); + Assert.assertEquals(50, session.userFederatedStorage().getNotBeforeOfUser(realm, userId)); List creds = session.userFederatedStorage().getStoredCredentials(realm, userId); Assert.assertEquals(1, creds.size()); Assert.assertTrue(getHashProvider(session, realm.getPasswordPolicy()).verify("password", creds.get(0))); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java index 191a39af89..4e0dca51c1 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java @@ -241,7 +241,7 @@ public class UserModelTest extends AbstractModelTest { @Test public void testUpdateUserSingleAttribute() { Map> expected = ImmutableMap.of( - "key1", Arrays.asList("value3"), + "key1", Arrays.asList("value3"), "key2", Arrays.asList("value2")); RealmModel realm = realmManager.createRealm("original"); @@ -398,6 +398,31 @@ public class UserModelTest extends AbstractModelTest { Assert.assertFalse(realm2User1.hasRole(role1)); } + @Test + public void testUserNotBefore() throws Exception { + RealmModel realm = realmManager.createRealm("original"); + + UserModel user1 = session.users().addUser(realm, "user1"); + session.users().setNotBeforeForUser(realm, user1, 10); + + commit(); + + realm = realmManager.getRealmByName("original"); + user1 = session.users().getUserByUsername("user1", realm); + int notBefore = session.users().getNotBeforeOfUser(realm, user1); + Assert.assertEquals(10, notBefore); + + // Try to update + session.users().setNotBeforeForUser(realm, user1, 20); + + commit(); + + realm = realmManager.getRealmByName("original"); + user1 = session.users().getUserByUsername("user1", realm); + notBefore = session.users().getNotBeforeOfUser(realm, user1); + Assert.assertEquals(20, notBefore); + } + public static void assertEquals(UserModel expected, UserModel actual) { Assert.assertEquals(expected.getUsername(), actual.getUsername()); Assert.assertEquals(expected.getCreatedTimestamp(), actual.getCreatedTimestamp()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java index 0312cbb753..846267f7d3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java @@ -462,26 +462,6 @@ public class UserSessionProviderTest { } - @Test - public void testFailCreateExistingSession() { - UserSessionModel userSession = session.sessions().createUserSession("123", realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null); - - // commit - resetSession(); - - - try { - session.sessions().createUserSession("123", realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null); - kc.stopSession(session, true); - Assert.fail("Not expected to successfully create duplicated userSession"); - } catch (IllegalStateException e) { - // Expected - session = kc.startSession(); - } - - } - - private void testAuthenticatedClientSession(AuthenticatedClientSessionModel clientSession, String expectedClientId, String expectedUserSessionId, String expectedAction, int expectedTimestamp) { Assert.assertEquals(expectedClientId, clientSession.getClient().getClientId()); Assert.assertEquals(expectedUserSessionId, clientSession.getUserSession().getId()); @@ -531,6 +511,15 @@ public class UserSessionProviderTest { resetSession(); + // Add the failure, which already exists + failure1 = session.sessions().addUserLoginFailure(realm, "user1"); + failure1.incrementFailures(); + + resetSession(); + + failure1 = session.sessions().getUserLoginFailure(realm, "user1"); + assertEquals(2, failure1.getNumFailures()); + failure1 = session.sessions().getUserLoginFailure(realm, "user1"); failure1.clearFailures(); @@ -556,13 +545,15 @@ public class UserSessionProviderTest { public void testOnUserRemoved() { createSessions(); - session.sessions().addUserLoginFailure(realm, "user1"); - session.sessions().addUserLoginFailure(realm, "user1@localhost"); - session.sessions().addUserLoginFailure(realm, "user2"); + UserModel user1 = session.users().getUserByUsername("user1", realm); + UserModel user2 = session.users().getUserByUsername("user2", realm); + + session.sessions().addUserLoginFailure(realm, user1.getId()); + session.sessions().addUserLoginFailure(realm, user2.getId()); resetSession(); - UserModel user1 = session.users().getUserByUsername("user1", realm); + user1 = session.users().getUserByUsername("user1", realm); new UserManager(session).removeUser(realm, user1); resetSession(); @@ -570,9 +561,8 @@ public class UserSessionProviderTest { assertTrue(session.sessions().getUserSessions(realm, user1).isEmpty()); assertFalse(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty()); - assertNull(session.sessions().getUserLoginFailure(realm, "user1")); - assertNull(session.sessions().getUserLoginFailure(realm, "user1@localhost")); - assertNotNull(session.sessions().getUserLoginFailure(realm, "user2")); + assertNull(session.sessions().getUserLoginFailure(realm, user1.getId())); + assertNotNull(session.sessions().getUserLoginFailure(realm, user2.getId())); } private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set roles, Set protocolMappers) { diff --git a/themes/src/main/resources-community/theme/base/account/messages/messages_nl.properties b/themes/src/main/resources-community/theme/base/account/messages/messages_nl.properties new file mode 100644 index 0000000000..f0882fbc01 --- /dev/null +++ b/themes/src/main/resources-community/theme/base/account/messages/messages_nl.properties @@ -0,0 +1,133 @@ +doSave=Opslaan +doCancel=Annuleer +doLogOutAllSessions=Alle sessies uitloggen +doRemove=Verwijder +doAdd=Voeg toe +doSignOut=Afmelden +editAccountHtmlTitle=Bewerk account +federatedIdentitiesHtmlTitle=Federated Identities +accountLogHtmlTitle=Account log +changePasswordHtmlTitle=Verander wachtwoord +sessionsHtmlTitle=Sessies +accountManagementTitle=Keycloak Accountbeheer +authenticatorTitle=Authenticator +applicationsHtmlTitle=Toepassingen +authenticatorCode=Eenmalige code +email=E-mailadres +firstName=Voornaam +givenName=Voornaam +fullName=Volledige naam +lastName=Achternaam +familyName=Achternaam +password=Wachtwoord +passwordConfirm=Bevestiging +passwordNew=Nieuw Wachtwoord +username=Gebruikersnaam +address=Adres +street=Straat +locality=Stad of plaats +region=Staat, provincie of regio +postal_code=Postcode +country=Land +emailVerified=E-mailadres geverifieerd +gssDelegationCredential=GSS gedelegeerde aanmeldgegevens +role_admin=Beheer +role_realm-admin=Realmbeheer +role_create-realm=Creëer realm +role_view-realm=Bekijk realm +role_view-users=Bekijk gebruikers +role_view-applications=Bekijk toepassingen +role_view-clients=Bekijk clients +role_view-events=Bekijk gebeurtenissen +role_view-identity-providers=Bekijk identity providers +role_manage-realm=Beheer realm +role_manage-users=Beheer gebruikers +role_manage-applications=Beheer toepassingen +role_manage-identity-providers=Beheer identity providers +role_manage-clients=Beheer clients +role_manage-events=Beheer gebeurtenissen +role_view-profile=Bekijk profiel +role_manage-account=Beheer account +role_manage-account-links=Beheer accountkoppelingen +role_read-token=Lees token +role_offline-access=Offline toegang +role_uma_authorization=Verkrijg UMA rechten +client_account=Account +client_security-admin-console=Console Veligheidsbeheer +client_admin-cli=Beheer CLI +client_realm-management=Realmbeheer +client_broker=Broker +requiredFields=Verplichte velden +allFieldsRequired=Alle velden verplicht +backToApplication=« Terug naar toepassing +backTo=Terug naar {0} +date=Datum +event=Gebeurtenis +ip=IP +client=Client +clients=Clients +details=Details +started=Gestart +lastAccess=Laatste toegang +expires=Vervalt +applications=Toepassingen +account=Account +federatedIdentity=Federated Identity +authenticator=Authenticator +sessions=Sessies +log=Log +application=Toepassing +availablePermissions=Beschikbare rechten +grantedPermissions=Gegunde rechten +grantedPersonalInfo=Gegunde Persoonsgegevens +additionalGrants=Verdere vergunningen +action=Actie +inResource=in +fullAccess=Volledige toegang +offlineToken=Offline Token +revoke=Vergunning intrekken +configureAuthenticators=Ingestelde authenticators +mobile=Mobiel nummer +totpStep1=Installeer FreeOTP of Google Authenticator op uw apparaat. Beide toepassingen zijn beschikbaar in Google Play en de Apple App Store. +totpStep2=Open de toepassing en scan de QR-code of voer de sleutel in. +totpStep3=Voer de door de toepassing gegeven eenmalige code in en klik op Opslaan om de configuratie af te ronden. +missingUsernameMessage=Gebruikersnaam ontbreekt. +missingFirstNameMessage=Voornaam onbreekt. +invalidEmailMessage=Ongeldig e-mailadres. +missingLastNameMessage=Achternaam ontbreekt. +missingEmailMessage=E-mailadres ontbreekt. +missingPasswordMessage=Wachtwoord ontbreekt. +notMatchPasswordMessage=Wachtwoorden komen niet overeen. +missingTotpMessage=Authenticatiecode ontbreekt. +invalidPasswordExistingMessage=Ongeldig bestaand wachtwoord. +invalidPasswordConfirmMessage=Wachtwoordbevestiging komt niet overeen. +invalidTotpMessage=Ongeldige authenticatiecode. +emailExistsMessage=E-mailadres bestaat reeds. +readOnlyUserMessage=U kunt uw account niet bijwerken aangezien het account alleen-lezen is. +readOnlyPasswordMessage=U kunt uw wachtwoord niet wijzigen omdat uw account alleen-lezen is. +successTotpMessage=Mobiele authenticator geconfigureerd. +successTotpRemovedMessage=Mobiele authenticator verwijderd. +successGrantRevokedMessage=Vergunning succesvol ingetrokken +accountUpdatedMessage=Uw account is gewijzigd. +accountPasswordUpdatedMessage=Uw wachtwoord is gewijzigd. +missingIdentityProviderMessage=Geen identity provider aangegeven. +invalidFederatedIdentityActionMessage=Ongeldige of ontbrekende actie op federated identity. +identityProviderNotFoundMessage=Gespecificeerde identity provider niet gevonden. +federatedIdentityLinkNotActiveMessage=Deze federated identity is niet langer geldig. +federatedIdentityRemovingLastProviderMessage=U kunt de laatste federated identity provider niet verwijderen aangezien u dan niet langer zou kunnen inloggen. +identityProviderRedirectErrorMessage=Kon niet herverwijzen naar identity provider. +identityProviderRemovedMessage=Identity provider met succes verwijderd. +identityProviderAlreadyLinkedMessage=Door {0} teruggegeven federated identity is al gekoppeld aan een andere gebruiker. +staleCodeAccountMessage=De pagina is verlopen. Probeer het nogmaals. +consentDenied=Toestemming geweigerd +accountDisabledMessage=Account is gedeactiveerd. Contacteer de beheerder. +accountTemporarilyDisabledMessage=Account is tijdelijk deactiveerd, neem contact op met de beheerder of probeer het later opnieuw. +invalidPasswordMinLengthMessage=Ongeldig wachtwoord: de minimale lengte is {0} karakters. +invalidPasswordMinLowerCaseCharsMessage=Ongeldig wachtwoord: het moet minstens {0} kleine letters bevatten. +invalidPasswordMinDigitsMessage=Ongeldig wachtwoord: het moet minstens {0} getallen bevatten. +invalidPasswordMinUpperCaseCharsMessage=Ongeldig wachtwoord: het moet minstens {0} hoofdletters bevatten. +invalidPasswordMinSpecialCharsMessage=Ongeldig wachtwoord: het moet minstens {0} speciale karakters bevatten. +invalidPasswordNotUsernameMessage=Ongeldig wachtwoord: het mag niet overeenkomen met de gebruikersnaam. +invalidPasswordRegexPatternMessage=Ongeldig wachtwoord: het voldoet niet aan het door de beheerder ingestelde patroon. +invalidPasswordHistoryMessage=Ongeldig wachtwoord: het mag niet overeen komen met een van de laatste {0} wachtwoorden. +invalidPasswordGenericMessage=Ongeldig wachtwoord: het nieuwe wachtwoord voldoet niet aan het wachtwoordbeleid. diff --git a/themes/src/main/resources-community/theme/base/account/messages/messages_no.properties b/themes/src/main/resources-community/theme/base/account/messages/messages_no.properties index 948ff6c8a9..2147735d54 100644 --- a/themes/src/main/resources-community/theme/base/account/messages/messages_no.properties +++ b/themes/src/main/resources-community/theme/base/account/messages/messages_no.properties @@ -159,6 +159,7 @@ locale_fr=Fran\u00e7ais locale_it=Italian locale_ja=\u65E5\u672C\u8A9E locale_no=Norsk +locale_nl=Nederlands locale_pt-BR=Portugu\u00EAs (Brasil) locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 locale_zh-CN=\u4e2d\u6587\u7b80\u4f53 diff --git a/themes/src/main/resources-community/theme/base/account/theme.properties b/themes/src/main/resources-community/theme/base/account/theme.properties index 6b2b6e8fac..f1aeaf5301 100644 --- a/themes/src/main/resources-community/theme/base/account/theme.properties +++ b/themes/src/main/resources-community/theme/base/account/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru,sv \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,lt,no,nl,pt-BR,ru,sv diff --git a/themes/src/main/resources-community/theme/base/admin/messages/admin-messages_nl.properties b/themes/src/main/resources-community/theme/base/admin/messages/admin-messages_nl.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/themes/src/main/resources-community/theme/base/admin/messages/messages_nl.properties b/themes/src/main/resources-community/theme/base/admin/messages/messages_nl.properties new file mode 100644 index 0000000000..4a04a5249d --- /dev/null +++ b/themes/src/main/resources-community/theme/base/admin/messages/messages_nl.properties @@ -0,0 +1,27 @@ +invalidPasswordMinLengthMessage=Ongeldig wachtwoord: de minimale lengte is {0} karakters. +invalidPasswordMinLowerCaseCharsMessage=Ongeldig wachtwoord: het moet minstens {0} kleine letters bevatten. +invalidPasswordMinDigitsMessage=Ongeldig wachtwoord: het moet minstens {0} getallen bevatten. +invalidPasswordMinUpperCaseCharsMessage=Ongeldig wachtwoord: het moet minstens {0} hoofdletters bevatten. +invalidPasswordMinSpecialCharsMessage=Ongeldig wachtwoord: het moet minstens {0} speciale karakters bevatten. +invalidPasswordNotUsernameMessage=Ongeldig wachtwoord: het mag niet overeenkomen met de gebruikersnaam. +invalidPasswordRegexPatternMessage=Ongeldig wachtwoord: het voldoet niet aan het door de beheerder ingestelde patroon. +invalidPasswordHistoryMessage=Ongeldig wachtwoord: het mag niet overeen komen met een van de laatste {0} wachtwoorden. +invalidPasswordGenericMessage=Ongeldig wachtwoord: het nieuwe wachtwoord voldoet niet aan het wachtwoordbeleid. + +ldapErrorInvalidCustomFilter=LDAP filter met aangepaste configuratie start niet met "(" of eindigt niet met ")". +ldapErrorConnectionTimeoutNotNumber=Verbindingstimeout moet een getal zijn +ldapErrorReadTimeoutNotNumber=Lees-timeout moet een getal zijn +ldapErrorMissingClientId=Client ID moet ingesteld zijn als Realm Roles Mapping niet gebruikt wordt. +ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Kan groepsovererving niet behouden bij UID-lidmaatschapstype. +ldapErrorCantWriteOnlyForReadOnlyLdap=Alleen-schrijven niet mogelijk als LDAP provider mode niet WRITABLE is +ldapErrorCantWriteOnlyAndReadOnly=Alleen-schrijven en alleen-lezen mogen niet tegelijk ingesteld zijn + +clientRedirectURIsFragmentError=Redirect URIs mogen geen URI fragment bevatten +clientRootURLFragmentError=Root URL mag geen URL fragment bevatten + +pairwiseMalformedClientRedirectURI=Client heeft een ongeldige redirect URI. +pairwiseClientRedirectURIsMissingHost=Client redirect URIs moeten een geldige host-component bevatten. +pairwiseClientRedirectURIsMultipleHosts=Zonder een geconfigureerde Sector Identifier URI mogen client redirect URIs niet meerdere host componenten hebben. +pairwiseMalformedSectorIdentifierURI=Onjuist notatie in Sector Identifier URI. +pairwiseFailedToGetRedirectURIs=Kon geen redirect URIs verkrijgen van de Sector Identifier URI. +pairwiseRedirectURIsMismatch=Client redirect URIs komen niet overeen met redict URIs ontvangen van de Sector Identifier URI. diff --git a/themes/src/main/resources-community/theme/base/admin/theme.properties b/themes/src/main/resources-community/theme/base/admin/theme.properties index 4bd8da4d73..36d3234157 100644 --- a/themes/src/main/resources-community/theme/base/admin/theme.properties +++ b/themes/src/main/resources-community/theme/base/admin/theme.properties @@ -1,2 +1,2 @@ import=common/keycloak -locales=ca,en,es,fr,it,ja,lt,no,pt-BR,ru,zh-CN \ No newline at end of file +locales=ca,en,es,fr,it,ja,lt,nl,no,pt-BR,ru,zh-CN diff --git a/themes/src/main/resources-community/theme/base/email/messages/messages_nl.properties b/themes/src/main/resources-community/theme/base/email/messages/messages_nl.properties new file mode 100644 index 0000000000..af84d6df9f --- /dev/null +++ b/themes/src/main/resources-community/theme/base/email/messages/messages_nl.properties @@ -0,0 +1,27 @@ +emailVerificationSubject=Bevestig e-mailadres +emailVerificationBody=Iemand heeft een {2} account aangemaakt met dit e-mailadres. Als u dit was, klikt u op de onderstaande koppeling om uw e-mailadres te bevestigen \n\n{0}\n\nDeze koppeling zal binnen {1} minuten vervallen.\n\nU kunt dit bericht negeren indien u dit account niet heeft aangemaakt. +emailVerificationBodyHtml=

    Iemand heeft een {2} account aangemaakt met dit e-mailadres. Als u dit was, klikt u op de onderstaande koppeling om uw e-mailadres te bevestigen

    Koppeling naar e-mailadres bevestiging

    Deze koppeling zal binnen {1} minuten vervallen.U kunt dit bericht negeren indien u dit account niet heeft aangemaakt.

    +emailTestSubject=[KEYCLOAK] - SMTP testbericht +emailTestBody=Dit is een testbericht +emailTestBodyHtml=

    Dit is een testbericht

    +identityProviderLinkSubject=Koppel {0} +identityProviderLinkBody=Iemand wil uw "{1}" account koppelen met "{0}" account van gebruiker {2}. Als u dit was, klik dan op de onderstaande link om de accounts te koppelen\n\n{3}\n\nDeze link zal over {4} minuten vervallen.\n\nAls u de accounts niet wilt koppelen, negeer dan dit bericht. Als u accounts koppelt, dan kunt u bij {1} inloggen via {0}. +identityProviderLinkBodyHtml=

    Iemand wil uw "{1}" account koppelen met "{0}" account van gebruiker {2}. Als u dit was, klik dan op de onderstaande link om de accounts te koppelen

    Link om accounts te koppelen

    Deze link zal over {4} minuten vervallen.

    Als u de accounts niet wilt koppelen, negeer dan dit bericht. Als u accounts koppelt, dan kunt u bij {1} inloggen via {0}.

    +passwordResetSubject=Wijzig wachtwoord +passwordResetBody=Iemand verzocht de aanmeldgegevens van uw {2} account te wijzigen. Als u dit was, klik dan op de onderstaande koppeling om ze te wijzigen.\n\n{0}\n\nDe link en de code zullen binnen {1} minuten vervallen.\n\nAls u uw aanmeldgegevens niet wilt wijzigen, negeer dan dit bericht en er zal niets gewijzigd worden. +passwordResetBodyHtml=

    Iemand verzocht de aanmeldgegevens van uw {2} account te wijzigen. Als u dit was, klik dan op de onderstaande koppeling om ze te wijzigen.

    Wijzig aanmeldgegevens

    De link en de code zullen binnen {1} minuten vervallen.

    Als u uw aanmeldgegevens niet wilt wijzigen, negeer dan dit bericht en er zal niets gewijzigd worden.

    +executeActionsSubject=Wijzig uw account +executeActionsBody=Uw beheerder heeft u verzocht uw {2} account te wijzigen. Klik op de onderstaande koppeling om dit proces te starten. \n\n{0}\n\nDeze link zal over {1} minuten vervallen. \n\nAls u niet over dit verzoek op de hoogte was, negeer dan dit bericht om uw account ongewijzigd te laten. +executeActionsBodyHtml=

    Uw beheerder heeft u verzocht uw {2} account te wijzigen. Klik op de onderstaande koppeling om dit proces te starten.

    Link naar account wijziging

    Deze link zal over {1} minuten vervallen.

    Als u niet over dit verzoek op de hoogte was, negeer dan dit bericht om uw account ongewijzigd te laten.

    +eventLoginErrorSubject=Inlogfout +eventLoginErrorBody=Er is een foutieve inlogpoging gedetecteerd op uw account om {0} vanuit {1}. Als u dit niet was, neem dan contact op met de beheerder. +eventLoginErrorBodyHtml=

    Er is een foutieve inlogpoging gedetecteerd op uw account om {0} vanuit {1}. Als u dit niet was, neem dan contact op met de beheerder.

    +eventRemoveTotpSubject=TOTP verwijderd +eventRemoveTotpBody=TOTP is verwijderd van uw account om {0} vanuit {1}. Als u dit niet was, neem dan contact op met uw beheerder. +eventRemoveTotpBodyHtml=

    TOTP is verwijderd van uw account om {0} vanuit {1}. Als u dit niet was, neem dan contact op met uw beheerder.

    +eventUpdatePasswordSubject=Wachtwoord gewijzigd +eventUpdatePasswordBody=Uw wachtwoord is gewijzigd om {0} door {1}. Als u dit niet was, neem dan contact op met uw beheerder. +eventUpdatePasswordBodyHtml=

    Uw wachtwoord is gewijzigd om {0} door {1}. Als u dit niet was, neem dan contact op met uw beheerder.

    +eventUpdateTotpSubject=TOTP gewijzigd +eventUpdateTotpBody=TOTP is gewijzigd voor uw account om {0} door {1}. Als u dit niet was, neem dan contact op met uw beheerder. +eventUpdateTotpBodyHtml=

    TOTP is gewijzigd voor uw account om {0} door {1}. Als u dit niet was, neem dan contact op met uw beheerder.

    diff --git a/themes/src/main/resources-community/theme/base/email/theme.properties b/themes/src/main/resources-community/theme/base/email/theme.properties index 6b2b6e8fac..b970c5e8f4 100644 --- a/themes/src/main/resources-community/theme/base/email/theme.properties +++ b/themes/src/main/resources-community/theme/base/email/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru,sv \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,lt,nl,no,pt-BR,ru,sv diff --git a/themes/src/main/resources-community/theme/base/login/messages/messages_nl.properties b/themes/src/main/resources-community/theme/base/login/messages/messages_nl.properties new file mode 100644 index 0000000000..13af95a91e --- /dev/null +++ b/themes/src/main/resources-community/theme/base/login/messages/messages_nl.properties @@ -0,0 +1,224 @@ +doLogIn=Inloggen +doRegister=Registeer +doCancel=Annuleer +doSubmit=Verzenden +doYes=Ja +doNo=Nee +doContinue=Doorgaan +doAccept=Accepteren +doDecline=Afwijzen +doForgotPassword=Wachtwoord vergeten? +doClickHere=Klik hier +doImpersonate=Identiteit overnemen +kerberosNotConfigured=Kerberos is niet geconfigureerd +kerberosNotConfiguredTitle=Kerberos is niet geconfigureerd +bypassKerberosDetail=U bent niet ingelogd via Kerberos of uw browser kan niet met Kerberos inloggen. Klik op 'doorgaan' om via een andere manier in te loggen +kerberosNotSetUp=Kerberos is onjuist geconfigureerd. U kunt niet inloggen. +registerWithTitle=Registeer met {0} +registerWithTitleHtml={0} +loginTitle=Inloggen bij {0} +loginTitleHtml={0} +impersonateTitle={0} Identiteit overnemen +impersonateTitleHtml={0} Identiteit overnemen +realmChoice=Realm +unknownUser=Onbekende gebruiker +loginTotpTitle=Mobile Authenticator Setup +loginProfileTitle=Update accountinformatie +loginTimeout=U bent te lang bezig geweest met inloggen. Het inlogproces begint overnieuw. +oauthGrantTitle=Verleen Toegang +oauthGrantTitleHtml={0} +errorTitle=Er is een fout opgetreden... +errorTitleHtml=Er is een fout opgetreden... +emailVerifyTitle=E-mailadres-verificatie +emailForgotTitle=Wachtwoord vergeten? +updatePasswordTitle=Wachtwoord updaten +codeSuccessTitle=Succescode +codeErrorTitle=Foutcode: {0} + +termsTitle=Voorwaarden +termsTitleHtml=Voorwaarden +termsText=

    Gedefinieerde voorwaarden

    +recaptchaFailed=Ongeldige Recaptcha +recaptchaNotConfigured=Recaptcha is verplicht, maar niet geconfigureerd +consentDenied=Toestemming geweigerd. + +noAccount=Nieuwe gebruiker? +username=Gebruikersnaam +usernameOrEmail=Gebruikersnaam of e-mailadres +firstName=Voornaam +givenName=Voornaam +lastName=Achternaam +familyName=Familienaam +email=E-mailadres +password=Wachtwoord +passwordConfirm=Bevestig wachtwoord +passwordNew=Nieuw wachtwoord +passwordNewConfirm=Bevestiging nieuwe wachtwoord +rememberMe=Ingelogd blijven +authenticatorCode=Authenticatiecode +address=Adres +postal_code=Postcode +country=Land +emailVerified=E-mailadres geverifieerd +gssDelegationCredential=GSS delegatie Credential +loginTotpStep1=Installeer FreeOTP of Google Authenticator op uw mobiele telefoon. Beide applicaties zijn beschikbaar in de Google Play en Apple App Store. +loginTotpStep2=Open de applicatie en scan de barcode of voer de sleutel in +loginTotpStep3=Voer de eenmalige code die door de applicatie is aangeleverd in en klik op 'Verzenden' om de setup te voltooien +loginTotpOneTime=Eenmalige code + +oauthGrantRequest=Wilt u deze toegangsrechten verlenen? +inResource=in + +emailVerifyInstruction1=Een e-mail met instructies om uw e-mailadres te verifiëren is zojuist verzonden. +emailVerifyInstruction2=Heeft u geen verificatiecode ontvangen in uw e-mail? +emailVerifyInstruction3=om opnieuw een e-mail te versturen. + +emailLinkIdpTitle=Link {0} +emailLinkIdp1=Er is een e-mail met instructies verzonden om {0} account {1} te koppelen met uw {2} account. +emailLinkIdp2=Heeft u geen verificatiecode in uw e-mail ontvangen? +emailLinkIdp3=om opnieuw een e-mail te versturen. + +backToLogin=« Terug naar Inloggen + +emailInstruction=Voer uw gebruikersnaam of e-mailadres in en wij sturen u een e-mailbericht met instructies voor het aanmaken van een nieuw wachtwoord. + +copyCodeInstruction=Kopieer deze code en plak deze in uw applicatie: + +personalInfo=Persoonlijke informatie: +role_realm-admin=Realm beheren +role_create-realm=Realm aanmaken +role_create-client=Client aanmaken +role_view-realm=Bekijk realm +role_view-users=Bekijk gebruikers +role_view-clients=Bekijk clients +role_view-events=Bekijk gebeurtenissen +role_view-identity-providers=Bekijk identity providers +role_manage-realm=Beheer realm +role_manage-users=Gebruikers beheren +role_manage-identity-providers=Beheer identity providers +role_manage-clients=Beheer clients +role_manage-events=Beheer gebeurtenissen +role_view-profile=Profiel bekijken +role_read-token=Token lezen +role_offline-access=Offline toegang +client_account=Account +client_security-admin-console=Security Admin Console +client_admin-cli=Admin CLI +client_broker=Broker +invalidUserMessage=Ongeldige gebruikersnaam of wachtwoord. +invalidEmailMessage=Ongeldig e-mailadres. +accountDisabledMessage=Account is uitgeschakeld, neem contact op met beheer. +accountTemporarilyDisabledMessage=Account is tijdelijk uitgeschakeld, neem contact op met beheer of probeer het later opnieuw. +expiredCodeMessage=Logintijd verlopen. Gelieve opnieuw in te loggen. + +missingFirstNameMessage=Voer uw voornaam in. +missingLastNameMessage=Voer uw achternaam in. +missingEmailMessage=Voer uw e-mailadres in. +missingUsernameMessage=Voer uw gebruikersnaam in. +missingPasswordMessage=Voer uw wachtwoord in. +missingTotpMessage=Voer uw authenticatiecode in. +notMatchPasswordMessage=Wachtwoorden komen niet overeen. +invalidPasswordExistingMessage=Ongeldig bestaand wachtwoord. +invalidPasswordConfirmMessage=Wachtwoord komt niet overeen met wachtwoordbevestiging. +invalidTotpMessage=Ongeldige authenticatiecode. + +usernameExistsMessage=Gebruikersnaam bestaat al. +emailExistsMessage=E-mailadres bestaat al. + +federatedIdentityExistsMessage=Gebruiker met {0} {1} bestaat al. Log in met het beheerdersaccount om het account te koppelen. + +confirmLinkIdpTitle=Account bestaat al +federatedIdentityConfirmLinkMessage=Gebruiker met {0} {1} bestaat al. Hoe wilt u doorgaan? +federatedIdentityConfirmReauthenticateMessage=Authenticeer als {0} om uw account te koppelen {1} +confirmLinkIdpReviewProfile=Nalopen profiel +confirmLinkIdpContinue=Voeg toe aan bestaande account + +configureTotpMessage=U moet de Mobile Authenticator configuren om uw account te activeren. +updateProfileMessage=U moet uw gebruikersprofiel bijwerken om uw account te activeren. +updatePasswordMessage=U moet uw wachtwoord wijzigen om uw account te activeren. +verifyEmailMessage=U moet uw e-mailadres verifiëren om uw account te activeren. +linkIdpMessage=U moet uw e-mailadres verifiëren om uw account te koppelen aan {0}. + +emailSentMessage=U ontvangt binnenkort een e-mail met verdere instructies. +emailSendErrorMessage=Het versturen van de e-mail is mislukt, probeer het later opnieuw. + +accountUpdatedMessage=Uw account is gewijzigd. +accountPasswordUpdatedMessage=Uw wachtwoord is gewijzigd. + +noAccessMessage=Geen toegang + +invalidPasswordMinLengthMessage=Ongeldig wachtwoord, de minimumlengte is {0} karakters. +invalidPasswordMinDigitsMessage=Ongeldig wachtwoord, deze moet minstens {0} cijfers bevatten. +invalidPasswordMinLowerCaseCharsMessage=Ongeldig wachtwoord, deze moet minstens {0} kleine letters bevatten. +invalidPasswordMinUpperCaseCharsMessage=Ongeldig wachtwoord, deze moet minstens {0} hoofdletters bevatten. +invalidPasswordMinSpecialCharsMessage=Ongeldig wachtwoord, deze moet minstens {0} speciale tekens bevatten. +invalidPasswordNotUsernameMessage=Ongeldig wachtwoord, deze mag niet overeen komen met de gebruikersnaam. +invalidPasswordRegexPatternMessage=Ongeldig wachtwoord, deze komt niet overeen met opgegeven reguliere expressie(s). +invalidPasswordHistoryMessage=Ongeldig wachtwoord, deze mag niet overeen komen met een van de laatste {0} wachtwoorden. + +failedToProcessResponseMessage=Het verwerken van de respons is mislukt +httpsRequiredMessage=HTTPS vereist +realmNotEnabledMessage=Realm niet geactiveerd +invalidRequestMessage=Ongeldige request +failedLogout=Afmelden is mislukt +unknownLoginRequesterMessage=De login requester is onbekend +loginRequesterNotEnabledMessage=De login requester is niet geactiveerd +bearerOnlyMessage=Bearer-only applicaties mogen geen browserlogin initiëren +standardFlowDisabledMessage=Client mag geen browserlogin starten met het opgegeven response_type. Standard flow is uitgeschakeld voor de client. +implicitFlowDisabledMessage=Client mag geen browserlogin starten met opgegeven response_type. Implicit flow is uitgeschakeld voor de klant. +invalidRedirectUriMessage=Ongeldige redirect-URI +unsupportedNameIdFormatMessage=Niet-ondersteunde NameIDFormat +invalidRequesterMessage=Ongeldige requester +registrationNotAllowedMessage=Registratie is niet toegestaan +resetCredentialNotAllowedMessage=Het opnieuw instellen van de aanmeldgegevens is niet toegestaan +permissionNotApprovedMessage=Recht verworpen. +noRelayStateInResponseMessage=Geen relay state in antwoord van de identity provider. +insufficientPermissionMessage=Onvoldoende rechten om identiteiten te koppelen. +couldNotProceedWithAuthenticationRequestMessage=Het authenticatieverzoek naar de identity provider wordt afgebroken. +couldNotObtainTokenMessage=Kon geen token bemachtigen van de identity provider. +unexpectedErrorRetrievingTokenMessage=Onverwachte fout bij het ophalen van de token van de identity provider. +unexpectedErrorHandlingResponseMessage=Onverwachte fout bij het verwerken van de respons van de identity provider. +identityProviderAuthenticationFailedMessage=Verificatie mislukt. Er kon niet worden geauthenticeerd met de identity provider. +identityProviderDifferentUserMessage=U bent geauthenticeerd als {0}, maar u werd verwacht als {1} geauthenticeerd te zijn +couldNotSendAuthenticationRequestMessage=Kan het authenticatieverzoek niet verzenden naar de identity provider. +unexpectedErrorHandlingRequestMessage=Onverwachte fout bij het verwerken van het authenticatieverzoek naar de identity provider. +invalidAccessCodeMessage=Ongeldige toegangscode. +sessionNotActiveMessage=Sessie inactief. +invalidCodeMessage=Er is een fout opgetreden, probeer nogmaals in te loggen. +identityProviderUnexpectedErrorMessage=Onverwachte fout tijdens de authenticatie met de identity provider +identityProviderNotFoundMessage=Geen identity provider gevonden met deze naam. +identityProviderLinkSuccess=Uw account is met succes gekoppeld aan {0} account {1}. +staleCodeMessage=Deze pagina is verlopen. Keer terug naar uw applicatie om opnieuw in te loggen. +realmSupportsNoCredentialsMessage=Realm ondersteunt geen enkel soort aanmeldgegeven. +identityProviderNotUniqueMessage=Realm ondersteunt meerdere identity providers. Er kon niet bepaald worden welke identity provider er gebruikt zou moeten worden tijdens de authenticatie. +emailVerifiedMessage=Uw e-mailadres is geverifieerd. +staleEmailVerificationLink=De link die u gebruikt is verlopen, wellicht omdat u uw e-mailadres al eerder geverifieerd heeft. + +backToApplication=« Terug naar de applicatie +missingParameterMessage=Missende parameters: {0} +clientNotFoundMessage=Client niet gevonden. +clientDisabledMessage=Client is inactief. +invalidParameterMessage=Ongeldige parameter: {0} +alreadyLoggedIn=U bent al ingelogd. + +p3pPolicy=CP="This is not a P3P policy!" +fullName=Volledige naam +street=Straat +locality=Plaats +region=Staat, provincie of regio +emailLinkIdp4=Als u de e-mail al geverifieerd heeft in een andere browser +emailLinkIdp5=om door te gaan. +pageExpiredTitle=Pagina is verlopen +pageExpiredMsg1=Om het inlogproces te herstarten +pageExpiredMsg2=Om het inlogproces te vervolgen +role_admin=Beheerder +role_view-applications=Bekijk applicaties +role_manage-applications=Beheer applicaties +role_manage-account=Beheer account +role_manage-account-links=Beheer account-links +client_realm-management=Realm-beheer +expiredActionMessage=Actie verlopen. Vervolg nu met inloggen. +invalidPasswordGenericMessage=Ongeldig wachtwoord: het nieuwe wachtwoord voldoet niet aan het wachtwoordbeleid. +identityProviderAlreadyLinkedMessage=De door {0} teruggegeven gefedereerde identiteit is al aan een andere gebruiker gekoppeld. +differentUserAuthenticated=U bent in deze sessie al als de gebruiker "{0}" aangemeld. Log eerst uit. +brokerLinkingSessionExpired=Broker account linking aangevraagd, maar de huidige sessie in verlopen. diff --git a/themes/src/main/resources-community/theme/base/login/messages/messages_no.properties b/themes/src/main/resources-community/theme/base/login/messages/messages_no.properties index 1f1a9d903e..47922cd277 100644 --- a/themes/src/main/resources-community/theme/base/login/messages/messages_no.properties +++ b/themes/src/main/resources-community/theme/base/login/messages/messages_no.properties @@ -214,6 +214,7 @@ locale_es=Espa\u00F1ol locale_fr=Fran\u00e7ais locale_it=Italian locale_ja=\u65E5\u672C\u8A9E +locale_nl=Nederlands locale_no=Norsk locale_pt_BR=Portugu\u00EAs (Brasil) locale_pt-BR=Portugu\u00EAs (Brasil) diff --git a/themes/src/main/resources-community/theme/base/login/theme.properties b/themes/src/main/resources-community/theme/base/login/theme.properties index 6b2b6e8fac..f1aeaf5301 100644 --- a/themes/src/main/resources-community/theme/base/login/theme.properties +++ b/themes/src/main/resources-community/theme/base/login/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru,sv \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,lt,no,nl,pt-BR,ru,sv diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties index 47dbda14ba..906c525ef2 100755 --- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties @@ -160,9 +160,10 @@ locale_es=Espa\u00F1ol locale_fr=Fran\u00e7ais locale_it=Italian locale_ja=\u65E5\u672C\u8A9E +locale_nl=Nederlands locale_no=Norsk locale_lt=Lietuvi\u0173 locale_pt-BR=Portugu\u00EAs (Brasil) locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 +locale_sv=Svenska locale_zh-CN=\u4e2d\u6587\u7b80\u4f53 -locale_sv=Svenska \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/account/messages/messages_zh_CN.properties b/themes/src/main/resources/theme/base/account/messages/messages_zh_CN.properties index 4691002972..f263668434 100644 --- a/themes/src/main/resources/theme/base/account/messages/messages_zh_CN.properties +++ b/themes/src/main/resources/theme/base/account/messages/messages_zh_CN.properties @@ -157,6 +157,7 @@ locale_es=Español locale_fr=Français locale_it=Italian locale_ja=日本語 +locale_nl=Nederlands locale_no=Norsk locale_lt=Lietuvių locale_pt-BR=Português (Brasil) diff --git a/themes/src/main/resources/theme/base/account/theme.properties b/themes/src/main/resources/theme/base/account/theme.properties index b9c3990957..d7c6edfc4d 100644 --- a/themes/src/main/resources/theme/base/account/theme.properties +++ b/themes/src/main/resources/theme/base/account/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru,zh-CN \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,lt,nl,no,pt-BR,ru,zh-CN diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js index 14c9392af4..037a0c3bf2 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js @@ -971,12 +971,12 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro $scope.applyToResourceTypeFlag = true; } - $scope.selectedPolicies = []; ResourceServerPermission.associatedPolicies({ realm : $route.current.params.realm, client : client.id, id : policy.id }, function(policies) { + $scope.selectedPolicies = []; for (i = 0; i < policies.length; i++) { policies[i].text = policies[i].name; $scope.selectedPolicies.push(policies[i]); diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 33cb93be30..76c9a9b701 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -730,12 +730,17 @@ module.controller('ClientImportCtrl', function($scope, $location, $upload, realm module.controller('ClientListCtrl', function($scope, realm, Client, serverInfo, $route, Dialog, Notifications, filterFilter) { $scope.realm = realm; - $scope.clients = Client.query({realm: realm.realm, viewableOnly: true}); + $scope.clients = []; $scope.currentPage = 1; $scope.currentPageInput = 1; + $scope.numberOfPages = 1; $scope.pageSize = 20; - $scope.numberOfPages = Math.ceil($scope.clients.length/$scope.pageSize); - + + Client.query({realm: realm.realm, viewableOnly: true}).$promise.then(function(clients) { + $scope.numberOfPages = Math.ceil(clients.length/$scope.pageSize); + $scope.clients = clients; + }); + $scope.$watch('search', function (newVal, oldVal) { $scope.filtered = filterFilter($scope.clients, newVal); $scope.totalItems = $scope.filtered.length; @@ -743,7 +748,7 @@ module.controller('ClientListCtrl', function($scope, realm, Client, serverInfo, $scope.currentPage = 1; $scope.currentPageInput = 1; }, true); - + $scope.removeClient = function(client) { Dialog.confirmDelete(client.clientId, 'client', function() { Client.remove({ @@ -1798,7 +1803,7 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo ClientProtocolMapper.update({ realm : realm.realm, client: client.id, - id : mapper.id + id : $scope.model.mapper.id }, $scope.model.mapper, function() { $scope.model.changed = false; mapper = angular.copy($scope.mapper); diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 77dc9cdb4e..2644379891 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -524,13 +524,11 @@ module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $htt } var policyString = ""; for (var i = 0; i < policies.length; i++) { - policyString += policies[i].id; - if (policies[i].value && policies[i].value != policies[i].defaultValue) { - policyString += '(' + policies[i].value + ')'; + policyString += policies[i].id + '(' + policies[i].value + ')'; + if (i != policies.length - 1) { + policyString += ' and '; } - policyString += " and "; } - policyString = policyString.substring(0, policyString.length - 5); return policyString; } @@ -560,7 +558,7 @@ module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $htt $scope.save = function() { $scope.realm.passwordPolicy = toString($scope.policy); - console.debug($scope.realm.passwordPolicy); + console.log($scope.realm.passwordPolicy); Realm.update($scope.realm, function () { $route.reload(); @@ -2515,6 +2513,7 @@ module.controller('ClientRegPolicyDetailCtrl', function($scope, realm, clientReg if ($scope.providerType.properties) { ComponentUtils.addLastEmptyValueToMultivaluedLists($scope.providerType.properties, $scope.instance.config); + ComponentUtils.addMvOptionsToMultivaluedLists($scope.providerType.properties); } var oldCopy = angular.copy($scope.instance); @@ -2525,7 +2524,7 @@ module.controller('ClientRegPolicyDetailCtrl', function($scope, realm, clientReg $scope.changed = true; } }, true); - + $scope.reset = function() { $route.reload(); }; diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js index fca7b334f1..b084a4fc08 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -232,6 +232,25 @@ module.factory('ComponentUtils', function() { } } } + + // Allows you to use ui-select2 with tag. + // In HTML you will then use property.mvOptions like this: + // - +
    diff --git a/themes/src/main/resources/theme/base/admin/theme.properties b/themes/src/main/resources/theme/base/admin/theme.properties index 4bd8da4d73..36d3234157 100644 --- a/themes/src/main/resources/theme/base/admin/theme.properties +++ b/themes/src/main/resources/theme/base/admin/theme.properties @@ -1,2 +1,2 @@ import=common/keycloak -locales=ca,en,es,fr,it,ja,lt,no,pt-BR,ru,zh-CN \ No newline at end of file +locales=ca,en,es,fr,it,ja,lt,nl,no,pt-BR,ru,zh-CN diff --git a/themes/src/main/resources/theme/base/email/theme.properties b/themes/src/main/resources/theme/base/email/theme.properties index b9c3990957..d7c6edfc4d 100644 --- a/themes/src/main/resources/theme/base/email/theme.properties +++ b/themes/src/main/resources/theme/base/email/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru,zh-CN \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,lt,nl,no,pt-BR,ru,zh-CN diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties index dbd0a3c340..9abb4feab4 100755 --- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -230,6 +230,7 @@ locale_es=Espa\u00F1ol locale_fr=Fran\u00e7ais locale_it=Italian locale_ja=\u65E5\u672C\u8A9E +locale_nl=Nederlands locale_no=Norsk locale_pt_BR=Portugu\u00EAs (Brasil) locale_pt-BR=Portugu\u00EAs (Brasil) diff --git a/themes/src/main/resources/theme/base/login/messages/messages_zh_CN.properties b/themes/src/main/resources/theme/base/login/messages/messages_zh_CN.properties index 7a1a0728ba..39340bbef2 100644 --- a/themes/src/main/resources/theme/base/login/messages/messages_zh_CN.properties +++ b/themes/src/main/resources/theme/base/login/messages/messages_zh_CN.properties @@ -215,6 +215,7 @@ locale_es=Español locale_fr=Français locale_it=Italian locale_ja=日本語 +locale_nl=Nederlands locale_no=Norsk locale_pt_BR=Português (Brasil) locale_pt-BR=Português (Brasil) diff --git a/themes/src/main/resources/theme/base/login/theme.properties b/themes/src/main/resources/theme/base/login/theme.properties index b9c3990957..d7c6edfc4d 100644 --- a/themes/src/main/resources/theme/base/login/theme.properties +++ b/themes/src/main/resources/theme/base/login/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru,zh-CN \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,lt,nl,no,pt-BR,ru,zh-CN