keycloak-aplcache
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java 3(+3 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/QueryParamterTokenRequestAuthenticator.java 5(+5 -0)
adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java 4(+4 -0)
adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentTest.java 49(+49 -0)
adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakAutoConfiguration.java 2(+2 -0)
adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java 13(+13 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationFailureHandler.java 41(+41 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java 59(+28 -31)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java 29(+21 -8)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java 10(+9 -1)
adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties 2(+2 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java 7(+7 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties 2(+2 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java 49(+44 -5)
boms/adapter/pom.xml 5(+5 -0)
core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationResponse.java 12(+5 -7)
federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java 4(+4 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java 7(+6 -1)
integration/client-registration/src/main/java/org/keycloak/client/registration/ClientRegistration.java 21(+12 -9)
integration/client-registration/src/main/java/org/keycloak/client/registration/HttpUtil.java 17(+11 -6)
misc/keycloak-test-helper/pom.xml 49(+28 -21)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InResource.java 24(+24 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java 24(+24 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyListQuery.java 7(+6 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyQuery.java 30(+30 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyResourceListQuery.java 42(+42 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyScopeListQuery.java 42(+42 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceListQuery.java 7(+6 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceQuery.java 30(+30 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java 26(+26 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java 10(+6 -4)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java 6(+4 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceRemovedEvent.java 12(+10 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceUpdatedEvent.java 10(+8 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java 12(+8 -4)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java 9(+7 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java 40(+34 -6)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java 227(+162 -65)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InResourcePredicate.java 50(+50 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InScopePredicate.java 35(+35 -0)
model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java 5(+0 -5)
pom.xml 4(+2 -2)
saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java 16(+16 -0)
saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/KEYCLOAK-4790-Empty-attribute-value.xml 35(+35 -0)
saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/KEYCLOAK-4790-Empty-attribute-value-last.xml 35(+35 -0)
server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java 10(+5 -5)
server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java 90(+90 -0)
server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java 2(+1 -1)
server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java 9(+7 -2)
server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java 2(+1 -1)
services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java 3(+2 -1)
services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java 5(+4 -1)
services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java 3(+2 -1)
services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java 58(+11 -47)
services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java 6(+2 -4)
services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java 29(+9 -20)
services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java 15(+12 -3)
services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java 5(+4 -1)
services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java 3(+3 -0)
services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java 2(+2 -0)
services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java 6(+6 -0)
services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java 11(+7 -4)
services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java 5(+5 -0)
services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java 4(+2 -2)
services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java 9(+9 -0)
services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java 44(+40 -4)
services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java 10(+7 -3)
testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java 10(+10 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java 2(+1 -1)
testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java 12(+8 -4)
testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SimpleWebXmlParser.java 14(+14 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ActionURIUtils.java 89(+89 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java 1(+1 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncSignAssertionsOnlyServlet.java 40(+40 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ErrorPage.java 8(+8 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/GoogleLoginPage.java 38(+16 -22)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/ClientAttributeUpdater.java 55(+55 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/RealmAttributeUpdater.java 55(+55 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java 24(+24 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java 26(+9 -17)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java 2(+2 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java 32(+28 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoServletsAdapterTest.java 6(+0 -6)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java 8(+4 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java 194(+181 -13)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java 61(+55 -6)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java 272(+272 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java 177(+91 -86)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java 18(+18 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java 39(+36 -3)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java 22(+22 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java 6(+2 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java 8(+7 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java 25(+10 -15)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java 45(+45 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java 12(+9 -3)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java 6(+2 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java 23(+23 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java 45(+31 -14)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java 3(+2 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/ScopeParameterTest.java 142(+142 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/BasicSamlTest.java 45(+44 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SamlClient.java 27(+18 -9)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java 7(+7 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keycloak-saml.xml 65(+65 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keystore.jks 0(+0 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json 19(+19 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/export/partialexport-testrealm.json 989(+989 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json 9(+9 -0)
themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html 2(+1 -1)
themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html 2(+1 -1)
themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html 9(+6 -3)
themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html 2(+1 -1)
themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html 2(+1 -1)
themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html 2(+1 -1)
themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html 9(+6 -3)
themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-detail.html 23(+0 -23)
themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-export-settings.html 35(+35 -0)
themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html 7(+5 -2)
themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html 9(+6 -3)
themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3.html 155(+154 -1)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index 31f842c..45c4557 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -35,6 +35,8 @@ import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
+ * @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
* @version $Revision: 1 $
*/
public class KeycloakDeployment {
@@ -88,6 +90,7 @@ public class KeycloakDeployment {
// https://tools.ietf.org/html/rfc7636
protected boolean pkce = false;
+ protected boolean ignoreOAuthQueryParameter;
public KeycloakDeployment() {
}
@@ -436,4 +439,11 @@ public class KeycloakDeployment {
this.pkce = pkce;
}
+ public void setIgnoreOAuthQueryParameter(boolean ignoreOAuthQueryParameter) {
+ this.ignoreOAuthQueryParameter = ignoreOAuthQueryParameter;
+ }
+
+ public boolean isOAuthQueryParameterEnabled() {
+ return !this.ignoreOAuthQueryParameter;
+ }
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index a651753..eca6849 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -37,6 +37,8 @@ import java.security.PublicKey;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
+ * @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
* @version $Revision: 1 $
*/
public class KeycloakDeploymentBuilder {
@@ -113,6 +115,7 @@ public class KeycloakDeploymentBuilder {
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
+ deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/QueryParamterTokenRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/QueryParamterTokenRequestAuthenticator.java
index 5ee6662..d2cabb3 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/QueryParamterTokenRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/QueryParamterTokenRequestAuthenticator.java
@@ -22,6 +22,8 @@ import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
+ * @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
+ * @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
* @version $Revision: 1 $
*/
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
@@ -33,6 +35,9 @@ public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAu
}
public AuthOutcome authenticate(HttpFacade exchange) {
+ if(!deployment.isOAuthQueryParameterEnabled()) {
+ return AuthOutcome.NOT_ATTEMPTED;
+ }
tokenString = null;
tokenString = getAccessTokenFromQueryParamter(exchange);
if (tokenString == null || tokenString.trim().isEmpty()) {
diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
index a4f04ec..cd191e2 100644
--- a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
+++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
@@ -29,10 +29,13 @@ import org.keycloak.common.util.PemUtils;
import org.keycloak.enums.TokenStore;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ * @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
+ * @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
*/
public class KeycloakDeploymentBuilderTest {
@@ -58,6 +61,7 @@ public class KeycloakDeploymentBuilderTest {
assertTrue(deployment.isPublicClient());
assertTrue(deployment.isEnableBasicAuth());
assertTrue(deployment.isExposeToken());
+ assertFalse(deployment.isOAuthQueryParameterEnabled());
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentTest.java
new file mode 100644
index 0000000..3bb5bce
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.adapters;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
+ * @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
+ */
+public class KeycloakDeploymentTest {
+ @Test
+ public void shouldNotEnableOAuthQueryParamWhenIgnoreIsTrue() {
+ KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
+ keycloakDeployment.setIgnoreOAuthQueryParameter(true);
+ assertFalse(keycloakDeployment.isOAuthQueryParameterEnabled());
+ }
+
+ @Test
+ public void shouldEnableOAuthQueryParamWhenIgnoreIsFalse() {
+ KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
+ keycloakDeployment.setIgnoreOAuthQueryParameter(false);
+ assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
+ }
+
+ @Test
+ public void shouldEnableOAuthQueryParamWhenIgnoreNotSet() {
+ KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
+
+ assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
+ }
+}
\ No newline at end of file
diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak.json b/adapters/oidc/adapter-core/src/test/resources/keycloak.json
index f53432f..521b8a9 100644
--- a/adapters/oidc/adapter-core/src/test/resources/keycloak.json
+++ b/adapters/oidc/adapter-core/src/test/resources/keycloak.json
@@ -32,5 +32,6 @@
"principal-attribute": "email",
"token-minimum-time-to-live": 10,
"min-time-between-jwks-requests": 20,
- "public-key-cache-ttl": 120
+ "public-key-cache-ttl": 120,
+ "ignore-oauth-query-parameter": true
}
\ No newline at end of file
diff --git a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakAutoConfiguration.java b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakAutoConfiguration.java
index 986cf9a..679691a 100755
--- a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakAutoConfiguration.java
+++ b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakAutoConfiguration.java
@@ -33,6 +33,7 @@ import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
import org.keycloak.adapters.undertow.KeycloakServletExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
@@ -60,6 +61,7 @@ import java.util.Set;
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
+@ConditionalOnProperty(value = "keycloak.enabled", matchIfMissing = true)
public class KeycloakAutoConfiguration {
private KeycloakSpringBootProperties keycloakProperties;
diff --git a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java
index 788412f..9c28a9a 100644
--- a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java
+++ b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java
@@ -34,6 +34,11 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
@JsonIgnore
private Map config = new HashMap();
+ /**
+ * Allow enabling of Keycloak Spring Boot adapter by configuration.
+ */
+ private boolean enabled = true;
+
public Map getConfig() {
return config;
}
@@ -43,6 +48,14 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
*/
private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>();
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
/**
* This matches security-constraint of the servlet spec
*/
diff --git a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationFailureHandler.java b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationFailureHandler.java
new file mode 100644
index 0000000..fcff678
--- /dev/null
+++ b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationFailureHandler.java
@@ -0,0 +1,41 @@
+/*
+ * 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.adapters.springsecurity.authentication;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+
+/**
+ * To return the forbidden code with the corresponding message.
+ *
+ * @author emilienbondu
+ *
+ */
+public class KeycloakAuthenticationFailureHandler implements AuthenticationFailureHandler {
+
+ @Override
+ public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate using the Authorization header");
+ }
+}
diff --git a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
index f9fb19e..fabf30e 100644
--- a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
+++ b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
@@ -17,6 +17,13 @@
package org.keycloak.adapters.springsecurity.filter;
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
@@ -25,7 +32,7 @@ import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
-import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
+import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory;
@@ -41,19 +48,12 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
-import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
/**
* Provides a Keycloak authentication processing filter.
*
@@ -61,17 +61,15 @@ import java.io.IOException;
* @version $Revision: 1 $
*/
public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
- public static final String DEFAULT_LOGIN_URL = "/sso/login";
+
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String SCHEME_BEARER = "bearer ";
public static final String SCHEME_BASIC = "basic ";
/**
- * Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
- * and any request with a <code>Authorization</code> header.
+ * Request matcher that matches all requests.
*/
- public static final RequestMatcher DEFAULT_REQUEST_MATCHER =
- new OrRequestMatcher(new AntPathRequestMatcher(DEFAULT_LOGIN_URL), new RequestHeaderRequestMatcher(AUTHORIZATION_HEADER));
+ private static RequestMatcher DEFAULT_REQUEST_MATCHER = new AntPathRequestMatcher("/**");
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
@@ -89,7 +87,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
*/
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
this(authenticationManager, DEFAULT_REQUEST_MATCHER);
- setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(DEFAULT_LOGIN_URL));
+ setAuthenticationFailureHandler(new KeycloakAuthenticationFailureHandler());
}
/**
@@ -140,7 +138,18 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
log.debug("Auth outcome: {}", result);
if (AuthOutcome.FAILED.equals(result)) {
- throw new KeycloakAuthenticationException("Auth outcome: " + result);
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ challenge.challenge(facade);
+ }
+ throw new KeycloakAuthenticationException("Invalid authorization header, see WWW-Authenticate header for details");
+ }
+ if (AuthOutcome.NOT_ATTEMPTED.equals(result)) {
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ challenge.challenge(facade);
+ }
+ throw new KeycloakAuthenticationException("Authorization header not found, see WWW-Authenticate header");
}
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
@@ -211,22 +220,10 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
}
@Override
- protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
- AuthenticationException failed) throws IOException, ServletException {
-
- if (this.isBearerTokenRequest(request)) {
- SecurityContextHolder.clearContext();
- response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate bearer token");
- return;
- }
- else if (this.isBasicAuthRequest(request)) {
- SecurityContextHolder.clearContext();
- response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate with basic authentication");
- return;
- }
-
- super.unsuccessfulAuthentication(request, response, failed);
- }
+ protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
+ AuthenticationException failed) throws IOException, ServletException {
+ super.unsuccessfulAuthentication(request, response, failed);
+ }
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
diff --git a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
index 515de4b..2414c38 100755
--- a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
+++ b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
@@ -27,6 +27,7 @@ import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
+import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.KeycloakUriBuilder;
@@ -89,6 +90,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
@Mock
private AuthenticationFailureHandler failureHandler;
+
+ private KeycloakAuthenticationFailureHandler keycloakFailureHandler;
@Mock
private OidcKeycloakAccount keycloakAccount;
@@ -106,6 +109,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
MockitoAnnotations.initMocks(this);
request = spy(new MockHttpServletRequest());
filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
+ keycloakFailureHandler = new KeycloakAuthenticationFailureHandler();
filter.setApplicationContext(applicationContext);
filter.setAuthenticationSuccessHandler(successHandler);
@@ -155,10 +159,12 @@ public class KeycloakAuthenticationProcessingFilterTest {
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
- filter.attemptAuthentication(request, response);
-
- verify(response).setStatus(302);
- verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
+ try {
+ filter.attemptAuthentication(request, response);
+ } catch (KeycloakAuthenticationException e) {
+ verify(response).setStatus(302);
+ verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
+ }
}
@Test(expected = KeycloakAuthenticationException.class)
@@ -210,8 +216,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
AuthenticationException exception = new BadCredentialsException("OOPS");
this.setBearerAuthHeader(request);
filter.unsuccessfulAuthentication(request, response, exception);
- verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
- verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
+ verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
any(AuthenticationException.class));
}
@@ -220,10 +225,18 @@ public class KeycloakAuthenticationProcessingFilterTest {
AuthenticationException exception = new BadCredentialsException("OOPS");
this.setBasicAuthHeader(request);
filter.unsuccessfulAuthentication(request, response, exception);
- verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
- verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
+ verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
any(AuthenticationException.class));
}
+
+ @Test
+ public void testDefaultFailureHanlder() throws Exception {
+ AuthenticationException exception = new BadCredentialsException("OOPS");
+ filter.setAuthenticationFailureHandler(keycloakFailureHandler);
+ filter.unsuccessfulAuthentication(request, response, exception);
+
+ verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), any(String.class));
+ }
@Test(expected = UnsupportedOperationException.class)
public void testSetAllowSessionCreation() throws Exception {
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java
index a7676ea..c090c60 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java
@@ -178,7 +178,14 @@ public class SharedAttributeDefinitons {
.setXmlName("autodetect-bearer-only")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
- .build();
+ .build();
+
+ protected static final SimpleAttributeDefinition IGNORE_OAUTH_QUERY_PARAMETER =
+ new SimpleAttributeDefinitionBuilder("ignore-oauth-query-parameter", ModelType.BOOLEAN, true)
+ .setXmlName("ignore-oauth-query-parameter")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static {
@@ -206,6 +213,7 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(TOKEN_STORE);
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
+ ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
}
/**
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties
index 71101a1..aa1ec96 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties
@@ -48,6 +48,7 @@ keycloak.realm.register-node-period=how often to re-register node
keycloak.realm.token-store=cookie or session storage for auth session data
keycloak.realm.principal-attribute=token attribute to use to set Principal name
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
+keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
keycloak.secure-deployment=A deployment secured by Keycloak
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
@@ -86,6 +87,7 @@ keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is
keycloak.secure-deployment.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-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.autodetect-bearer-only=autodetect bearer-only requests
+keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
keycloak.secure-deployment.credential=Credential value
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
index cc51ec4..f13b2c8 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -67,6 +67,7 @@
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
@@ -111,6 +112,7 @@
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java
index fafed42..4d693db 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java
@@ -179,6 +179,12 @@ public class SharedAttributeDefinitons {
.setDefaultValue(new ModelNode(false))
.build();
+ protected static final SimpleAttributeDefinition IGNORE_OAUTH_QUERY_PARAMETER =
+ new SimpleAttributeDefinitionBuilder("ignore-oauth-query-parameter", ModelType.BOOLEAN, true)
+ .setXmlName("ignore-oauth-query-parameter")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
@@ -209,6 +215,7 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(TOKEN_STORE);
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
+ ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
}
private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) {
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 a297c1d..1df5979 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
@@ -48,6 +48,7 @@ keycloak.realm.register-node-period=how often to re-register node
keycloak.realm.token-store=cookie or session storage for auth session data
keycloak.realm.principal-attribute=token attribute to use to set Principal name
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
+keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
keycloak.secure-deployment=A deployment secured by Keycloak
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
@@ -86,6 +87,7 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri
keycloak.secure-deployment.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-deployment.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-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
+keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
keycloak.secure-deployment.credential=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 8118209..604e6ac 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
@@ -67,6 +67,7 @@
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
@@ -111,6 +112,7 @@
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
index 550eeeb..5721b03 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -53,10 +53,12 @@ import org.keycloak.saml.SAML2AuthnRequestBuilder;
import org.keycloak.saml.SAMLRequestParser;
import org.keycloak.saml.SignatureAlgorithm;
import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.Base64;
+import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
@@ -74,10 +76,14 @@ import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.*;
+
+import javax.xml.namespace.QName;
+
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
+import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
/**
*
@@ -210,7 +216,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return AuthOutcome.FAILED;
}
}
- return handleLoginResponse((ResponseType) statusResponse, postBinding, onCreateSession);
+ return handleLoginResponse(holder, postBinding, onCreateSession);
} finally {
sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
}
@@ -312,7 +318,8 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return false;
}
- protected AuthOutcome handleLoginResponse(final ResponseType responseType, boolean postBinding, OnSessionCreated onCreateSession) {
+ protected AuthOutcome handleLoginResponse(SAMLDocumentHolder responseHolder, boolean postBinding, OnSessionCreated onCreateSession) {
+ final ResponseType responseType = (ResponseType) responseHolder.getSamlObject();
AssertionType assertion = null;
if (! isSuccessfulSamlResponse(responseType) || responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
challenge = new AuthChallenge() {
@@ -357,11 +364,12 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
try {
- validateSamlSignature(new SAMLDocumentHolder(AssertionUtil.asDocument(assertion)), postBinding, GeneralConstants.SAML_RESPONSE_KEY);
+ validateSamlSignature(new SAMLDocumentHolder(buildAssertionDocument(responseHolder, assertion)), postBinding, GeneralConstants.SAML_RESPONSE_KEY);
} catch (VerificationException e) {
log.error("Failed to verify saml assertion signature", e);
challenge = new AuthChallenge() {
+
@Override
public boolean challenge(HttpFacade exchange) {
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
@@ -376,8 +384,24 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
}
};
return AuthOutcome.FAILED;
- } catch (ProcessingException e) {
- e.printStackTrace();
+ } catch (Exception e) {
+ log.error("Error processing validation of SAML assertion: " + e.getMessage());
+ challenge = new AuthChallenge() {
+
+ @Override
+ public boolean challenge(HttpFacade exchange) {
+ SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.EXTRACTION_FAILURE);
+ exchange.getRequest().setError(error);
+ exchange.getResponse().sendError(403);
+ return true;
+ }
+
+ @Override
+ public int getResponseCode() {
+ return 403;
+ }
+ };
+ return AuthOutcome.FAILED;
}
}
@@ -480,6 +504,21 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
&& Objects.equals(responseType.getStatus().getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
}
+ private Document buildAssertionDocument(final SAMLDocumentHolder responseHolder, AssertionType assertion) throws ConfigurationException, ProcessingException {
+ Element encryptedAssertion = org.keycloak.saml.common.util.DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
+ if (encryptedAssertion != null) {
+ // encrypted assertion.
+ // We'll need to decrypt it first.
+ Document encryptedAssertionDocument = DocumentUtil.createDocument();
+ encryptedAssertionDocument.appendChild(encryptedAssertionDocument.importNode(encryptedAssertion, true));
+ Element assertionElement = XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
+ Document assertionDocument = DocumentUtil.createDocument();
+ assertionDocument.appendChild(assertionDocument.importNode(assertionElement, true));
+ return assertionDocument;
+ }
+ return AssertionUtil.asDocument(assertion);
+ }
+
private String getAttributeValue(Object attrValue) {
String value = null;
if (attrValue instanceof String) {
boms/adapter/pom.xml 5(+5 -0)
diff --git a/boms/adapter/pom.xml b/boms/adapter/pom.xml
index ccc3504..46c6272 100644
--- a/boms/adapter/pom.xml
+++ b/boms/adapter/pom.xml
@@ -124,6 +124,11 @@
<artifactId>keycloak-spring-boot-starter</artifactId>
<version>3.2.0.CR1-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-authz-client</artifactId>
+ <version>3.2.0.CR1-SNAPSHOT</version>
+ </dependency>
</dependencies>
</dependencyManagement>
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index ddd525b..2eb5089 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
* Configuration for Java based adapters
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
+ * @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
* @version $Revision: 1 $
*/
@JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required",
@@ -38,7 +40,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
"register-node-at-startup", "register-node-period", "token-store", "principal-attribute",
"proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live",
"min-time-between-jwks-requests", "public-key-cache-ttl",
- "policy-enforcer"
+ "policy-enforcer", "ignore-oauth-query-parameter"
})
public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClientConfig {
@@ -81,6 +83,8 @@ public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClien
// https://tools.ietf.org/html/rfc7636
@JsonProperty("enable-pkce")
protected boolean pkce = false;
+ @JsonProperty("ignore-oauth-query-parameter")
+ protected boolean ignoreOAuthQueryParameter = false;
/**
* The Proxy url to use for requests to the auth-server, configurable via the adapter config property {@code proxy-url}.
@@ -257,4 +261,11 @@ public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClien
this.pkce = pkce;
}
+ public boolean isIgnoreOAuthQueryParameter() {
+ return ignoreOAuthQueryParameter;
+ }
+
+ public void setIgnoreOAuthQueryParameter(boolean ignoreOAuthQueryParameter) {
+ this.ignoreOAuthQueryParameter = ignoreOAuthQueryParameter;
+ }
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationResponse.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationResponse.java
index 4565771..bfa4c30 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationResponse.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyEvaluationResponse.java
@@ -19,13 +19,11 @@
package org.keycloak.representations.idm.authorization;
import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.idm.authorization.DecisionEffect;
-import org.keycloak.representations.idm.authorization.PolicyRepresentation;
-import org.keycloak.representations.idm.authorization.ResourceRepresentation;
-import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -123,7 +121,7 @@ public class PolicyEvaluationResponse {
private PolicyRepresentation policy;
private DecisionEffect status;
private List<PolicyResultRepresentation> associatedPolicies;
- private List<ScopeRepresentation> scopes = new ArrayList<>();
+ private Set<String> scopes = new HashSet<>();
public PolicyRepresentation getPolicy() {
return policy;
@@ -162,11 +160,11 @@ public class PolicyEvaluationResponse {
return this.policy.equals(policy.getPolicy());
}
- public void setScopes(List<ScopeRepresentation> scopes) {
+ public void setScopes(Set<String> scopes) {
this.scopes = scopes;
}
- public List<ScopeRepresentation> getScopes() {
+ public Set<String> getScopes() {
return scopes;
}
}
diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl
index d78ff75..5ea7e93 100755
--- a/distribution/demo-dist/src/main/xslt/standalone.xsl
+++ b/distribution/demo-dist/src/main/xslt/standalone.xsl
@@ -92,7 +92,9 @@
<local-cache name="authenticationSessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="loginFailures"/>
- <local-cache name="authorization"/>
+ <local-cache name="authorization">
+ <eviction max-entries="10000" strategy="LRU"/>
+ </local-cache>
<local-cache name="actionTokens"/>
<local-cache name="work"/>
<local-cache name="keys">
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
index d3d8317..49b8385 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
@@ -326,6 +326,10 @@ public class LDAPOperationManager {
filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + id + "))";
}
+ if (logger.isTraceEnabled()) {
+ logger.tracef("Using filter for lookup user by LDAP ID: %s", filter);
+ }
+
return filter;
}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index ed7e071..cba7eb3 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -163,8 +163,13 @@ public interface RealmResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- public Response partialImport(PartialImportRepresentation rep);
+ Response partialImport(PartialImportRepresentation rep);
+ @Path("partial-export")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ RealmRepresentation partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles,
+ @QueryParam("exportClients") Boolean exportClients);
@Path("authentication")
@Consumes(MediaType.APPLICATION_JSON)
AuthenticationManagementResource flows();
diff --git a/integration/client-registration/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/integration/client-registration/src/main/java/org/keycloak/client/registration/ClientRegistration.java
index b4ee78a..15fe4b9 100644
--- a/integration/client-registration/src/main/java/org/keycloak/client/registration/ClientRegistration.java
+++ b/integration/client-registration/src/main/java/org/keycloak/client/registration/ClientRegistration.java
@@ -17,8 +17,11 @@
package org.keycloak.client.registration;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.IOException;
+import java.io.InputStream;
+
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClients;
import org.keycloak.representations.adapters.config.AdapterConfig;
@@ -26,8 +29,8 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.util.JsonSerialization;
-import java.io.IOException;
-import java.io.InputStream;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -73,7 +76,7 @@ public class ClientRegistration {
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
- InputStream resultStream = httpUtil.doPost(content, JSON, JSON, DEFAULT);
+ InputStream resultStream = httpUtil.doPost(content, JSON, UTF_8, JSON, DEFAULT);
return deserialize(resultStream, ClientRepresentation.class);
}
@@ -89,7 +92,7 @@ public class ClientRegistration {
public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
- InputStream resultStream = httpUtil.doPut(content, JSON, JSON, DEFAULT, client.getClientId());
+ InputStream resultStream = httpUtil.doPut(content, JSON, UTF_8, JSON, DEFAULT, client.getClientId());
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
}
@@ -129,7 +132,7 @@ public class ClientRegistration {
public OIDCClientRepresentation create(OIDCClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
- InputStream resultStream = httpUtil.doPost(content, JSON, JSON, OIDC);
+ InputStream resultStream = httpUtil.doPost(content, JSON, UTF_8, JSON, OIDC);
return deserialize(resultStream, OIDCClientRepresentation.class);
}
@@ -140,7 +143,7 @@ public class ClientRegistration {
public OIDCClientRepresentation update(OIDCClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
- InputStream resultStream = httpUtil.doPut(content, JSON, JSON, OIDC, client.getClientId());
+ InputStream resultStream = httpUtil.doPut(content, JSON, UTF_8, JSON, OIDC, client.getClientId());
return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
}
@@ -157,7 +160,7 @@ public class ClientRegistration {
public class SAMLClientRegistration {
public ClientRepresentation create(String entityDescriptor) throws ClientRegistrationException {
- InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, JSON, SAML);
+ InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, UTF_8, JSON, SAML);
return deserialize(resultStream, ClientRepresentation.class);
}
diff --git a/integration/client-registration/src/main/java/org/keycloak/client/registration/HttpUtil.java b/integration/client-registration/src/main/java/org/keycloak/client/registration/HttpUtil.java
index 7ca1361..f6e65e6 100644
--- a/integration/client-registration/src/main/java/org/keycloak/client/registration/HttpUtil.java
+++ b/integration/client-registration/src/main/java/org/keycloak/client/registration/HttpUtil.java
@@ -31,6 +31,7 @@ import org.keycloak.common.util.StreamUtil;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.Charset;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -52,13 +53,13 @@ class HttpUtil {
this.auth = auth;
}
- InputStream doPost(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
+ InputStream doPost(String content, String contentType, Charset charset, String acceptType, String... path) throws ClientRegistrationException {
try {
HttpPost request = new HttpPost(getUrl(baseUri, path));
- request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
+ request.setHeader(HttpHeaders.CONTENT_TYPE, contentType(contentType, charset));
request.setHeader(HttpHeaders.ACCEPT, acceptType);
- request.setEntity(new StringEntity(content));
+ request.setEntity(new StringEntity(content, charset));
addAuth(request);
@@ -77,6 +78,10 @@ class HttpUtil {
throw new ClientRegistrationException("Failed to send request", e);
}
}
+
+ private String contentType(String contentType, Charset charset) {
+ return contentType + ";charset=" + charset.name();
+ }
InputStream doGet(String acceptType, String... path) throws ClientRegistrationException {
try {
@@ -105,13 +110,13 @@ class HttpUtil {
}
}
- InputStream doPut(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
+ InputStream doPut(String content, String contentType, Charset charset, String acceptType, String... path) throws ClientRegistrationException {
try {
HttpPut request = new HttpPut(getUrl(baseUri, path));
- request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
+ request.setHeader(HttpHeaders.CONTENT_TYPE, contentType(contentType, charset));
request.setHeader(HttpHeaders.ACCEPT, acceptType);
- request.setEntity(new StringEntity(content));
+ request.setEntity(new StringEntity(content, charset));
addAuth(request);
misc/keycloak-test-helper/pom.xml 49(+28 -21)
diff --git a/misc/keycloak-test-helper/pom.xml b/misc/keycloak-test-helper/pom.xml
index 97741bd..5a9983c 100644
--- a/misc/keycloak-test-helper/pom.xml
+++ b/misc/keycloak-test-helper/pom.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
@@ -12,26 +13,32 @@
<name>keycloak-test-helper</name>
<description>Helper library to test application using Keycloak.</description>
<packaging>jar</packaging>
+ <properties>
+ <resteasy.client.version>3.0.7.Final</resteasy.client.version>
+ </properties>
<dependencies>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-client-registration-api</artifactId>
- <version>3.2.0.CR1-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-admin-client</artifactId>
- <version>3.2.0.CR1-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>org.jboss.resteasy</groupId>
- <artifactId>resteasy-client</artifactId>
- <version>3.0.7.Final</version>
- </dependency>
- <dependency>
- <groupId>org.jboss.resteasy</groupId>
- <artifactId>resteasy-jackson2-provider</artifactId>
- <version>3.0.7.Final</version>
- </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-client-registration-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-admin-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-client</artifactId>
+ <version>3.0.7.Final</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-jackson2-provider</artifactId>
+ <version>3.0.7.Final</version>
+ </dependency>
+ <dependency>
+ <groupId>org.seleniumhq.selenium</groupId>
+ <artifactId>selenium-java</artifactId>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/page/IndexPage.java b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/page/IndexPage.java
new file mode 100644
index 0000000..d51f9a1
--- /dev/null
+++ b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/page/IndexPage.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.test.page;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
+ */
+public class IndexPage {
+
+ public static final String UNAUTHORIZED = "401 Unauthorized";
+
+ @FindBy(name = "loginBtn")
+ private WebElement loginButton;
+
+ @FindBy(name = "logoutBtn")
+ private WebElement logoutButton;
+
+ @FindBy(name = "adminBtn")
+ private WebElement adminButton;
+
+ @FindBy(name = "publicBtn")
+ private WebElement publicButton;
+
+ @FindBy(name = "securedBtn")
+ private WebElement securedBtn;
+
+ public void clickLogin() {
+ loginButton.click();
+ }
+
+ public void clickLogout() {
+ logoutButton.click();
+ }
+
+ public void clickAdmin() {
+ adminButton.click();
+ }
+
+ public void clickPublic() {
+ publicButton.click();
+ }
+
+ public void clickSecured() {
+ securedBtn.click();
+ }
+}
diff --git a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/page/LoginPage.java b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/page/LoginPage.java
new file mode 100644
index 0000000..5c2bed0
--- /dev/null
+++ b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/page/LoginPage.java
@@ -0,0 +1,87 @@
+/*
+ * 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.test.page;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginPage {
+
+ @FindBy(id = "username")
+ private WebElement usernameInput;
+
+ @FindBy(id = "password")
+ private WebElement passwordInput;
+
+ @FindBy(id = "totp")
+ private WebElement totp;
+
+ @FindBy(id = "rememberMe")
+ private WebElement rememberMe;
+
+ @FindBy(name = "login")
+ private WebElement submitButton;
+
+ @FindBy(name = "cancel")
+ private WebElement cancelButton;
+
+ @FindBy(linkText = "Register")
+ private WebElement registerLink;
+
+ @FindBy(linkText = "Forgot Password?")
+ private WebElement resetPasswordLink;
+
+ @FindBy(linkText = "Username")
+ private WebElement recoverUsernameLink;
+
+ @FindBy(className = "alert-error")
+ private WebElement loginErrorMessage;
+
+ @FindBy(className = "alert-warning")
+ private WebElement loginWarningMessage;
+
+ @FindBy(className = "alert-success")
+ private WebElement loginSuccessMessage;
+
+
+ @FindBy(className = "alert-info")
+ private WebElement loginInfoMessage;
+
+ @FindBy(className = "instruction")
+ private WebElement instruction;
+
+
+ @FindBy(id = "kc-current-locale-link")
+ private WebElement languageText;
+
+ @FindBy(id = "kc-locale-dropdown")
+ private WebElement localeDropdown;
+
+ public void login(String username, String password) {
+ usernameInput.clear();
+ usernameInput.sendKeys(username);
+
+ passwordInput.clear();
+ passwordInput.sendKeys(password);
+
+ submitButton.click();
+ }
+}
diff --git a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/page/ProfilePage.java b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/page/ProfilePage.java
new file mode 100644
index 0000000..903ee12
--- /dev/null
+++ b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/page/ProfilePage.java
@@ -0,0 +1,71 @@
+/*
+ * 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.test.page;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
+ */
+public class ProfilePage {
+
+ @FindBy(name = "profileBtn")
+ private WebElement profileButton;
+
+ @FindBy(name = "tokenBtn")
+ private WebElement tokenButton;
+
+ @FindBy(name = "logoutBtn")
+ private WebElement logoutButton;
+
+ @FindBy(name = "accountBtn")
+ private WebElement accountButton;
+
+ @FindBy(id = "token-content")
+ private WebElement tokenContent;
+
+ @FindBy(id = "username")
+ private WebElement username;
+
+ public String getUsername() {
+ return username.getText();
+ }
+
+ public void clickProfile() {
+ profileButton.click();
+ }
+
+ public void clickToken() {
+ tokenButton.click();
+ }
+
+ public void clickLogout() {
+ logoutButton.click();
+ }
+
+ public void clickAccount() {
+ accountButton.click();
+ }
+
+ public String getTokenContent() throws Exception {
+ return tokenContent.getText();
+ }
+
+}
+
diff --git a/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml b/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml
index 154d5a3..f48407f 100644
--- a/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml
+++ b/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml
@@ -15,6 +15,10 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-adapter</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-authz-client</artifactId>
+ </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InResource.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InResource.java
new file mode 100644
index 0000000..e316b1c
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InResource.java
@@ -0,0 +1,24 @@
+/*
+ * 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.cache.infinispan.authorization.entities;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface InResource {
+ String getResourceId();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java
new file mode 100644
index 0000000..c345677
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.cache.infinispan.authorization.entities;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface InScope {
+ String getScopeId();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyListQuery.java
index 1cbf044..0b2227a 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyListQuery.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyListQuery.java
@@ -9,7 +9,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class PolicyListQuery extends AbstractRevisioned implements InResourceServer {
+public class PolicyListQuery extends AbstractRevisioned implements PolicyQuery {
private final Set<String> policies;
private final String serverId;
@@ -33,4 +33,9 @@ public class PolicyListQuery extends AbstractRevisioned implements InResourceSer
public Set<String> getPolicies() {
return policies;
}
+
+ @Override
+ public boolean isInvalid(Set<String> invalidations) {
+ return invalidations.contains(getId()) || invalidations.contains(getResourceServerId());
+ }
}
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyQuery.java
new file mode 100644
index 0000000..a844b25
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyQuery.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.models.cache.infinispan.authorization.entities;
+
+import java.util.Set;
+
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface PolicyQuery extends InResourceServer, Revisioned {
+
+ Set<String> getPolicies();
+ boolean isInvalid(Set<String> invalidations);
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyResourceListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyResourceListQuery.java
new file mode 100755
index 0000000..d0af77b
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyResourceListQuery.java
@@ -0,0 +1,42 @@
+/*
+ * 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.cache.infinispan.authorization.entities;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class PolicyResourceListQuery extends PolicyListQuery implements InResource {
+
+ private final String resourceId;
+
+ public PolicyResourceListQuery(Long revision, String id, String resourceId, Set<String> policies, String serverId) {
+ super(revision, id, policies, serverId);
+ this.resourceId = resourceId;
+ }
+
+ @Override
+ public boolean isInvalid(Set<String> invalidations) {
+ return super.isInvalid(invalidations) || invalidations.contains(getResourceId());
+ }
+
+ @Override
+ public String getResourceId() {
+ return resourceId;
+ }
+}
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyScopeListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyScopeListQuery.java
new file mode 100755
index 0000000..7db8a01
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyScopeListQuery.java
@@ -0,0 +1,42 @@
+/*
+ * 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.cache.infinispan.authorization.entities;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class PolicyScopeListQuery extends PolicyListQuery implements InScope {
+
+ private final String scopeId;
+
+ public PolicyScopeListQuery(Long revision, String id, String scopeId, Set<String> resources, String serverId) {
+ super(revision, id, resources, serverId);
+ this.scopeId = scopeId;
+ }
+
+ @Override
+ public String getScopeId() {
+ return scopeId;
+ }
+
+ @Override
+ public boolean isInvalid(Set<String> invalidations) {
+ return super.isInvalid(invalidations) || invalidations.contains(getScopeId());
+ }
+}
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceListQuery.java
index d322b62..d8db81b 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceListQuery.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceListQuery.java
@@ -9,7 +9,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class ResourceListQuery extends AbstractRevisioned implements InResourceServer {
+public class ResourceListQuery extends AbstractRevisioned implements ResourceQuery, InResourceServer {
private final Set<String> resources;
private final String serverId;
@@ -33,4 +33,9 @@ public class ResourceListQuery extends AbstractRevisioned implements InResourceS
public Set<String> getResources() {
return resources;
}
+
+ @Override
+ public boolean isInvalid(Set<String> invalidations) {
+ return invalidations.contains(getId()) || invalidations.contains(getResourceServerId());
+ }
}
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceQuery.java
new file mode 100644
index 0000000..db07bc8
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceQuery.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.models.cache.infinispan.authorization.entities;
+
+import java.util.Set;
+
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface ResourceQuery extends Revisioned {
+
+ Set<String> getResources();
+ boolean isInvalid(Set<String> invalidations);
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java
new file mode 100755
index 0000000..ee73344
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java
@@ -0,0 +1,26 @@
+package org.keycloak.models.cache.infinispan.authorization.entities;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ResourceScopeListQuery extends ResourceListQuery implements InScope {
+
+ private final String scopeId;
+
+ public ResourceScopeListQuery(Long revision, String id, String scopeId, Set<String> resources, String serverId) {
+ super(revision, id, resources, serverId);
+ this.scopeId = scopeId;
+ }
+
+ @Override
+ public String getScopeId() {
+ return scopeId;
+ }
+
+ @Override
+ public boolean isInvalid(Set<String> invalidations) {
+ return super.isInvalid(invalidations) || invalidations.contains(getScopeId());
+ }
+}
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java
index 3a721ba..759c284 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java
@@ -17,11 +17,11 @@
package org.keycloak.models.cache.infinispan.authorization.events;
+import java.util.Set;
+
import org.keycloak.models.cache.infinispan.authorization.StoreFactoryCacheManager;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
-import java.util.Set;
-
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@@ -29,12 +29,14 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati
private String id;
private String name;
+ private Set<String> resources;
private String serverId;
- public static PolicyRemovedEvent create(String id, String name, String serverId) {
+ public static PolicyRemovedEvent create(String id, String name, Set<String> resources, String serverId) {
PolicyRemovedEvent event = new PolicyRemovedEvent();
event.id = id;
event.name = name;
+ event.resources = resources;
event.serverId = serverId;
return event;
}
@@ -51,6 +53,6 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
- cache.policyRemoval(id, name, serverId, invalidations);
+ cache.policyRemoval(id, name, resources, serverId, invalidations);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java
index 1e1d992..b576bda 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java
@@ -29,12 +29,14 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati
private String id;
private String name;
+ private static Set<String> resources;
private String serverId;
- public static PolicyUpdatedEvent create(String id, String name, String serverId) {
+ public static PolicyUpdatedEvent create(String id, String name, Set<String> resources, String serverId) {
PolicyUpdatedEvent event = new PolicyUpdatedEvent();
event.id = id;
event.name = name;
+ event.resources = resources;
event.serverId = serverId;
return event;
}
@@ -51,6 +53,6 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
- cache.policyUpdated(id, name, serverId, invalidations);
+ cache.policyUpdated(id, name, resources, serverId, invalidations);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceRemovedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceRemovedEvent.java
index 4e1a31f..15964a6 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceRemovedEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/ResourceRemovedEvent.java
@@ -29,12 +29,20 @@ public class ResourceRemovedEvent extends InvalidationEvent implements Authoriza
private String id;
private String name;
+ private String owner;
private String serverId;
+ private String type;
+ private String uri;
+ private Set<String> scopes;
- public static ResourceRemovedEvent create(String id, String name, String serverId) {
+ public static ResourceRemovedEvent create(String id, String name, String type, String uri, String owner, Set<String> scopes, String serverId) {
ResourceRemovedEvent event = new ResourceRemovedEvent();
event.id = id;
event.name = name;
+ event.type = type;
+ event.uri = uri;
+ event.owner = owner;
+ event.scopes = scopes;
event.serverId = serverId;
return event;
}
@@ -51,6 +59,6 @@ public class ResourceRemovedEvent extends InvalidationEvent implements Authoriza
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
- cache.resourceRemoval(id, name, serverId, invalidations);
+ cache.resourceRemoval(id, name, type, uri, owner, scopes, serverId, invalidations);
}
}
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 113d5e0..cc30f23 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
@@ -30,11 +30,17 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
private String id;
private String name;
private String serverId;
+ private String type;
+ private String uri;
+ private Set<String> scopes;
- public static ResourceUpdatedEvent create(String id, String name, String serverId) {
+ public static ResourceUpdatedEvent create(String id, String name, String type, String uri, Set<String> scopes, String serverId) {
ResourceUpdatedEvent event = new ResourceUpdatedEvent();
event.id = id;
event.name = name;
+ event.type = type;
+ event.uri = uri;
+ event.scopes = scopes;
event.serverId = serverId;
return event;
}
@@ -51,6 +57,6 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
- cache.resourceUpdated(id, name, serverId, invalidations);
+ cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
index a8cd379..970d1b9 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
@@ -27,8 +27,6 @@ import org.keycloak.representations.idm.authorization.Logic;
import java.util.Collections;
import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -49,7 +47,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override
public Policy getDelegateForUpdate() {
if (updated == null) {
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourceServerId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getResourceServerId());
updated = cacheSession.getPolicyStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
if (updated == null) throw new IllegalStateException("Not found in database");
}
@@ -98,6 +96,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override
public void setName(String name) {
getDelegateForUpdate();
+ cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getResourceServerId());
updated.setName(name);
}
@@ -208,7 +207,6 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
public void addScope(Scope scope) {
getDelegateForUpdate();
updated.addScope(scope);
-
}
@Override
@@ -235,6 +233,9 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override
public void addResource(Resource resource) {
getDelegateForUpdate();
+ HashSet<String> resources = new HashSet<>();
+ resources.add(resource.getId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId());
updated.addResource(resource);
}
@@ -242,6 +243,9 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override
public void removeResource(Resource resource) {
getDelegateForUpdate();
+ HashSet<String> resources = new HashSet<>();
+ resources.add(resource.getId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId());
updated.removeResource(resource);
}
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 d44cc7c..dc721b7 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
@@ -26,6 +26,8 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -44,7 +46,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public Resource getDelegateForUpdate() {
if (updated == null) {
- cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getResourceServerId());
+ cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
updated = cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
if (updated == null) throw new IllegalStateException("Not found in database");
}
@@ -93,6 +95,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setName(String name) {
getDelegateForUpdate();
+ cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
updated.setName(name);
}
@@ -124,8 +127,8 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setUri(String uri) {
getDelegateForUpdate();
+ cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uri, cached.getScopesIds(), cached.getResourceServerId());
updated.setUri(uri);
-
}
@Override
@@ -137,6 +140,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setType(String type) {
getDelegateForUpdate();
+ cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
updated.setType(type);
}
@@ -164,6 +168,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void updateScopes(Set<Scope> scopes) {
getDelegateForUpdate();
+ cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId());
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 2c86648..7943589 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
@@ -21,7 +21,9 @@ import org.jboss.logging.Logger;
import org.keycloak.models.cache.infinispan.CacheManager;
import org.keycloak.models.cache.infinispan.RealmCacheManager;
import org.keycloak.models.cache.infinispan.authorization.events.AuthorizationCacheInvalidationEvent;
+import org.keycloak.models.cache.infinispan.authorization.stream.InResourcePredicate;
import org.keycloak.models.cache.infinispan.authorization.stream.InResourceServerPredicate;
+import org.keycloak.models.cache.infinispan.authorization.stream.InScopePredicate;
import org.keycloak.models.cache.infinispan.entities.Revisioned;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
@@ -65,28 +67,54 @@ public class StoreFactoryCacheManager extends CacheManager {
public void scopeUpdated(String id, String name, String serverId, Set<String> invalidations) {
invalidations.add(id);
invalidations.add(StoreFactoryCacheSession.getScopeByNameCacheKey(name, serverId));
+ invalidations.add(StoreFactoryCacheSession.getResourceByScopeCacheKey(id, serverId));
}
public void scopeRemoval(String id, String name, String serverId, Set<String> invalidations) {
scopeUpdated(id, name, serverId, invalidations);
+ addInvalidations(InScopePredicate.create().scope(id), invalidations);
}
- public void resourceUpdated(String id, String name, String serverId, Set<String> invalidations) {
+ public void resourceUpdated(String id, String name, String type, String uri, Set<String> scopes, String serverId, Set<String> invalidations) {
invalidations.add(id);
invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, serverId));
+
+ if (type != null) {
+ invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, serverId));
+ addInvalidations(InResourcePredicate.create().resource(type), invalidations);
+ }
+
+ if (uri != null) {
+ invalidations.add(StoreFactoryCacheSession.getResourceByUriCacheKey(uri, serverId));
+ }
+
+ if (scopes != null) {
+ for (String scope : scopes) {
+ invalidations.add(StoreFactoryCacheSession.getResourceByScopeCacheKey(scope, serverId));
+ addInvalidations(InScopePredicate.create().scope(scope), invalidations);
+ }
+ }
}
- public void resourceRemoval(String id, String name, String serverId, Set<String> invalidations) {
- resourceUpdated(id, name, serverId, invalidations);
+ public void resourceRemoval(String id, String name, String type, String uri, String owner, Set<String> scopes, String serverId, Set<String> invalidations) {
+ resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
+ invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId));
+ addInvalidations(InResourcePredicate.create().resource(id), invalidations);
}
- public void policyUpdated(String id, String name, String serverId, Set<String> invalidations) {
+ public void policyUpdated(String id, String name, Set<String> resources, String serverId, Set<String> invalidations) {
invalidations.add(id);
invalidations.add(StoreFactoryCacheSession.getPolicyByNameCacheKey(name, serverId));
+
+ if (resources != null) {
+ for (String resource : resources) {
+ invalidations.add(StoreFactoryCacheSession.getPolicyByResource(resource, serverId));
+ }
+ }
}
- public void policyRemoval(String id, String name, String serverId, Set<String> invalidations) {
- policyUpdated(id, name, serverId, invalidations);
+ public void policyRemoval(String id, String name, Set<String> resources, String serverId, Set<String> invalidations) {
+ policyUpdated(id, name, resources, serverId, 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 87f311b..3e4c205 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
@@ -16,6 +16,18 @@
*/
package org.keycloak.models.cache.infinispan.authorization;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
import org.jboss.logging.Logger;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
@@ -34,7 +46,12 @@ import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourc
import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourceServer;
import org.keycloak.models.cache.infinispan.authorization.entities.CachedScope;
import org.keycloak.models.cache.infinispan.authorization.entities.PolicyListQuery;
+import org.keycloak.models.cache.infinispan.authorization.entities.PolicyQuery;
+import org.keycloak.models.cache.infinispan.authorization.entities.PolicyResourceListQuery;
+import org.keycloak.models.cache.infinispan.authorization.entities.PolicyScopeListQuery;
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceListQuery;
+import org.keycloak.models.cache.infinispan.authorization.entities.ResourceQuery;
+import org.keycloak.models.cache.infinispan.authorization.entities.ResourceScopeListQuery;
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceServerListQuery;
import org.keycloak.models.cache.infinispan.authorization.entities.ScopeListQuery;
import org.keycloak.models.cache.infinispan.authorization.events.PolicyRemovedEvent;
@@ -48,12 +65,6 @@ import org.keycloak.models.cache.infinispan.authorization.events.ScopeUpdatedEve
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@@ -233,20 +244,20 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
invalidationEvents.add(ScopeUpdatedEvent.create(id, name, serverId));
}
- public void registerResourceInvalidation(String id, String name, String serverId) {
- cache.resourceUpdated(id, name, serverId, invalidations);
+ public void registerResourceInvalidation(String id, String name, String type, String uri, Set<String> scopes, String serverId) {
+ cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
ResourceAdapter adapter = managedResources.get(id);
if (adapter != null) adapter.invalidateFlag();
- invalidationEvents.add(ResourceUpdatedEvent.create(id, name, serverId));
+ invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId));
}
- public void registerPolicyInvalidation(String id, String name, String serverId) {
- cache.policyUpdated(id, name, serverId, invalidations);
+ public void registerPolicyInvalidation(String id, String name, Set<String> resources, String serverId) {
+ cache.policyUpdated(id, name, resources, serverId, invalidations);
PolicyAdapter adapter = managedPolicies.get(id);
if (adapter != null) adapter.invalidateFlag();
- invalidationEvents.add(PolicyUpdatedEvent.create(id, name, serverId));
+ invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, serverId));
}
public ResourceServerStore getResourceServerStoreDelegate() {
@@ -277,10 +288,38 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
return "resource.name." + name + "." + serverId;
}
+ public static String getResourceByOwnerCacheKey(String owner, String serverId) {
+ return "resource.owner." + owner + "." + serverId;
+ }
+
+ public static String getResourceByTypeCacheKey(String type, String serverId) {
+ return "resource.type." + type + "." + serverId;
+ }
+
+ public static String getResourceByUriCacheKey(String uri, String serverId) {
+ return "resource.uri." + uri + "." + serverId;
+ }
+
+ public static String getResourceByScopeCacheKey(String scopeId, String serverId) {
+ return "resource.scope." + scopeId + "." + serverId;
+ }
+
public static String getPolicyByNameCacheKey(String name, String serverId) {
return "policy.name." + name + "." + serverId;
}
+ public static String getPolicyByResource(String resourceId, String serverId) {
+ return "policy.resource." + resourceId + "." + serverId;
+ }
+
+ public static String getPolicyByResourceType(String type, String serverId) {
+ return "policy.resource.type." + type + "." + serverId;
+ }
+
+ public static String getPolicyByScope(String scope, String serverId) {
+ return "policy.scope." + scope + "." + serverId;
+ }
+
public StoreFactory getDelegate() {
if (delegate != null) return delegate;
delegate = session.getProvider(StoreFactory.class);
@@ -451,7 +490,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(), resourceServer.getId());
+ registerResourceInvalidation(resource.getId(), resource.getName(), resource.getType(), resource.getUri(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resourceServer.getId());
return resource;
}
@@ -462,8 +501,8 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
if (resource == null) return;
cache.invalidateObject(id);
- invalidationEvents.add(ResourceRemovedEvent.create(id, resource.getName(), resource.getResourceServer().getId()));
- cache.resourceRemoval(id, resource.getName(), resource.getResourceServer().getId(), invalidations);
+ invalidationEvents.add(ResourceRemovedEvent.create(id, resource.getName(), resource.getType(), resource.getUri(), resource.getOwner(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resource.getResourceServer().getId()));
+ cache.resourceRemoval(id, resource.getName(), resource.getType(), resource.getUri(), resource.getOwner(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resource.getResourceServer().getId(), invalidations);
getResourceStoreDelegate().delete(id);
}
@@ -498,37 +537,37 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
public Resource findByName(String name, String resourceServerId) {
if (name == null) return null;
String cacheKey = getResourceByNameCacheKey(name, resourceServerId);
- ResourceListQuery query = cache.get(cacheKey, ResourceListQuery.class);
- if (query != null) {
- logger.tracev("resource by name cache hit: {0}", name);
- }
- if (query == null) {
- Long loaded = cache.getCurrentRevision(cacheKey);
- Resource model = getResourceStoreDelegate().findByName(name, resourceServerId);
- if (model == null) return null;
- if (invalidations.contains(model.getId())) return model;
- query = new ResourceListQuery(loaded, cacheKey, model.getId(), resourceServerId);
- cache.addRevisioned(query, startupRevision);
- return model;
- } else if (invalidations.contains(cacheKey)) {
- return getResourceStoreDelegate().findByName(name, resourceServerId);
- } else {
- String id = query.getResources().iterator().next();
- if (invalidations.contains(id)) {
- return getResourceStoreDelegate().findByName(name, resourceServerId);
+ List<Resource> result = cacheQuery(cacheKey, ResourceListQuery.class, () -> {
+ Resource resource = getResourceStoreDelegate().findByName(name, resourceServerId);
+
+ if (resource == null) {
+ return Collections.emptyList();
}
- return findById(id, query.getResourceServerId());
+
+ return Arrays.asList(resource);
+ },
+ (revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
+
+ if (result.isEmpty()) {
+ return null;
}
+
+ return result.get(0);
}
@Override
public List<Resource> findByOwner(String ownerId, String resourceServerId) {
- return getResourceStoreDelegate().findByOwner(ownerId, resourceServerId);
+ String cacheKey = getResourceByOwnerCacheKey(ownerId, resourceServerId);
+ return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByOwner(ownerId, resourceServerId),
+ (revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
}
@Override
public List<Resource> findByUri(String uri, String resourceServerId) {
- return getResourceStoreDelegate().findByUri(uri, resourceServerId);
+ if (uri == null) return null;
+ String cacheKey = getResourceByUriCacheKey(uri, resourceServerId);
+ return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByUri(uri, resourceServerId),
+ (revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
}
@Override
@@ -543,21 +582,52 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
@Override
public List<Resource> findByScope(List<String> ids, String resourceServerId) {
- return getResourceStoreDelegate().findByScope(ids, resourceServerId);
+ if (ids == null) return null;
+ List<Resource> result = new ArrayList<>();
+
+ for (String id : ids) {
+ String cacheKey = getResourceByScopeCacheKey(id, resourceServerId);
+ result.addAll(cacheQuery(cacheKey, ResourceScopeListQuery.class, () -> getResourceStoreDelegate().findByScope(Arrays.asList(id), resourceServerId), (revision, resources) -> new ResourceScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId));
+ }
+
+ return result;
}
@Override
public List<Resource> findByType(String type, String resourceServerId) {
- return getResourceStoreDelegate().findByType(type, resourceServerId);
+ if (type == null) return null;
+ String cacheKey = getResourceByTypeCacheKey(type, resourceServerId);
+ return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByType(type, resourceServerId),
+ (revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
+ }
+
+ private <R, Q extends ResourceQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId) {
+ Q query = cache.get(cacheKey, queryType);
+ if (query != null) {
+ logger.tracev("cache hit for key: {0}", cacheKey);
+ }
+ if (query == null) {
+ Long loaded = cache.getCurrentRevision(cacheKey);
+ List<R> model = resultSupplier.get();
+ if (model == null) return null;
+ if (invalidations.contains(cacheKey)) return model;
+ query = querySupplier.apply(loaded, model);
+ cache.addRevisioned(query, startupRevision);
+ return model;
+ } else if (query.isInvalid(invalidations)) {
+ return resultSupplier.get();
+ } else {
+ return query.getResources().stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList());
+ }
}
}
protected class PolicyCache implements PolicyStore {
@Override
public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
- Policy resource = getPolicyStoreDelegate().create(representation, resourceServer);
- registerPolicyInvalidation(resource.getId(), resource.getName(), resourceServer.getId());
- return resource;
+ Policy policy = getPolicyStoreDelegate().create(representation, resourceServer);
+ registerPolicyInvalidation(policy.getId(), policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), resourceServer.getId());
+ return policy;
}
@Override
@@ -567,8 +637,8 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
if (policy == null) return;
cache.invalidateObject(id);
- invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), policy.getResourceServer().getId()));
- cache.policyRemoval(id, policy.getName(), policy.getResourceServer().getId(), invalidations);
+ invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), policy.getResourceServer().getId()));
+ cache.policyRemoval(id, policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), policy.getResourceServer().getId(), invalidations);
getPolicyStoreDelegate().delete(id);
}
@@ -604,27 +674,22 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
public Policy findByName(String name, String resourceServerId) {
if (name == null) return null;
String cacheKey = getPolicyByNameCacheKey(name, resourceServerId);
- PolicyListQuery query = cache.get(cacheKey, PolicyListQuery.class);
- if (query != null) {
- logger.tracev("policy by name cache hit: {0}", name);
- }
- if (query == null) {
- Long loaded = cache.getCurrentRevision(cacheKey);
- Policy model = getPolicyStoreDelegate().findByName(name, resourceServerId);
- if (model == null) return null;
- if (invalidations.contains(model.getId())) return model;
- query = new PolicyListQuery(loaded, cacheKey, model.getId(), resourceServerId);
- cache.addRevisioned(query, startupRevision);
- return model;
- } else if (invalidations.contains(cacheKey)) {
- return getPolicyStoreDelegate().findByName(name, resourceServerId);
- } else {
- String id = query.getPolicies().iterator().next();
- if (invalidations.contains(id)) {
- return getPolicyStoreDelegate().findByName(name, resourceServerId);
+ List<Policy> result = cacheQuery(cacheKey, PolicyListQuery.class, () -> {
+ Policy policy = getPolicyStoreDelegate().findByName(name, resourceServerId);
+
+ if (policy == null) {
+ return Collections.emptyList();
}
- return findById(id, query.getResourceServerId());
+
+ return Arrays.asList(policy);
+ },
+ (revision, policies) -> new PolicyListQuery(revision, cacheKey, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
+
+ if (result.isEmpty()) {
+ return null;
}
+
+ return result.get(0);
}
@Override
@@ -639,17 +704,29 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
@Override
public List<Policy> findByResource(String resourceId, String resourceServerId) {
- return getPolicyStoreDelegate().findByResource(resourceId, resourceServerId);
+ String cacheKey = getPolicyByResource(resourceId, resourceServerId);
+ return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResource(resourceId, resourceServerId),
+ (revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceId, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
}
@Override
public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
- return getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId);
+ String cacheKey = getPolicyByResourceType(resourceType, resourceServerId);
+ return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId),
+ (revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceType, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
}
@Override
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceServerId) {
- return getPolicyStoreDelegate().findByScopeIds(scopeIds, resourceServerId);
+ if (scopeIds == null) return null;
+ List<Policy> result = new ArrayList<>();
+
+ for (String id : scopeIds) {
+ String cacheKey = getPolicyByScope(id, resourceServerId);
+ result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId));
+ }
+
+ return result;
}
@Override
@@ -661,6 +738,26 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
public List<Policy> findDependentPolicies(String id, String resourceServerId) {
return getPolicyStoreDelegate().findDependentPolicies(id, resourceServerId);
}
+
+ private <R, Q extends PolicyQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId) {
+ Q query = cache.get(cacheKey, queryType);
+ if (query != null) {
+ logger.tracev("cache hit for key: {0}", cacheKey);
+ }
+ if (query == null) {
+ Long loaded = cache.getCurrentRevision(cacheKey);
+ List<R> model = resultSupplier.get();
+ if (model == null) return null;
+ if (invalidations.contains(cacheKey)) return model;
+ query = querySupplier.apply(loaded, model);
+ cache.addRevisioned(query, startupRevision);
+ return model;
+ } else if (query.isInvalid(invalidations)) {
+ return resultSupplier.get();
+ } else {
+ return query.getPolicies().stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList());
+ }
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InResourcePredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InResourcePredicate.java
new file mode 100755
index 0000000..f72de49
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InResourcePredicate.java
@@ -0,0 +1,50 @@
+/*
+ * 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.cache.infinispan.authorization.stream;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import org.keycloak.models.cache.infinispan.authorization.entities.InResource;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class InResourcePredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
+
+ private String resourceId;
+
+ public static InResourcePredicate create() {
+ return new InResourcePredicate();
+ }
+
+ public InResourcePredicate resource(String id) {
+ resourceId = id;
+ return this;
+ }
+
+ @Override
+ public boolean test(Map.Entry<String, Revisioned> entry) {
+ Object value = entry.getValue();
+ if (value == null) return false;
+ if (!(value instanceof InResource)) return false;
+
+ return resourceId.equals(((InResource)value).getResourceId());
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InScopePredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InScopePredicate.java
new file mode 100755
index 0000000..3a2a197
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InScopePredicate.java
@@ -0,0 +1,35 @@
+package org.keycloak.models.cache.infinispan.authorization.stream;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import org.keycloak.models.cache.infinispan.authorization.entities.InResourceServer;
+import org.keycloak.models.cache.infinispan.authorization.entities.InScope;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class InScopePredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
+ private String scopeId;
+
+ public static InScopePredicate create() {
+ return new InScopePredicate();
+ }
+
+ public InScopePredicate scope(String id) {
+ scopeId = id;
+ return this;
+ }
+
+ @Override
+ public boolean test(Map.Entry<String, Revisioned> entry) {
+ Object value = entry.getValue();
+ if (value == null) return false;
+ if (!(value instanceof InScope)) return false;
+
+ return scopeId.equals(((InScope)value).getScopeId());
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 92d41d5..f3c41e7 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -29,18 +29,20 @@ import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.ClientTemplateEntity;
import org.keycloak.models.jpa.entities.ProtocolMapperEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
-import org.keycloak.models.jpa.entities.ScopeMappingEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -222,7 +224,7 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
}
@Override
- public Set<RoleModel> getRealmScopeMappings() {
+ public Set<RoleModel> getRealmScopeMappings() {
Set<RoleModel> roleMappings = getScopeMappings();
Set<RoleModel> appRoles = new HashSet<>();
@@ -240,47 +242,22 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
@Override
public Set<RoleModel> getScopeMappings() {
- TypedQuery<String> query = em.createNamedQuery("clientScopeMappingIds", String.class);
- query.setParameter("client", getEntity());
- List<String> ids = query.getResultList();
- Set<RoleModel> roles = new HashSet<RoleModel>();
- for (String roleId : ids) {
- RoleModel role = realm.getRoleById(roleId);
- if (role == null) continue;
- roles.add(role);
- }
- return roles;
+ return getEntity().getScopeMapping().stream()
+ .map(RoleEntity::getId)
+ .map(realm::getRoleById)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
}
@Override
public void addScopeMapping(RoleModel role) {
- Set<RoleModel> roles = getScopeMappings();
- if (roles.contains(role)) return;
- ScopeMappingEntity entity = new ScopeMappingEntity();
- entity.setClient(getEntity());
RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
- entity.setRole(roleEntity);
- em.persist(entity);
- em.flush();
- em.detach(entity);
+ getEntity().getScopeMapping().add(roleEntity);
}
@Override
public void deleteScopeMapping(RoleModel role) {
- TypedQuery<ScopeMappingEntity> query = getRealmScopeMappingQuery(role);
- List<ScopeMappingEntity> results = query.getResultList();
- if (results.size() == 0) return;
- for (ScopeMappingEntity entity : results) {
- em.remove(entity);
- }
- }
-
- protected TypedQuery<ScopeMappingEntity> getRealmScopeMappingQuery(RoleModel role) {
- TypedQuery<ScopeMappingEntity> query = em.createNamedQuery("hasScope", ScopeMappingEntity.class);
- query.setParameter("client", getEntity());
- RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
- query.setParameter("role", roleEntity);
- return query;
+ getEntity().getScopeMapping().remove(RoleAdapter.toRoleEntity(role, em));
}
@Override
@@ -689,7 +666,6 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
}
RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
entities.add(roleEntity);
- em.flush();
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
index 8e759a4..c46b1cd 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
@@ -37,11 +37,6 @@ import javax.persistence.Table;
*/
@Table(name="AUTHENTICATION_EXECUTION")
@Entity
-@NamedQueries({
- @NamedQuery(name="getAuthenticationExecutionsByFlow", query="select authenticator from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm and authenticator.parentFlow = :parentFlow"),
- @NamedQuery(name="deleteAuthenticationExecutionsByRealm", query="delete from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm"),
- @NamedQuery(name="deleteAuthenticationExecutionsByRealmAndFlow", query="delete from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm and authenticator.parentFlow = :parentFlow"),
-})
public class AuthenticationExecutionEntity {
@Id
@Column(name="ID", length = 36)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
index 402acf1..b9927b9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
@@ -39,10 +39,6 @@ import java.util.Collection;
*/
@Table(name="AUTHENTICATION_FLOW")
@Entity
-@NamedQueries({
- @NamedQuery(name="getAuthenticationFlowsByRealm", query="select flow from AuthenticationFlowEntity flow where flow.realm = :realm"),
- @NamedQuery(name="deleteAuthenticationFlowByRealm", query="delete from AuthenticationFlowEntity flow where flow.realm = :realm")
-})
public class AuthenticationFlowEntity {
@Id
@Column(name="ID", length = 36)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index efbd154..ececf70 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -162,6 +162,10 @@ public class ClientEntity {
@JoinTable(name="CLIENT_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
+ @OneToMany(fetch = FetchType.LAZY)
+ @JoinTable(name="SCOPE_MAPPING", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
+ protected Set<RoleEntity> scopeMapping = new HashSet<>();
+
@ElementCollection
@MapKeyColumn(name="NAME")
@Column(name="VALUE")
@@ -456,6 +460,14 @@ public class ClientEntity {
this.useTemplateMappers = useTemplateMappers;
}
+ public Set<RoleEntity> getScopeMapping() {
+ return scopeMapping;
+ }
+
+ public void setScopeMapping(Set<RoleEntity> scopeMapping) {
+ this.scopeMapping = scopeMapping;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java
index bf141a8..4dda333 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java
@@ -33,12 +33,6 @@ import javax.persistence.Table;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-@NamedQueries({
- @NamedQuery(name="getComponentConfig", query="select attr from ComponentConfigEntity attr where attr.component = :component"),
- @NamedQuery(name="deleteComponentConfigByComponent", query="delete from ComponentConfigEntity attr where attr.component = :component"),
- @NamedQuery(name="deleteComponentConfigByRealm", query="delete from ComponentConfigEntity attr where attr.component IN (select u from ComponentEntity u where u.realm=:realm)"),
- @NamedQuery(name="deleteComponentConfigByParent", query="delete from ComponentConfigEntity attr where attr.component IN (select u from ComponentEntity u where u.parentId=:parentId)"),
-})
@Table(name="COMPONENT_CONFIG")
@Entity
public class ComponentConfigEntity {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java
index 0a7728e..d688f8e 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java
@@ -17,8 +17,12 @@
package org.keycloak.models.jpa.entities;
+import java.util.HashSet;
+import java.util.Set;
+
import javax.persistence.Access;
import javax.persistence.AccessType;
+import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -27,19 +31,12 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
import javax.persistence.Table;
/**
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
*/
-@NamedQueries({
- @NamedQuery(name="getComponents", query="select attr from ComponentEntity attr where attr.realm = :realm"),
- @NamedQuery(name="getComponentsByParentAndType", query="select attr from ComponentEntity attr where attr.realm = :realm and attr.providerType = :providerType and attr.parentId = :parentId"),
- @NamedQuery(name="getComponentByParent", query="select attr from ComponentEntity attr where attr.realm = :realm and attr.parentId = :parentId"),
- @NamedQuery(name="getComponentIdsByParent", query="select attr.id from ComponentEntity attr where attr.realm = :realm and attr.parentId = :parentId"),
- @NamedQuery(name="deleteComponentByRealm", query="delete from ComponentEntity c where c.realm = :realm"),
- @NamedQuery(name="deleteComponentByParent", query="delete from ComponentEntity c where c.parentId = :parentId")
-})
@Entity
@Table(name="COMPONENT")
public class ComponentEntity {
@@ -68,6 +65,9 @@ public class ComponentEntity {
@Column(name="SUB_TYPE")
protected String subType;
+ @OneToMany(fetch = FetchType.LAZY, cascade ={ CascadeType.ALL}, orphanRemoval = true, mappedBy = "component")
+ Set<ComponentConfigEntity> componentConfigs = new HashSet<>();
+
public String getId() {
return id;
}
@@ -124,6 +124,14 @@ public class ComponentEntity {
this.realm = realm;
}
+ public Set<ComponentConfigEntity> getComponentConfigs() {
+ return componentConfigs;
+ }
+
+ public void setComponentConfigs(Set<ComponentConfigEntity> componentConfigs) {
+ this.componentConfigs = componentConfigs;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
index a5dbfc4..b65febd 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
@@ -35,8 +35,6 @@ import javax.persistence.Table;
*/
@NamedQueries({
@NamedQuery(name="getGroupAttributesByNameAndValue", query="select attr from GroupAttributeEntity attr where attr.name = :name and attr.value = :value"),
- @NamedQuery(name="deleteGroupAttributesByGroup", query="delete from GroupAttributeEntity attr where attr.group = :group"),
- @NamedQuery(name="deleteGroupAttributesByRealm", query="delete from GroupAttributeEntity attr where attr.group IN (select u from GroupEntity u where u.realm=:realm)")
})
@Table(name="GROUP_ATTRIBUTE")
@Entity
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
index 6f4c1ec..98b130a 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
@@ -38,13 +38,7 @@ import java.util.Collection;
* @version $Revision: 1 $
*/
@NamedQueries({
- @NamedQuery(name="getAllGroupsByRealm", query="select u from GroupEntity u where u.realm = :realm order by u.name"),
- @NamedQuery(name="getAllGroupIdsByRealm", query="select u.id from GroupEntity u where u.realm.id = :realm order by u.name"),
- @NamedQuery(name="getGroupById", query="select u from GroupEntity u where u.id = :id and u.realm = :realm"),
@NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"),
- @NamedQuery(name="getTopLevelGroupIds", query="select u.id from GroupEntity u where u.parent is null and u.realm.id = :realm"),
- @NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm"),
- @NamedQuery(name="deleteGroupsByRealm", query="delete from GroupEntity u where u.realm = :realm")
})
@Entity
@Table(name="KEYCLOAK_GROUP")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index cc62c8a..13988dc 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -159,6 +159,9 @@ public class RealmEntity {
@JoinTable(name="REALM_DEFAULT_GROUPS", joinColumns = { @JoinColumn(name="REALM_ID")}, inverseJoinColumns = { @JoinColumn(name="GROUP_ID")})
protected Collection<GroupEntity> defaultGroups = new ArrayList<>();
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+ protected Collection<GroupEntity> groups = new ArrayList<>();
+
@Column(name="EVENTS_ENABLED")
protected boolean eventsEnabled;
@Column(name="EVENTS_EXPIRATION")
@@ -199,6 +202,9 @@ public class RealmEntity {
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<AuthenticationFlowEntity> authenticationFlows = new ArrayList<>();
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.ALL}, orphanRemoval = true, mappedBy = "realm")
+ Set<ComponentEntity> components = new HashSet<>();
+
@Column(name="BROWSER_FLOW")
protected String browserFlow;
@@ -426,6 +432,14 @@ public class RealmEntity {
this.defaultGroups = defaultGroups;
}
+ public Collection<GroupEntity> getGroups() {
+ return groups;
+ }
+
+ public void setGroups(Collection<GroupEntity> groups) {
+ this.groups = groups;
+ }
+
public String getPasswordPolicy() {
return passwordPolicy;
}
@@ -623,6 +637,14 @@ public class RealmEntity {
this.authenticationFlows = authenticationFlows;
}
+ public Set<ComponentEntity> getComponents() {
+ return components;
+ }
+
+ public void setComponents(Set<ComponentEntity> components) {
+ this.components = components;
+ }
+
public String getOtpPolicyType() {
return otpPolicyType;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 55e4108..718d19a 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -43,6 +43,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -133,14 +134,6 @@ public class JpaRealmProvider implements RealmProvider {
int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
.setParameter("realm", realm).executeUpdate();
- num = em.createNamedQuery("deleteGroupAttributesByRealm")
- .setParameter("realm", realm).executeUpdate();
- num = em.createNamedQuery("deleteGroupsByRealm")
- .setParameter("realm", realm).executeUpdate();
- num = em.createNamedQuery("deleteComponentConfigByRealm")
- .setParameter("realm", realm).executeUpdate();
- num = em.createNamedQuery("deleteComponentByRealm")
- .setParameter("realm", realm).executeUpdate();
TypedQuery<String> query = em.createNamedQuery("getClientIdsByRealm", String.class);
query.setParameter("realm", realm.getId());
@@ -228,7 +221,6 @@ public class JpaRealmProvider implements RealmProvider {
roleEntity.setClientRole(true);
roleEntity.setRealmId(realm.getId());
em.persist(roleEntity);
- em.flush();
RoleAdapter adapter = new RoleAdapter(session, realm, em, roleEntity);
return adapter;
}
@@ -281,10 +273,11 @@ public class JpaRealmProvider implements RealmProvider {
RoleEntity roleEntity = em.getReference(RoleEntity.class, role.getId());
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
- em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
+ realm.getClients().forEach(c -> c.deleteScopeMapping(role));
em.createNamedQuery("deleteTemplateScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
int val = em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", roleEntity.getId()).executeUpdate();
+ em.flush();
em.remove(roleEntity);
session.getKeycloakSessionFactory().publish(new RoleContainerModel.RoleRemovedEvent() {
@@ -337,27 +330,23 @@ public class JpaRealmProvider implements RealmProvider {
@Override
public List<GroupModel> getGroups(RealmModel realm) {
- List<String> groups = em.createNamedQuery("getAllGroupIdsByRealm", String.class)
- .setParameter("realm", realm.getId()).getResultList();
- if (groups == null) return Collections.EMPTY_LIST;
- List<GroupModel> list = new LinkedList<>();
- for (String id : groups) {
- list.add(session.realms().getGroupById(id, realm));
- }
- return Collections.unmodifiableList(list);
+ RealmEntity ref = em.getReference(RealmEntity.class, realm.getId());
+
+ return ref.getGroups().stream()
+ .map(g -> session.realms().getGroupById(g.getId(), realm))
+ .collect(Collectors.collectingAndThen(
+ Collectors.toList(), Collections::unmodifiableList));
}
@Override
public List<GroupModel> getTopLevelGroups(RealmModel realm) {
- List<String> groups = em.createNamedQuery("getTopLevelGroupIds", String.class)
- .setParameter("realm", realm.getId())
- .getResultList();
- if (groups == null) return Collections.EMPTY_LIST;
- List<GroupModel> list = new LinkedList<>();
- for (String id : groups) {
- list.add(session.realms().getGroupById(id, realm));
- }
- return Collections.unmodifiableList(list);
+ RealmEntity ref = em.getReference(RealmEntity.class, realm.getId());
+
+ return ref.getGroups().stream()
+ .filter(g -> g.getParent() == null)
+ .map(g -> session.realms().getGroupById(g.getId(), realm))
+ .collect(Collectors.collectingAndThen(
+ Collectors.toList(), Collections::unmodifiableList));
}
@Override
@@ -377,9 +366,11 @@ public class JpaRealmProvider implements RealmProvider {
if (!groupEntity.getRealm().getId().equals(realm.getId())) {
return false;
}
- // I don't think we need this as GroupEntity has cascade removal. It causes batch errors if you turn this on.
- // em.createNamedQuery("deleteGroupAttributesByGroup").setParameter("group", groupEntity).executeUpdate();
em.createNamedQuery("deleteGroupRoleMappingsByGroup").setParameter("group", groupEntity).executeUpdate();
+
+ RealmEntity realmEntity = em.getReference(RealmEntity.class, realm.getId());
+ realmEntity.getGroups().remove(groupEntity);
+
em.remove(groupEntity);
return true;
@@ -401,6 +392,7 @@ public class JpaRealmProvider implements RealmProvider {
RealmEntity realmEntity = em.getReference(RealmEntity.class, realm.getId());
groupEntity.setRealm(realmEntity);
em.persist(groupEntity);
+ realmEntity.getGroups().add(groupEntity);
GroupAdapter adapter = new GroupAdapter(realm, em, groupEntity);
return adapter;
@@ -494,9 +486,6 @@ public class JpaRealmProvider implements RealmProvider {
ClientEntity clientEntity = ((ClientAdapter)client).getEntity();
- em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
- em.flush();
-
session.getKeycloakSessionFactory().publish(new RealmModel.ClientRemovedEvent() {
@Override
public ClientModel getClient() {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index b3b4db2..33ca943 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -58,7 +58,6 @@ import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
-import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -68,7 +67,10 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -1375,16 +1377,10 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
@Override
public List<AuthenticationFlowModel> getAuthenticationFlows() {
- TypedQuery<AuthenticationFlowEntity> query = em.createNamedQuery("getAuthenticationFlowsByRealm", AuthenticationFlowEntity.class);
- query.setParameter("realm", realm);
- List<AuthenticationFlowEntity> flows = query.getResultList();
- if (flows.isEmpty()) return Collections.EMPTY_LIST;
- List<AuthenticationFlowModel> models = new LinkedList<>();
- for (AuthenticationFlowEntity entity : flows) {
- AuthenticationFlowModel model = entityToModel(entity);
- models.add(model);
- }
- return Collections.unmodifiableList(models);
+ return realm.getAuthenticationFlows().stream()
+ .map(this::entityToModel)
+ .collect(Collectors.collectingAndThen(
+ Collectors.toList(), Collections::unmodifiableList));
}
@Override
@@ -1461,26 +1457,20 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
entity.setRealm(realm);
realm.getAuthenticationFlows().add(entity);
em.persist(entity);
- em.flush();
model.setId(entity.getId());
return model;
}
@Override
public List<AuthenticationExecutionModel> getAuthenticationExecutions(String flowId) {
- TypedQuery<AuthenticationExecutionEntity> query = em.createNamedQuery("getAuthenticationExecutionsByFlow", AuthenticationExecutionEntity.class);
AuthenticationFlowEntity flow = em.getReference(AuthenticationFlowEntity.class, flowId);
- query.setParameter("realm", realm);
- query.setParameter("parentFlow", flow);
- List<AuthenticationExecutionEntity> queryResult = query.getResultList();
- if (queryResult.isEmpty()) return Collections.EMPTY_LIST;
- List<AuthenticationExecutionModel> executions = new LinkedList<>();
- for (AuthenticationExecutionEntity entity : queryResult) {
- AuthenticationExecutionModel model = entityToModel(entity);
- executions.add(model);
- }
- Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
- return Collections.unmodifiableList(executions);
+
+ return flow.getExecutions().stream()
+ .filter(e -> getId().equals(e.getRealm().getId()))
+ .map(this::entityToModel)
+ .sorted(AuthenticationExecutionModel.ExecutionComparator.SINGLETON)
+ .collect(Collectors.collectingAndThen(
+ Collectors.toList(), Collections::unmodifiableList));
}
public AuthenticationExecutionModel entityToModel(AuthenticationExecutionEntity entity) {
@@ -1519,7 +1509,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
entity.setRealm(realm);
entity.setAutheticatorFlow(model.isAuthenticatorFlow());
em.persist(entity);
- em.flush();
model.setId(entity.getId());
return model;
@@ -1557,7 +1546,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
auth.setConfig(model.getConfig());
realm.getAuthenticatorConfigs().add(auth);
em.persist(auth);
- em.flush();
model.setId(auth.getId());
return model;
}
@@ -1850,12 +1838,14 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
c.setSubType(model.getSubType());
c.setRealm(realm);
em.persist(c);
+ realm.getComponents().add(c);
setConfig(model, c);
model.setId(c.getId());
return model;
}
protected void setConfig(ComponentModel model, ComponentEntity c) {
+ c.getComponentConfigs().clear();
for (String key : model.getConfig().keySet()) {
List<String> vals = model.getConfig().get(key);
if (vals == null) {
@@ -1867,7 +1857,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
config.setName(key);
config.setValue(val);
config.setComponent(c);
- em.persist(config);
+ c.getComponentConfigs().add(config);
}
}
}
@@ -1884,8 +1874,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
c.setProviderType(component.getProviderType());
c.setParentId(component.getParentId());
c.setSubType(component.getSubType());
- em.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate();
- em.flush();
setConfig(component, c);
ComponentUtil.notifyUpdated(session, this, old, component);
@@ -1898,55 +1886,39 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
if (c == null) return;
session.users().preRemove(this, component);
removeComponents(component.getId());
- em.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate();
- em.remove(c);
+ getEntity().getComponents().remove(c);
}
@Override
public void removeComponents(String parentId) {
- TypedQuery<String> query = em.createNamedQuery("getComponentIdsByParent", String.class)
- .setParameter("realm", realm)
- .setParameter("parentId", parentId);
- List<String> results = query.getResultList();
- if (results.isEmpty()) return;
- for (String id : results) {
- session.users().preRemove(this, getComponent(id));
- }
- em.createNamedQuery("deleteComponentConfigByParent").setParameter("parentId", parentId).executeUpdate();
- em.createNamedQuery("deleteComponentByParent").setParameter("parentId", parentId).executeUpdate();
+ Predicate<ComponentEntity> sameParent = c -> Objects.equals(parentId, c.getParentId());
+
+ getEntity().getComponents().stream()
+ .filter(sameParent)
+ .map(this::entityToModel)
+ .forEach(c -> session.users().preRemove(this, c));
+ getEntity().getComponents().removeIf(sameParent);
}
@Override
- public List<ComponentModel> getComponents(String parentId, String providerType) {
+ public List<ComponentModel> getComponents(String parentId, final String providerType) {
if (parentId == null) parentId = getId();
- TypedQuery<ComponentEntity> query = em.createNamedQuery("getComponentsByParentAndType", ComponentEntity.class)
- .setParameter("realm", realm)
- .setParameter("parentId", parentId)
- .setParameter("providerType", providerType);
- List<ComponentEntity> results = query.getResultList();
- List<ComponentModel> rtn = new LinkedList<>();
- for (ComponentEntity c : results) {
- ComponentModel model = entityToModel(c);
- rtn.add(model);
+ final String parent = parentId;
- }
- return rtn;
+ return realm.getComponents().stream()
+ .filter(c -> parent.equals(c.getParentId())
+ && providerType.equals(c.getProviderType()))
+ .map(this::entityToModel)
+ .collect(Collectors.toList());
}
@Override
- public List<ComponentModel> getComponents(String parentId) {
- TypedQuery<ComponentEntity> query = em.createNamedQuery("getComponentsByParent", ComponentEntity.class)
- .setParameter("realm", realm)
- .setParameter("parentId", parentId);
- List<ComponentEntity> results = query.getResultList();
- List<ComponentModel> rtn = new LinkedList<>();
- for (ComponentEntity c : results) {
- ComponentModel model = entityToModel(c);
- rtn.add(model);
-
- }
- return rtn;
+ public List<ComponentModel> getComponents(final String parentId) {
+ return realm.getComponents().stream()
+ .filter(c -> parentId.equals(c.getParentId()))
+ .map(this::entityToModel)
+ .collect(Collectors.toList());
}
protected ComponentModel entityToModel(ComponentEntity c) {
@@ -1958,10 +1930,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
model.setSubType(c.getSubType());
model.setParentId(c.getParentId());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
- TypedQuery<ComponentConfigEntity> configQuery = em.createNamedQuery("getComponentConfig", ComponentConfigEntity.class)
- .setParameter("component", c);
- List<ComponentConfigEntity> configResults = configQuery.getResultList();
- for (ComponentConfigEntity configEntity : configResults) {
+ for (ComponentConfigEntity configEntity : c.getComponentConfigs()) {
config.add(configEntity.getName(), configEntity.getValue());
}
model.setConfig(config);
@@ -1970,16 +1939,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
@Override
public List<ComponentModel> getComponents() {
- TypedQuery<ComponentEntity> query = em.createNamedQuery("getComponents", ComponentEntity.class)
- .setParameter("realm", realm);
- List<ComponentEntity> results = query.getResultList();
- List<ComponentModel> rtn = new LinkedList<>();
- for (ComponentEntity c : results) {
- ComponentModel model = entityToModel(c);
- rtn.add(model);
-
- }
- return rtn;
+ return realm.getComponents().stream().map(this::entityToModel).collect(Collectors.toList());
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
index 0f409a8..7aa1da8 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
@@ -101,7 +101,6 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
if (composite.equals(entity)) return;
}
getEntity().getCompositeRoles().add(entity);
- em.flush();
}
@Override
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
index 51be2fd..b3872b8 100644
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
@@ -24,4 +24,139 @@
<addPrimaryKey columnNames="USER_SESSION_ID,CLIENT_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK3" tableName="OFFLINE_CLIENT_SESSION"/>
</changeSet>
-</databaseChangeLog>
\ No newline at end of file
+ <changeSet author="glavoie@gmail.com" id="3.2.0.idx">
+ <createIndex indexName="IDX_ASSOC_POL_ASSOC_POL_ID" tableName="ASSOCIATED_POLICY">
+ <column name="ASSOCIATED_POLICY_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_AUTH_EXEC_REALM_FLOW" tableName="AUTHENTICATION_EXECUTION">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ <column name="FLOW_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_AUTH_EXEC_FLOW" tableName="AUTHENTICATION_EXECUTION">
+ <column name="FLOW_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_AUTH_FLOW_REALM" tableName="AUTHENTICATION_FLOW">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_AUTH_CONFIG_REALM" tableName="AUTHENTICATOR_CONFIG">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_CLIENT_CLIENT_TEMPL_ID" tableName="CLIENT">
+ <column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_CLIENT_DEF_ROLES_CLIENT" tableName="CLIENT_DEFAULT_ROLES">
+ <column name="CLIENT_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_CLIENT_ID_PROV_MAP_CLIENT" tableName="CLIENT_IDENTITY_PROV_MAPPING">
+ <column name="CLIENT_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_CLIENT_SESSION_SESSION" tableName="CLIENT_SESSION">
+ <column name="SESSION_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_COMPONENT_REALM" tableName="COMPONENT">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_COMPO_CONFIG_COMPO" tableName="COMPONENT_CONFIG">
+ <column name="COMPONENT_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_COMPOSITE" tableName="COMPOSITE_ROLE">
+ <column name="COMPOSITE" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_COMPOSITE_CHILD" tableName="COMPOSITE_ROLE">
+ <column name="CHILD_ROLE" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_CREDENTIAL_ATTR_CRED" tableName="CREDENTIAL_ATTRIBUTE">
+ <column name="CREDENTIAL_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_FED_CRED_ATTR_CRED" tableName="FED_CREDENTIAL_ATTRIBUTE">
+ <column name="CREDENTIAL_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_GROUP_ATTR_GROUP" tableName="GROUP_ATTRIBUTE">
+ <column name="GROUP_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_GROUP_ROLE_MAPP_GROUP" tableName="GROUP_ROLE_MAPPING">
+ <column name="GROUP_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_IDENT_PROV_REALM" tableName="IDENTITY_PROVIDER">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_ID_PROV_MAPP_REALM" tableName="IDENTITY_PROVIDER_MAPPER">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_KEYCLOAK_ROLE_CLIENT" tableName="KEYCLOAK_ROLE">
+ <column name="CLIENT" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_KEYCLOAK_ROLE_REALM" tableName="KEYCLOAK_ROLE">
+ <column name="REALM" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_PROTOCOL_MAPPER_CLIENT" tableName="PROTOCOL_MAPPER">
+ <column name="CLIENT_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_PROTO_MAPP_CLIENT_TEMPL" tableName="PROTOCOL_MAPPER">
+ <column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_REALM_MASTER_ADM_CLI" tableName="REALM">
+ <column name="MASTER_ADMIN_CLIENT" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_REALM_ATTR_REALM" tableName="REALM_ATTRIBUTE">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_REALM_DEF_GRP_REALM" tableName="REALM_DEFAULT_GROUPS">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_REALM_DEF_ROLES_REALM" tableName="REALM_DEFAULT_ROLES">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_REALM_EVT_TYPES_REALM" tableName="REALM_ENABLED_EVENT_TYPES">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_REALM_EVT_LIST_REALM" tableName="REALM_EVENTS_LISTENERS">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_REALM_SUPP_LOCAL_REALM" tableName="REALM_SUPPORTED_LOCALES">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_REDIR_URI_CLIENT" tableName="REDIRECT_URIS">
+ <column name="CLIENT_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_REQ_ACT_PROV_REALM" tableName="REQUIRED_ACTION_PROVIDER">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_RES_POLICY_POLICY" tableName="RESOURCE_POLICY">
+ <column name="POLICY_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_RES_SCOPE_SCOPE" tableName="RESOURCE_SCOPE">
+ <column name="SCOPE_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_RES_SERV_POL_RES_SERV" tableName="RESOURCE_SERVER_POLICY">
+ <column name="RESOURCE_SERVER_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_RES_SRV_RES_RES_SRV" tableName="RESOURCE_SERVER_RESOURCE">
+ <column name="RESOURCE_SERVER_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_RES_SRV_SCOPE_RES_SRV" tableName="RESOURCE_SERVER_SCOPE">
+ <column name="RESOURCE_SERVER_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_SCOPE_MAPPING_ROLE" tableName="SCOPE_MAPPING">
+ <column name="ROLE_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_SCOPE_POLICY_POLICY" tableName="SCOPE_POLICY">
+ <column name="POLICY_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_TEMPL_SCOPE_MAPP_ROLE" tableName="TEMPLATE_SCOPE_MAPPING">
+ <column name="ROLE_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_USR_FED_MAP_FED_PRV" tableName="USER_FEDERATION_MAPPER">
+ <column name="FEDERATION_PROVIDER_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_USR_FED_MAP_REALM" tableName="USER_FEDERATION_MAPPER">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_USR_FED_PRV_REALM" tableName="USER_FEDERATION_PROVIDER">
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ <createIndex indexName="IDX_WEB_ORIG_CLIENT" tableName="WEB_ORIGINS">
+ <column name="CLIENT_ID" type="VARCHAR(36)"/>
+ </createIndex>
+ </changeSet>
+</databaseChangeLog>
diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml
index c93a440..5d3fa81 100755
--- a/model/jpa/src/main/resources/META-INF/persistence.xml
+++ b/model/jpa/src/main/resources/META-INF/persistence.xml
@@ -37,7 +37,6 @@
<class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
<class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
- <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
<class>org.keycloak.models.jpa.entities.IdentityProviderEntity</class>
<class>org.keycloak.models.jpa.entities.IdentityProviderMapperEntity</class>
<class>org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity</class>
pom.xml 4(+2 -2)
diff --git a/pom.xml b/pom.xml
index e18f9da..d5b108d 100755
--- a/pom.xml
+++ b/pom.xml
@@ -64,7 +64,7 @@
<h2.version>1.3.173</h2.version>
<hibernate.entitymanager.version>5.0.7.Final</hibernate.entitymanager.version>
<hibernate.javax.persistence.version>1.0.0.Final</hibernate.javax.persistence.version>
- <infinispan.version>8.1.0.Final</infinispan.version>
+ <infinispan.version>8.2.6.Final</infinispan.version>
<jackson.version>2.5.4</jackson.version>
<javax.mail.version>1.5.5</javax.mail.version>
<jboss.logging.version>3.3.0.Final</jboss.logging.version>
@@ -79,7 +79,7 @@
<sun.istack.version>2.21</sun.istack.version>
<sun.jaxb.version>2.2.11</sun.jaxb.version>
<sun.xsom.version>20140925</sun.xsom.version>
- <undertow.version>1.3.15.Final</undertow.version>
+ <undertow.version>1.4.11.Final</undertow.version>
<xmlsec.version>2.0.5</xmlsec.version>
<!-- Authorization Drools Policy Provider -->
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java b/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
index 7e7daa5..f2fa845 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
@@ -146,7 +146,7 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
}
private boolean valid(String str) {
- return str != null && str.length() > 0;
+ return str != null && ! str.isEmpty();
}
});
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/util/SAMLParserUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/util/SAMLParserUtil.java
index b1ed278..a58cdd3 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/util/SAMLParserUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/util/SAMLParserUtil.java
@@ -308,6 +308,11 @@ public class SAMLParserUtil {
return parseNameIDType(xmlEventReader);
}
} else if (xmlEvent instanceof EndElement) {
+ // consume the end element tag
+ EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
+ String endElementTag = StaxParserUtil.getEndElementName(end);
+ if (! StaxParserUtil.matches(end, JBossSAMLConstants.ATTRIBUTE_VALUE.get()))
+ throw logger.parserUnknownEndElement(endElementTag);
return "";
}
diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
index 713a5bd..2f2157c 100644
--- a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
+++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
@@ -200,4 +200,20 @@ public class SAMLParserTest {
assertThat(parsedObject, instanceOf(EntityDescriptorType.class));
}
}
+
+ @Test
+ public void testEmptyAttributeValue() throws Exception {
+ try (InputStream st = SAMLParserTest.class.getResourceAsStream("KEYCLOAK-4790-Empty-attribute-value.xml")) {
+ Object parsedObject = parser.parse(st);
+ assertThat(parsedObject, instanceOf(ResponseType.class));
+ }
+ }
+
+ @Test
+ public void testEmptyAttributeValueLast() throws Exception {
+ try (InputStream st = SAMLParserTest.class.getResourceAsStream("KEYCLOAK-4790-Empty-attribute-value-last.xml")) {
+ Object parsedObject = parser.parse(st);
+ assertThat(parsedObject, instanceOf(ResponseType.class));
+ }
+ }
}
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/KEYCLOAK-4790-Empty-attribute-value.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/KEYCLOAK-4790-Empty-attribute-value.xml
new file mode 100644
index 0000000..b4b03a1
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/KEYCLOAK-4790-Empty-attribute-value.xml
@@ -0,0 +1,35 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_d9e9e102-f048-48fb-a1a3-b5a82d9cd9c3" Version="2.0"
+ IssueInstant="2017-04-24T12:50:14.645Z" Destination="https://y/auth/realms/administration/broker/saml/endpoint"
+ Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="ID_638a829f-7ad2-408e-b3e5-5f2240010ds7f">
+ <saml:Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://x/</saml:Issuer>
+ <samlp:Status>
+ <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+ </samlp:Status>
+ <Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ID="_0cceed2a-e409-4faa-a411-c647be748f2b" IssueInstant="2017-04-24T12:50:14.645Z" Version="2.0">
+ <Issuer>https://x/</Issuer>
+ <Subject>
+ <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">C=c,OU=ou</NameID>
+ <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+ <SubjectConfirmationData InResponseTo="ID_638a829f-7ad2-408e-b3e5-5f224001057f" NotOnOrAfter="2017-04-24T12:55:14.645Z" Recipient="https://y/auth/realms/administration/broker/saml/endpoint"/>
+ </SubjectConfirmation>
+ </Subject>
+ <Conditions NotBefore="2017-04-24T12:45:14.380Z" NotOnOrAfter="2017-04-24T13:45:14.380Z">
+ <AudienceRestriction>
+ <Audience>https://x/auth/realms/administration</Audience>
+ </AudienceRestriction>
+ </Conditions>
+ <AttributeStatement>
+ <Attribute Name="urn:oid:0.9.2342.19200300.100.1.2" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+ <AttributeValue/>
+ </Attribute>
+ <Attribute Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+ <AttributeValue>aa</AttributeValue>
+ </Attribute>
+ </AttributeStatement>
+ <AuthnStatement AuthnInstant="2017-04-24T12:50:14.037Z" SessionIndex="_0cceed2a-e409-4faa-a411-c647be748f2b">
+ <AuthnContext>
+ <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:X509</AuthnContextClassRef>
+ </AuthnContext>
+ </AuthnStatement>
+ </Assertion>
+</samlp:Response>
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/KEYCLOAK-4790-Empty-attribute-value-last.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/KEYCLOAK-4790-Empty-attribute-value-last.xml
new file mode 100644
index 0000000..efd56df
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/KEYCLOAK-4790-Empty-attribute-value-last.xml
@@ -0,0 +1,35 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_d9e9e102-f048-48fb-a1a3-b5a82d9cd9c3" Version="2.0"
+ IssueInstant="2017-04-24T12:50:14.645Z" Destination="https://y/auth/realms/administration/broker/saml/endpoint"
+ Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="ID_638a829f-7ad2-408e-b3e5-5f2240010ds7f">
+ <saml:Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://x/</saml:Issuer>
+ <samlp:Status>
+ <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+ </samlp:Status>
+ <Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ID="_0cceed2a-e409-4faa-a411-c647be748f2b" IssueInstant="2017-04-24T12:50:14.645Z" Version="2.0">
+ <Issuer>https://x/</Issuer>
+ <Subject>
+ <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">C=c,OU=ou</NameID>
+ <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+ <SubjectConfirmationData InResponseTo="ID_638a829f-7ad2-408e-b3e5-5f224001057f" NotOnOrAfter="2017-04-24T12:55:14.645Z" Recipient="https://y/auth/realms/administration/broker/saml/endpoint"/>
+ </SubjectConfirmation>
+ </Subject>
+ <Conditions NotBefore="2017-04-24T12:45:14.380Z" NotOnOrAfter="2017-04-24T13:45:14.380Z">
+ <AudienceRestriction>
+ <Audience>https://x/auth/realms/administration</Audience>
+ </AudienceRestriction>
+ </Conditions>
+ <AttributeStatement>
+ <Attribute Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+ <AttributeValue>aa</AttributeValue>
+ </Attribute>
+ <Attribute Name="urn:oid:0.9.2342.19200300.100.1.2" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+ <AttributeValue/>
+ </Attribute>
+ </AttributeStatement>
+ <AuthnStatement AuthnInstant="2017-04-24T12:50:14.037Z" SessionIndex="_0cceed2a-e409-4faa-a411-c647be748f2b">
+ <AuthnContext>
+ <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:X509</AuthnContextClassRef>
+ </AuthnContext>
+ </AuthnStatement>
+ </Assertion>
+</samlp:Response>
diff --git a/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java b/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
index 8833c8a..f3367af 100755
--- a/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
+++ b/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
@@ -17,6 +17,7 @@
package org.keycloak.models;
+import org.keycloak.policy.PasswordPolicyConfigException;
import org.keycloak.policy.PasswordPolicyProvider;
import java.io.Serializable;
@@ -68,10 +69,17 @@ public class PasswordPolicy implements Serializable {
PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, key);
if (provider == null) {
- throw new IllegalArgumentException("Unsupported policy");
+ throw new PasswordPolicyConfigException("Password policy not found");
}
- policyConfig.put(key, provider.parseConfig(config));
+ Object o;
+ try {
+ o = provider.parseConfig(config);
+ } catch (PasswordPolicyConfigException e) {
+ throw new ModelException("Invalid config for " + key + ": " + e.getMessage());
+ }
+
+ policyConfig.put(key, o);
}
}
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyConfigException.java b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyConfigException.java
new file mode 100644
index 0000000..8d9a879
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyConfigException.java
@@ -0,0 +1,14 @@
+package org.keycloak.policy;
+
+import org.keycloak.models.ModelException;
+
+/**
+ * Created by st on 23/05/17.
+ */
+public class PasswordPolicyConfigException extends ModelException {
+
+ public PasswordPolicyConfigException(String message) {
+ super(message);
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProvider.java b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProvider.java
index 3f7c3ea..5d54251 100644
--- a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProvider.java
+++ b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProvider.java
@@ -33,4 +33,12 @@ public interface PasswordPolicyProvider extends Provider {
PolicyError validate(String user, String password);
Object parseConfig(String value);
+ default Integer parseInteger(String value, Integer defaultValue) {
+ try {
+ return value != null ? Integer.parseInt(value) : defaultValue;
+ } catch (NumberFormatException e) {
+ throw new PasswordPolicyConfigException("Not a valid number");
+ }
+ }
+
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
index fb6e029..2f5576e 100755
--- a/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
+++ b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowContext.java
@@ -58,7 +58,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
/**
- * ClientSessionModel attached to this flow
+ * AuthenticationSessionModel attached to this flow
*
* @return
*/
@@ -74,7 +74,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
/**
* Get the action URL for the required action.
*
- * @param code client session access code
+ * @param code authentication session access code
* @return
*/
URI getActionUrl(String code);
@@ -114,7 +114,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
void resetFlow(Runnable afterResetListener);
/**
- * Fork the current flow. The client session will be cloned and set to point at the realm's browser login flow. The Response will be the result
+ * Fork the current flow. The authentication session will be cloned and set to point at the realm's browser login flow. The Response will be the result
* of this fork. The previous flow will still be set at the current execution. This is used by reset password when it sends an email.
* It sends an email linking to the current flow and redirects the browser to a new browser login flow.
*
@@ -125,7 +125,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
void fork();
/**
- * Fork the current flow. The client session will be cloned and set to point at the realm's browser login flow. The Response will be the result
+ * Fork the current flow. The authentication session will be cloned and set to point at the realm's browser login flow. The Response will be the result
* of this fork. The previous flow will still be set at the current execution. This is used by reset password when it sends an email.
* It sends an email linking to the current flow and redirects the browser to a new browser login flow.
*
@@ -135,7 +135,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
*/
void forkWithSuccessMessage(FormMessage message);
/**
- * Fork the current flow. The client session will be cloned and set to point at the realm's browser login flow. The Response will be the result
+ * Fork the current flow. The authentication session will be cloned and set to point at the realm's browser login flow. The Response will be the result
* of this fork. The previous flow will still be set at the current execution. This is used by reset password when it sends an email.
* It sends an email linking to the current flow and redirects the browser to a new browser login flow.
*
diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/FlowStatus.java b/server-spi-private/src/main/java/org/keycloak/authentication/FlowStatus.java
index 3ad8ef0..4037720 100755
--- a/server-spi-private/src/main/java/org/keycloak/authentication/FlowStatus.java
+++ b/server-spi-private/src/main/java/org/keycloak/authentication/FlowStatus.java
@@ -62,7 +62,7 @@ public enum FlowStatus {
ATTEMPTED,
/**
- * This flow is being forked. The current client session is being cloned, reset, and redirected to browser login.
+ * This flow is being forked. The current authentication session is being cloned, reset, and redirected to browser login.
*
*/
FORK,
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
index ba8276f..f8a810e 100644
--- a/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
@@ -17,6 +17,7 @@
package org.keycloak.broker.provider;
import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.broker.provider.util.IdentityBrokerState;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.sessions.AuthenticationSessionModel;
@@ -30,13 +31,13 @@ public class AuthenticationRequest {
private final KeycloakSession session;
private final UriInfo uriInfo;
- private final String state;
+ private final IdentityBrokerState state;
private final HttpRequest httpRequest;
private final RealmModel realm;
private final String redirectUri;
private final AuthenticationSessionModel authSession;
- public AuthenticationRequest(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
+ public AuthenticationRequest(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, HttpRequest httpRequest, UriInfo uriInfo, IdentityBrokerState state, String redirectUri) {
this.session = session;
this.realm = realm;
this.httpRequest = httpRequest;
@@ -54,7 +55,7 @@ public class AuthenticationRequest {
return this.uriInfo;
}
- public String getState() {
+ public IdentityBrokerState getState() {
return this.state;
}
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java
new file mode 100644
index 0000000..c44b4c4
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java
@@ -0,0 +1,90 @@
+/*
+ * 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.broker.provider.util;
+
+import java.util.regex.Pattern;
+
+/**
+ * Encapsulates parsing logic related to state passed to identity provider in "state" (or RelayState) parameter
+ *
+ * Not Thread-safe
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdentityBrokerState {
+
+ private String decodedState;
+ private String clientId;
+ private String encodedState;
+
+ private IdentityBrokerState() {
+ }
+
+ public static IdentityBrokerState decoded(String decodedState, String clientId) {
+ IdentityBrokerState state = new IdentityBrokerState();
+ state.decodedState = decodedState;
+ state.clientId = clientId;
+ return state;
+ }
+
+ public static IdentityBrokerState encoded(String encodedState) {
+ IdentityBrokerState state = new IdentityBrokerState();
+ state.encodedState = encodedState;
+ return state;
+ }
+
+
+ public String getDecodedState() {
+ if (decodedState == null) {
+ decode();
+ }
+ return decodedState;
+ }
+
+ public String getClientId() {
+ if (decodedState == null) {
+ decode();
+ }
+ return clientId;
+ }
+
+ public String getEncodedState() {
+ if (encodedState == null) {
+ encode();
+ }
+ return encodedState;
+ }
+
+
+ private void decode() {
+ String[] decoded = DOT.split(encodedState, 0);
+ decodedState = decoded[0];
+ if (decoded.length > 0) {
+ clientId = decoded[1];
+ }
+ }
+
+
+ private void encode() {
+ encodedState = decodedState + "." + clientId;
+ }
+
+ private static final Pattern DOT = Pattern.compile("\\.");
+
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
index 32195ef..ac435e3 100755
--- a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
@@ -22,7 +22,6 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.provider.Provider;
-import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
diff --git a/server-spi-private/src/main/java/org/keycloak/models/Constants.java b/server-spi-private/src/main/java/org/keycloak/models/Constants.java
index 260ac1d..40f9081 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/Constants.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/Constants.java
@@ -52,8 +52,12 @@ public interface Constants {
int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
+ String EXECUTION = "execution";
+ String CLIENT_ID = "client_id";
String KEY = "key";
+ String SKIP_LINK = "skipLink";
+
// Prefix for user attributes used in various "context"data maps
String USER_ATTRIBUTES_PREFIX = "user.attributes.";
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
index 98c1ce3..1d41f0d 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
@@ -54,7 +54,7 @@ public class ComponentUtil {
return getComponentFactory(session, component.getProviderType(), component.getProviderId());
}
- private static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, String providerType, String providerId) {
+ public static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, String providerType, String providerId) {
try {
ComponentFactory componentFactory = getComponentFactory(session, providerType, providerId);
List<ProviderConfigProperty> l = componentFactory.getConfigProperties();
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 43e8ef8..6637bd8 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
@@ -872,8 +872,6 @@ public class ModelToRepresentation {
return scope;
}).collect(Collectors.toSet()));
- resource.setTypedScopes(new ArrayList<>());
-
if (resource.getType() != null) {
ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore();
for (Resource typed : resourceStore.findByType(resource.getType(), resourceServer.getId())) {
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 7e528fb..e102d3d 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
@@ -151,6 +151,8 @@ public class RepresentationToModel {
if (rep.getMaxDeltaTimeSeconds() != null) newRealm.setMaxDeltaTimeSeconds(rep.getMaxDeltaTimeSeconds());
if (rep.getFailureFactor() != null) newRealm.setFailureFactor(rep.getFailureFactor());
if (rep.isEventsEnabled() != null) newRealm.setEventsEnabled(rep.isEventsEnabled());
+ if (rep.getEnabledEventTypes() != null)
+ newRealm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
if (rep.getEventsExpiration() != null) newRealm.setEventsExpiration(rep.getEventsExpiration());
if (rep.getEventsListeners() != null) newRealm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
if (rep.isAdminEventsEnabled() != null) newRealm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
index 26f5adc..5bdd018 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
@@ -17,15 +17,18 @@
package org.keycloak.models.utils;
+import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ComponentExportRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -70,4 +73,86 @@ public class StripSecretsUtils {
return rep;
}
+ public static RealmRepresentation stripForExport(KeycloakSession session, RealmRepresentation rep) {
+ strip(rep);
+
+ List<ClientRepresentation> clients = rep.getClients();
+ if (clients != null) {
+ for (ClientRepresentation c : clients) {
+ strip(c);
+ }
+ }
+ List<IdentityProviderRepresentation> providers = rep.getIdentityProviders();
+ if (providers != null) {
+ for (IdentityProviderRepresentation r : providers) {
+ strip(r);
+ }
+ }
+
+ MultivaluedHashMap<String, ComponentExportRepresentation> components = rep.getComponents();
+ if (components != null) {
+ for (Map.Entry<String, List<ComponentExportRepresentation>> ent : components.entrySet()) {
+ for (ComponentExportRepresentation c : ent.getValue()) {
+ strip(session, ent.getKey(), c);
+ }
+ }
+ }
+
+ List<UserRepresentation> users = rep.getUsers();
+ if (users != null) {
+ for (UserRepresentation u: users) {
+ strip(u);
+ }
+ }
+
+ users = rep.getFederatedUsers();
+ if (users != null) {
+ for (UserRepresentation u: users) {
+ strip(u);
+ }
+ }
+
+ return rep;
+ }
+
+ public static UserRepresentation strip(UserRepresentation user) {
+ user.setCredentials(null);
+ return user;
+ }
+
+ public static ClientRepresentation strip(ClientRepresentation rep) {
+ if (rep.getSecret() != null) {
+ rep.setSecret(ComponentRepresentation.SECRET_VALUE);
+ }
+ return rep;
+ }
+
+ public static ComponentExportRepresentation strip(KeycloakSession session, String providerType, ComponentExportRepresentation rep) {
+ Map<String, ProviderConfigProperty> configProperties = ComponentUtil.getComponentConfigProperties(session, providerType, rep.getProviderId());
+ if (rep.getConfig() == null) {
+ return rep;
+ }
+
+ Iterator<Map.Entry<String, List<String>>> itr = rep.getConfig().entrySet().iterator();
+ while (itr.hasNext()) {
+ Map.Entry<String, List<String>> next = itr.next();
+ ProviderConfigProperty configProperty = configProperties.get(next.getKey());
+ if (configProperty != null) {
+ if (configProperty.isSecret()) {
+ next.setValue(Collections.singletonList(ComponentRepresentation.SECRET_VALUE));
+ }
+ } else {
+ itr.remove();
+ }
+ }
+
+ MultivaluedHashMap<String, ComponentExportRepresentation> sub = rep.getSubComponents();
+ for (Map.Entry<String, List<ComponentExportRepresentation>> ent: sub.entrySet()) {
+ for (ComponentExportRepresentation c: ent.getValue()) {
+ strip(session, ent.getKey(), c);
+ }
+ }
+ return rep;
+ }
+
}
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
index c1c6218..194059b 100644
--- a/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
@@ -91,7 +91,7 @@ public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolic
String providerId = value != null && value.length() > 0 ? value : PasswordPolicy.HASH_ALGORITHM_DEFAULT;
PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, providerId);
if (provider == null) {
- throw new ModelException("Password hashing provider not found");
+ throw new PasswordPolicyConfigException("Password hashing provider not found");
}
return providerId;
}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
index 004d540..fe7c9df 100644
--- a/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
@@ -73,7 +73,7 @@ public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
@Override
public Object parseConfig(String value) {
- return value != null ? Integer.parseInt(value) : HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE;
+ return parseInteger(value, HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE);
}
@Override
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java
index 5ba71fe..82a7029 100644
--- a/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java
@@ -47,7 +47,7 @@ public class LengthPasswordPolicyProvider implements PasswordPolicyProvider {
@Override
public Object parseConfig(String value) {
- return value != null ? Integer.parseInt(value) : 8;
+ return parseInteger(value, 8);
}
@Override
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java
index f080d00..b1fb3f4 100644
--- a/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java
@@ -53,7 +53,7 @@ public class LowerCasePasswordPolicyProvider implements PasswordPolicyProvider {
@Override
public Object parseConfig(String value) {
- return value != null ? Integer.parseInt(value) : 1;
+ return parseInteger(value, 1);
}
@Override
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java
index 52c83b8..cfcfbc5 100644
--- a/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java
@@ -23,6 +23,7 @@ import org.keycloak.models.UserModel;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -55,9 +56,13 @@ public class RegexPatternsPasswordPolicyProvider implements PasswordPolicyProvid
@Override
public Object parseConfig(String value) {
if (value == null) {
- throw new IllegalArgumentException("Config required");
+ throw new PasswordPolicyConfigException("Config required");
+ }
+ try {
+ return Pattern.compile(value);
+ } catch (PatternSyntaxException e) {
+ throw new PasswordPolicyConfigException("Not a valid regular expression");
}
- return Pattern.compile(value);
}
@Override
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java
index fa85137..7d2cfe8 100644
--- a/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java
@@ -53,7 +53,7 @@ public class SpecialCharsPasswordPolicyProvider implements PasswordPolicyProvide
@Override
public Object parseConfig(String value) {
- return value != null ? Integer.parseInt(value) : 1;
+ return parseInteger(value, 1);
}
@Override
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java
index 16ac1ef..f34f92f 100644
--- a/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java
@@ -53,7 +53,7 @@ public class UpperCasePasswordPolicyProvider implements PasswordPolicyProvider {
@Override
public Object parseConfig(String value) {
- return value != null ? Integer.parseInt(value) : 1;
+ return parseInteger(value, 1);
}
@Override
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java b/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java
index a55c587..1550a8d 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java
@@ -26,7 +26,6 @@ import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.sessions.AuthenticationSessionModel;
-import java.util.function.Function;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilderException;
import javax.ws.rs.core.UriInfo;
@@ -45,7 +44,7 @@ public class ActionTokenContext<T extends JsonWebToken> {
@FunctionalInterface
public interface ProcessBrokerFlow {
- Response brokerLoginFlow(String code, String execution, String flowPath);
+ Response brokerLoginFlow(String code, String execution, String clientId, String flowPath);
};
private final KeycloakSession session;
@@ -113,7 +112,7 @@ public class ActionTokenContext<T extends JsonWebToken> {
ClientModel client = realm.getClientByClientId(clientId == null ? Constants.ACCOUNT_MANAGEMENT_CLIENT_ID : clientId);
authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, true);
- authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+ authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
authSession.setRedirectUri(redirectUri);
@@ -158,6 +157,7 @@ public class ActionTokenContext<T extends JsonWebToken> {
}
public Response brokerFlow(String code, String flowPath) {
- return processBrokerFlow.brokerLoginFlow(code, getExecutionId(), flowPath);
+ ClientModel client = authenticationSession.getClient();
+ return processBrokerFlow.brokerLoginFlow(code, getExecutionId(), client.getClientId(), flowPath);
}
}
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java
index 389441e..bd56eea 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java
@@ -23,6 +23,7 @@ import org.keycloak.authentication.actiontoken.*;
import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticator;
import org.keycloak.events.*;
import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.messages.Messages;
@@ -86,7 +87,7 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
return tokenContext.getSession().getProvider(LoginFormsProvider.class)
.setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, token.getIdentityProviderAlias(), token.getIdentityProviderUsername())
- .setAttribute("skipLink", true)
+ .setAttribute(Constants.SKIP_LINK, true)
.createInfoPage();
}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 2427091..23d06e3 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -33,6 +33,7 @@ import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -485,15 +486,17 @@ public class AuthenticationProcessor {
return LoginActionsService.loginActionsBaseUrl(getUriInfo())
.path(AuthenticationProcessor.this.flowPath)
.queryParam(OAuth2Constants.CODE, code)
- .queryParam("execution", getExecution().getId())
+ .queryParam(Constants.EXECUTION, getExecution().getId())
+ .queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
.build(getRealm().getName());
}
@Override
public URI getActionTokenUrl(String tokenString) {
return LoginActionsService.actionTokenProcessor(getUriInfo())
- .queryParam("key", tokenString)
- .queryParam("execution", getExecution().getId())
+ .queryParam(Constants.KEY, tokenString)
+ .queryParam(Constants.EXECUTION, getExecution().getId())
+ .queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
.build(getRealm().getName());
}
@@ -501,7 +504,8 @@ public class AuthenticationProcessor {
public URI getRefreshExecutionUrl() {
return LoginActionsService.loginActionsBaseUrl(getUriInfo())
.path(AuthenticationProcessor.this.flowPath)
- .queryParam("execution", getExecution().getId())
+ .queryParam(Constants.EXECUTION, getExecution().getId())
+ .queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
.build(getRealm().getName());
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
index a2a9b3a..7189b95 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -132,7 +132,10 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias()
);
UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
- String link = builder.queryParam("execution", context.getExecution().getId()).build(realm.getName()).toString();
+ String link = builder
+ .queryParam(Constants.EXECUTION, context.getExecution().getId())
+ .queryParam(Constants.CLIENT_ID, context.getExecution().getId())
+ .build(realm.getName()).toString();
long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs);
try {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
index cb31e8d..b255ace 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
@@ -64,8 +64,9 @@ public class IdentityProviderAuthenticator implements Authenticator {
for (IdentityProviderModel identityProvider : identityProviders) {
if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) {
String accessCode = new ClientSessionCode<>(context.getSession(), context.getRealm(), context.getAuthenticationSession()).getCode();
+ String clientId = context.getAuthenticationSession().getClient().getClientId();
Response response = Response.seeOther(
- Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode))
+ Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode, clientId))
.build();
LOG.debugf("Redirecting to %s", providerId);
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index 955879f..82c12ec 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -24,6 +24,8 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -247,9 +249,11 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
}
public URI getActionUrl(String executionId, String code) {
+ ClientModel client = processor.getAuthenticationSession().getClient();
return LoginActionsService.registrationFormProcessor(processor.getUriInfo())
.queryParam(OAuth2Constants.CODE, code)
- .queryParam("execution", executionId)
+ .queryParam(Constants.EXECUTION, executionId)
+ .queryParam(Constants.CLIENT_ID, client.getClientId())
.build(processor.getRealm().getName());
}
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java
index cb60861..72b602f 100755
--- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPage.java
@@ -36,7 +36,6 @@ import java.util.List;
*/
public class RegistrationPage implements FormAuthenticator, FormAuthenticatorFactory {
- public static final String EXECUTION = "execution";
public static final String FIELD_PASSWORD_CONFIRM = "password-confirm";
public static final String FIELD_PASSWORD = "password";
public static final String FIELD_EMAIL = "email";
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 87b3403..1d9475a 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -23,6 +23,8 @@ import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Time;
import org.keycloak.events.EventBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -132,9 +134,11 @@ public class RequiredActionContextResult implements RequiredActionContext {
@Override
public URI getActionUrl(String code) {
+ ClientModel client = authenticationSession.getClient();
return LoginActionsService.requiredActionProcessor(getUriInfo())
.queryParam(OAuth2Constants.CODE, code)
- .queryParam("execution", factory.getId())
+ .queryParam(Constants.EXECUTION, factory.getId())
+ .queryParam(Constants.CLIENT_ID, client.getClientId())
.build(getRealm().getName());
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
index e7f4633..fbcffd4 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
@@ -39,6 +39,18 @@ public class PermissionService extends PolicyService {
}
@Override
+ protected PolicyTypeService doCreatePolicyTypeResource(String type) {
+ return new PolicyTypeService(type, resourceServer, authorization, auth) {
+ @Override
+ protected List<Object> doSearch(Integer firstResult, Integer maxResult, Map<String, String[]> filters) {
+ filters.put("permission", new String[] {Boolean.TRUE.toString()});
+ filters.put("type", new String[] {type});
+ return super.doSearch(firstResult, maxResult, filters);
+ }
+ };
+ }
+
+ @Override
protected List<Object> doSearch(Integer firstResult, Integer maxResult, Map<String, String[]> filters) {
filters.put("permission", new String[] {Boolean.TRUE.toString()});
return super.doSearch(firstResult, maxResult, filters);
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
index 6ebed11..ccb0d23 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
@@ -75,7 +75,7 @@ public class PolicyService {
PolicyProviderFactory providerFactory = getPolicyProviderFactory(type);
if (providerFactory != null) {
- return new PolicyTypeService(type, resourceServer, authorization, auth);
+ return doCreatePolicyTypeResource(type);
}
Policy policy = authorization.getStoreFactory().getPolicyStore().findById(type, resourceServer.getId());
@@ -83,6 +83,10 @@ public class PolicyService {
return doCreatePolicyResource(policy);
}
+ protected PolicyTypeService doCreatePolicyTypeResource(String type) {
+ return new PolicyTypeService(type, resourceServer, authorization, auth);
+ }
+
protected Object doCreatePolicyResource(Policy policy) {
return new PolicyResourceService(policy, resourceServer, authorization, auth);
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
index d6868c5..25877e0 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
@@ -17,6 +17,8 @@
package org.keycloak.authorization.admin;
import java.io.IOException;
+import java.util.List;
+import java.util.Map;
import javax.ws.rs.Path;
@@ -88,4 +90,10 @@ public class PolicyTypeService extends PolicyService {
PolicyProviderFactory providerFactory = authorization.getProviderFactory(policy.getType());
return ModelToRepresentation.toRepresentation(policy, providerFactory.getRepresentationType(), authorization);
}
+
+ @Override
+ protected List<Object> doSearch(Integer firstResult, Integer maxResult, Map<String, String[]> filters) {
+ filters.put("type", new String[] {type});
+ return super.doSearch(firstResult, maxResult, filters);
+ }
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
index c424725..4b59450 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
@@ -19,12 +19,10 @@ package org.keycloak.authorization.admin.representation;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision;
import org.keycloak.authorization.common.KeycloakIdentity;
-import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.util.Permissions;
-import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.authorization.DecisionEffect;
import org.keycloak.representations.idm.authorization.PolicyEvaluationResponse;
@@ -38,7 +36,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
+import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -128,37 +126,8 @@ public class PolicyEvaluationResponseBuilder {
List<ScopeRepresentation> scopes = result.getScopes();
- if (scopes == null) {
- scopes = new ArrayList<>();
- result.setScopes(scopes);
- }
-
- List<ScopeRepresentation> currentScopes = evaluationResultRepresentation.getScopes();
-
- if (currentScopes != null) {
- List<ScopeRepresentation> allowedScopes = result.getAllowedScopes();
- for (ScopeRepresentation scope : currentScopes) {
- if (!scopes.contains(scope)) {
- scopes.add(scope);
- }
- if (evaluationResultRepresentation.getStatus().equals(Decision.Effect.PERMIT)) {
- if (!allowedScopes.contains(scope)) {
- allowedScopes.add(scope);
- }
- } else {
- evaluationResultRepresentation.getPolicies().forEach(new Consumer<PolicyEvaluationResponse.PolicyResultRepresentation>() {
- @Override
- public void accept(PolicyEvaluationResponse.PolicyResultRepresentation policyResultRepresentation) {
- if (policyResultRepresentation.getStatus().equals(Decision.Effect.PERMIT)) {
- if (!allowedScopes.contains(scope)) {
- allowedScopes.add(scope);
- }
- }
- }
- });
- }
- }
- result.setAllowedScopes(allowedScopes);
+ if (DecisionEffect.PERMIT.equals(result.getStatus())) {
+ result.setAllowedScopes(scopes);
}
if (resource.getId() != null) {
@@ -176,19 +145,7 @@ public class PolicyEvaluationResponseBuilder {
for (PolicyEvaluationResponse.PolicyResultRepresentation policy : new ArrayList<>(evaluationResultRepresentation.getPolicies())) {
if (!policies.contains(policy)) {
policies.add(policy);
- } else {
- policy = policies.get(policies.indexOf(policy));
}
-
- if (policy.getStatus().equals(Decision.Effect.DENY)) {
- Policy policyModel = authorization.getStoreFactory().getPolicyStore().findById(policy.getPolicy().getId(), resourceServer.getId());
- for (ScopeRepresentation scope : policyModel.getScopes().stream().map(scopeModel -> ModelToRepresentation.toRepresentation(scopeModel, authorization)).collect(Collectors.toList())) {
- if (!policy.getScopes().contains(scope) && policyModel.getScopes().stream().filter(policyScope -> policyScope.getId().equals(scope.getId())).findFirst().isPresent()) {
- result.getAllowedScopes().remove(scope);
- policy.getScopes().add(scope);
- }
- }
- } else {}
}
});
@@ -207,12 +164,19 @@ public class PolicyEvaluationResponseBuilder {
representation.setType(policy.getPolicy().getType());
representation.setDecisionStrategy(policy.getPolicy().getDecisionStrategy());
+ representation.setResources(policy.getPolicy().getResources().stream().map(resource -> resource.getName()).collect(Collectors.toSet()));
+
+ Set<String> scopeNames = policy.getPolicy().getScopes().stream().map(scope -> scope.getName()).collect(Collectors.toSet());
+
+ representation.setScopes(scopeNames);
+
policyResultRep.setPolicy(representation);
+
if (policy.getStatus() == Decision.Effect.DENY) {
policyResultRep.setStatus(DecisionEffect.DENY);
+ policyResultRep.setScopes(representation.getScopes());
} else {
policyResultRep.setStatus(DecisionEffect.PERMIT);
-
}
policyResultRep.setAssociatedPolicies(policy.getAssociatedPolicies().stream().map(result -> toRepresentation(result, authorization)).collect(Collectors.toList()));
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
index 3a9337e..875a0e0 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -65,6 +65,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -238,10 +239,7 @@ public class AuthorizationTokenService {
if ("$KC_SCOPE_PERMISSION".equals(key)) {
ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore();
- List<Scope> scopes = entry.getValue().stream().map(scopeName -> {
- Scope byName = scopeStore.findByName(scopeName, resourceServer.getId());
- return byName;
- }).collect(Collectors.toList());
+ List<Scope> scopes = entry.getValue().stream().map(scopeName -> scopeStore.findByName(scopeName, resourceServer.getId())).filter(scope -> Objects.nonNull(scope)).collect(Collectors.toList());
return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream();
} else {
Resource entryResource = storeFactory.getResourceStore().findById(key, resourceServer.getId());
diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
index 463ff0b..71058ac 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
@@ -23,8 +23,10 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -319,10 +321,7 @@ public class EntitlementService {
if ("$KC_SCOPE_PERMISSION".equals(key)) {
ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore();
- List<Scope> scopes = entry.getValue().stream().map(scopeName -> {
- Scope byName = scopeStore.findByName(scopeName, resourceServer.getId());
- return byName;
- }).collect(Collectors.toList());
+ List<Scope> scopes = entry.getValue().stream().map(scopeName -> scopeStore.findByName(scopeName, resourceServer.getId())).filter(scope -> Objects.nonNull(scope)).collect(Collectors.toList());
return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream();
} else {
Resource entryResource = storeFactory.getResourceStore().findById(key, resourceServer.getId());
diff --git a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
index 339747a..f4a877e 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -155,7 +155,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
return UriBuilder.fromUri(getConfig().getAuthorizationUrl())
.queryParam(OAUTH2_PARAMETER_SCOPE, getConfig().getDefaultScope())
- .queryParam(OAUTH2_PARAMETER_STATE, request.getState())
+ .queryParam(OAUTH2_PARAMETER_STATE, request.getState().getEncodedState())
.queryParam(OAUTH2_PARAMETER_RESPONSE_TYPE, "code")
.queryParam(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
.queryParam(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri());
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java b/services/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java
index 78459e8..508fcbd 100644
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java
@@ -21,6 +21,7 @@ import org.keycloak.broker.provider.DefaultDataMarshaller;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.StaxUtil;
@@ -66,7 +67,7 @@ public class SAMLDataMarshaller extends DefaultDataMarshaller {
throw new RuntimeException(pe);
}
- return new String(bos.toByteArray());
+ return new String(bos.toByteArray(), GeneralConstants.SAML_CHARSET);
} else {
return super.serialize(obj);
}
@@ -79,12 +80,12 @@ public class SAMLDataMarshaller extends DefaultDataMarshaller {
try {
if (clazz.equals(ResponseType.class) || clazz.equals(AssertionType.class)) {
- byte[] bytes = xmlString.getBytes();
+ byte[] bytes = xmlString.getBytes(GeneralConstants.SAML_CHARSET);
InputStream is = new ByteArrayInputStream(bytes);
Object respType = new SAMLParser().parse(is);
return clazz.cast(respType);
} else if (clazz.equals(AuthnStatementType.class)) {
- byte[] bytes = xmlString.getBytes();
+ byte[] bytes = xmlString.getBytes(GeneralConstants.SAML_CHARSET);
InputStream is = new ByteArrayInputStream(bytes);
XMLEventReader xmlEventReader = new SAMLParser().createEventReader(is);
AuthnStatementType authnStatement = SAMLParserUtil.parseAuthnStatement(xmlEventReader);
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 51d6eb8..886ee4d 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -100,7 +100,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
.protocolBinding(protocolBinding)
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
- .relayState(request.getState());
+ .relayState(request.getState().getEncodedState());
boolean postBinding = getConfig().isPostBindingAuthnRequest();
if (getConfig().isWantAuthnRequestsSigned()) {
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportOptions.java b/services/src/main/java/org/keycloak/exportimport/util/ExportOptions.java
new file mode 100644
index 0000000..b23f756
--- /dev/null
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportOptions.java
@@ -0,0 +1,44 @@
+package org.keycloak.exportimport.util;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class ExportOptions {
+
+ private boolean usersIncluded = true;
+ private boolean clientsIncluded = true;
+ private boolean groupsAndRolesIncluded = true;
+
+ public ExportOptions() {
+ }
+
+ public ExportOptions(boolean users, boolean clients, boolean groupsAndRoles) {
+ usersIncluded = users;
+ clientsIncluded = clients;
+ groupsAndRolesIncluded = groupsAndRoles;
+ }
+
+ public boolean isUsersIncluded() {
+ return usersIncluded;
+ }
+
+ public boolean isClientsIncluded() {
+ return clientsIncluded;
+ }
+
+ public boolean isGroupsAndRolesIncluded() {
+ return groupsAndRolesIncluded;
+ }
+
+ public void setUsersIncluded(boolean value) {
+ usersIncluded = value;
+ }
+
+ public void setClientsIncluded(boolean value) {
+ clientsIncluded = value;
+ }
+
+ public void setGroupsAndRolesIncluded(boolean value) {
+ groupsAndRolesIncluded = value;
+ }
+}
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 51ed405..7eaf246 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -23,6 +23,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@@ -84,7 +85,17 @@ import com.fasterxml.jackson.databind.SerializationFeature;
public class ExportUtils {
public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, boolean includeUsers) {
- RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm, true);
+ ExportOptions opts = new ExportOptions(false, true, true);
+ if (includeUsers) {
+ opts.setUsersIncluded(true);
+ }
+ return exportRealm(session, realm, opts);
+ }
+
+ public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, ExportOptions options) {
+ RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm, false);
+ ModelToRepresentation.exportAuthenticationFlows(realm, rep);
+ ModelToRepresentation.exportRequiredActions(realm, rep);
// Project/product version
rep.setKeycloakVersion(Version.VERSION);
@@ -99,73 +110,87 @@ public class ExportUtils {
rep.setClientTemplates(templateReps);
// Clients
- List<ClientModel> clients = realm.getClients();
- List<ClientRepresentation> clientReps = new ArrayList<>();
- for (ClientModel app : clients) {
- ClientRepresentation clientRep = exportClient(session, app);
- clientReps.add(clientRep);
+ List<ClientModel> clients = Collections.emptyList();
+
+ if (options.isClientsIncluded()) {
+ clients = realm.getClients();
+ List<ClientRepresentation> clientReps = new ArrayList<>();
+ for (ClientModel app : clients) {
+ ClientRepresentation clientRep = exportClient(session, app);
+ clientReps.add(clientRep);
+ }
+ rep.setClients(clientReps);
}
- rep.setClients(clientReps);
- // Roles
- List<RoleRepresentation> realmRoleReps = null;
- Map<String, List<RoleRepresentation>> clientRolesReps = new HashMap<>();
+ // Groups and Roles
+ if (options.isGroupsAndRolesIncluded()) {
+ ModelToRepresentation.exportGroups(realm, rep);
- Set<RoleModel> realmRoles = realm.getRoles();
- if (realmRoles != null && realmRoles.size() > 0) {
- realmRoleReps = exportRoles(realmRoles);
- }
- for (ClientModel client : clients) {
- Set<RoleModel> currentAppRoles = client.getRoles();
- List<RoleRepresentation> currentAppRoleReps = exportRoles(currentAppRoles);
- clientRolesReps.put(client.getClientId(), currentAppRoleReps);
- }
+ List<RoleRepresentation> realmRoleReps = null;
+ Map<String, List<RoleRepresentation>> clientRolesReps = new HashMap<>();
- RolesRepresentation rolesRep = new RolesRepresentation();
- if (realmRoleReps != null) {
- rolesRep.setRealm(realmRoleReps);
- }
- if (clientRolesReps.size() > 0) {
- rolesRep.setClient(clientRolesReps);
+ Set<RoleModel> realmRoles = realm.getRoles();
+ if (realmRoles != null && realmRoles.size() > 0) {
+ realmRoleReps = exportRoles(realmRoles);
+ }
+
+ RolesRepresentation rolesRep = new RolesRepresentation();
+ if (realmRoleReps != null) {
+ rolesRep.setRealm(realmRoleReps);
+ }
+
+ if (options.isClientsIncluded()) {
+ for (ClientModel client : clients) {
+ Set<RoleModel> currentAppRoles = client.getRoles();
+ List<RoleRepresentation> currentAppRoleReps = exportRoles(currentAppRoles);
+ clientRolesReps.put(client.getClientId(), currentAppRoleReps);
+ }
+ if (clientRolesReps.size() > 0) {
+ rolesRep.setClient(clientRolesReps);
+ }
+ }
+ rep.setRoles(rolesRep);
}
- rep.setRoles(rolesRep);
// Scopes
- List<ClientModel> allClients = new ArrayList<>(clients);
Map<String, List<ScopeMappingRepresentation>> clientScopeReps = new HashMap<>();
- // Scopes of clients
- for (ClientModel client : allClients) {
- Set<RoleModel> clientScopes = client.getScopeMappings();
- ScopeMappingRepresentation scopeMappingRep = null;
- for (RoleModel scope : clientScopes) {
- if (scope.getContainer() instanceof RealmModel) {
- if (scopeMappingRep == null) {
- scopeMappingRep = rep.clientScopeMapping(client.getClientId());
- }
- scopeMappingRep.role(scope.getName());
- } else {
- ClientModel app = (ClientModel)scope.getContainer();
- String appName = app.getClientId();
- List<ScopeMappingRepresentation> currentAppScopes = clientScopeReps.get(appName);
- if (currentAppScopes == null) {
- currentAppScopes = new ArrayList<>();
- clientScopeReps.put(appName, currentAppScopes);
- }
+ if (options.isClientsIncluded()) {
+ List<ClientModel> allClients = new ArrayList<>(clients);
+
+ // Scopes of clients
+ for (ClientModel client : allClients) {
+ Set<RoleModel> clientScopes = client.getScopeMappings();
+ ScopeMappingRepresentation scopeMappingRep = null;
+ for (RoleModel scope : clientScopes) {
+ if (scope.getContainer() instanceof RealmModel) {
+ if (scopeMappingRep == null) {
+ scopeMappingRep = rep.clientScopeMapping(client.getClientId());
+ }
+ scopeMappingRep.role(scope.getName());
+ } else {
+ ClientModel app = (ClientModel) scope.getContainer();
+ String appName = app.getClientId();
+ List<ScopeMappingRepresentation> currentAppScopes = clientScopeReps.get(appName);
+ if (currentAppScopes == null) {
+ currentAppScopes = new ArrayList<>();
+ clientScopeReps.put(appName, currentAppScopes);
+ }
- ScopeMappingRepresentation currentClientScope = null;
- for (ScopeMappingRepresentation scopeMapping : currentAppScopes) {
- if (client.getClientId().equals(scopeMapping.getClient())) {
- currentClientScope = scopeMapping;
- break;
+ ScopeMappingRepresentation currentClientScope = null;
+ for (ScopeMappingRepresentation scopeMapping : currentAppScopes) {
+ if (client.getClientId().equals(scopeMapping.getClient())) {
+ currentClientScope = scopeMapping;
+ break;
+ }
}
+ if (currentClientScope == null) {
+ currentClientScope = new ScopeMappingRepresentation();
+ currentClientScope.setClient(client.getClientId());
+ currentAppScopes.add(currentClientScope);
+ }
+ currentClientScope.role(scope.getName());
}
- if (currentClientScope == null) {
- currentClientScope = new ScopeMappingRepresentation();
- currentClientScope.setClient(client.getClientId());
- currentAppScopes.add(currentClientScope);
- }
- currentClientScope.role(scope.getName());
}
}
}
@@ -211,11 +236,11 @@ public class ExportUtils {
}
// Finally users if needed
- if (includeUsers) {
+ if (options.isUsersIncluded()) {
List<UserModel> allUsers = session.users().getUsers(realm, true);
List<UserRepresentation> users = new LinkedList<>();
for (UserModel user : allUsers) {
- UserRepresentation userRep = exportUser(session, realm, user);
+ UserRepresentation userRep = exportUser(session, realm, user, options);
users.add(userRep);
}
@@ -225,7 +250,7 @@ public class ExportUtils {
List<UserRepresentation> federatedUsers = new LinkedList<>();
for (String userId : session.userFederatedStorage().getStoredUsers(realm, 0, -1)) {
- UserRepresentation userRep = exportFederatedUser(session, realm, userId);
+ UserRepresentation userRep = exportFederatedUser(session, realm, userId, options);
federatedUsers.add(userRep);
}
if (federatedUsers.size() > 0) {
@@ -335,13 +360,15 @@ public class ExportUtils {
private static PolicyRepresentation createPolicyRepresentation(AuthorizationProvider authorizationProvider, Policy policy) {
KeycloakSession session = authorizationProvider.getKeycloakSession();
RealmModel realm = authorizationProvider.getRealm();
- StoreFactory storeFactory = authorizationProvider.getStoreFactory();
+
try {
PolicyRepresentation rep = toRepresentation(policy, PolicyRepresentation.class, authorizationProvider);
rep.setId(null);
- Map<String, String> config = rep.getConfig();
+ Map<String, String> config = new HashMap<>(rep.getConfig());
+
+ rep.setConfig(config);
String roles = config.get("roles");
@@ -462,7 +489,7 @@ public class ExportUtils {
* @param user
* @return fully exported user representation
*/
- public static UserRepresentation exportUser(KeycloakSession session, RealmModel realm, UserModel user) {
+ public static UserRepresentation exportUser(KeycloakSession session, RealmModel realm, UserModel user, ExportOptions options) {
UserRepresentation userRep = ModelToRepresentation.toRepresentation(session, realm, user);
// Social links
@@ -533,12 +560,13 @@ public class ExportUtils {
}
}
- List<String> groups = new LinkedList<>();
- for (GroupModel group : user.getGroups()) {
- groups.add(ModelToRepresentation.buildGroupPath(group));
+ if (options.isGroupsAndRolesIncluded()) {
+ List<String> groups = new LinkedList<>();
+ for (GroupModel group : user.getGroups()) {
+ groups.add(ModelToRepresentation.buildGroupPath(group));
+ }
+ userRep.setGroups(groups);
}
- userRep.setGroups(groups);
-
return userRep;
}
@@ -569,6 +597,10 @@ public class ExportUtils {
// Streaming API
public static void exportUsersToStream(KeycloakSession session, RealmModel realm, List<UserModel> usersToExport, ObjectMapper mapper, OutputStream os) throws IOException {
+ exportUsersToStream(session, realm, usersToExport, mapper, os, new ExportOptions());
+ }
+
+ public static void exportUsersToStream(KeycloakSession session, RealmModel realm, List<UserModel> usersToExport, ObjectMapper mapper, OutputStream os, ExportOptions options) throws IOException {
JsonFactory factory = mapper.getFactory();
JsonGenerator generator = factory.createGenerator(os, JsonEncoding.UTF8);
try {
@@ -582,7 +614,7 @@ public class ExportUtils {
generator.writeStartArray();
for (UserModel user : usersToExport) {
- UserRepresentation userRep = ExportUtils.exportUser(session, realm, user);
+ UserRepresentation userRep = ExportUtils.exportUser(session, realm, user, options);
generator.writeObject(userRep);
}
@@ -594,6 +626,10 @@ public class ExportUtils {
}
public static void exportFederatedUsersToStream(KeycloakSession session, RealmModel realm, List<String> usersToExport, ObjectMapper mapper, OutputStream os) throws IOException {
+ exportFederatedUsersToStream(session, realm, usersToExport, mapper, os, new ExportOptions());
+ }
+
+ public static void exportFederatedUsersToStream(KeycloakSession session, RealmModel realm, List<String> usersToExport, ObjectMapper mapper, OutputStream os, ExportOptions options) throws IOException {
JsonFactory factory = mapper.getFactory();
JsonGenerator generator = factory.createGenerator(os, JsonEncoding.UTF8);
try {
@@ -607,7 +643,7 @@ public class ExportUtils {
generator.writeStartArray();
for (String userId : usersToExport) {
- UserRepresentation userRep = ExportUtils.exportFederatedUser(session, realm, userId);
+ UserRepresentation userRep = ExportUtils.exportFederatedUser(session, realm, userId, options);
generator.writeObject(userRep);
}
@@ -624,7 +660,7 @@ public class ExportUtils {
* @param id
* @return fully exported user representation
*/
- public static UserRepresentation exportFederatedUser(KeycloakSession session, RealmModel realm, String id) {
+ public static UserRepresentation exportFederatedUser(KeycloakSession session, RealmModel realm, String id, ExportOptions options) {
UserRepresentation userRep = new UserRepresentation();
userRep.setId(id);
MultivaluedHashMap<String, String> attributes = session.userFederatedStorage().getAttributes(realm, id);
@@ -654,30 +690,32 @@ public class ExportUtils {
}
// Role mappings
- Set<RoleModel> roles = session.userFederatedStorage().getRoleMappings(realm, id);
- List<String> realmRoleNames = new ArrayList<>();
- Map<String, List<String>> clientRoleNames = new HashMap<>();
- for (RoleModel role : roles) {
- if (role.getContainer() instanceof RealmModel) {
- realmRoleNames.add(role.getName());
- } else {
- ClientModel client = (ClientModel)role.getContainer();
- String clientId = client.getClientId();
- List<String> currentClientRoles = clientRoleNames.get(clientId);
- if (currentClientRoles == null) {
- currentClientRoles = new ArrayList<>();
- clientRoleNames.put(clientId, currentClientRoles);
- }
+ if (options.isGroupsAndRolesIncluded()) {
+ Set<RoleModel> roles = session.userFederatedStorage().getRoleMappings(realm, id);
+ List<String> realmRoleNames = new ArrayList<>();
+ Map<String, List<String>> clientRoleNames = new HashMap<>();
+ for (RoleModel role : roles) {
+ if (role.getContainer() instanceof RealmModel) {
+ realmRoleNames.add(role.getName());
+ } else {
+ ClientModel client = (ClientModel) role.getContainer();
+ String clientId = client.getClientId();
+ List<String> currentClientRoles = clientRoleNames.get(clientId);
+ if (currentClientRoles == null) {
+ currentClientRoles = new ArrayList<>();
+ clientRoleNames.put(clientId, currentClientRoles);
+ }
- currentClientRoles.add(role.getName());
+ currentClientRoles.add(role.getName());
+ }
}
- }
- if (realmRoleNames.size() > 0) {
- userRep.setRealmRoles(realmRoleNames);
- }
- if (clientRoleNames.size() > 0) {
- userRep.setClientRoles(clientRoleNames);
+ if (realmRoleNames.size() > 0) {
+ userRep.setRealmRoles(realmRoleNames);
+ }
+ if (clientRoleNames.size() > 0) {
+ userRep.setClientRoles(clientRoleNames);
+ }
}
// Credentials
@@ -700,13 +738,13 @@ public class ExportUtils {
userRep.setClientConsents(consentReps);
}
-
- List<String> groups = new LinkedList<>();
- for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) {
- groups.add(ModelToRepresentation.buildGroupPath(group));
+ if (options.isGroupsAndRolesIncluded()) {
+ List<String> groups = new LinkedList<>();
+ for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) {
+ groups.add(ModelToRepresentation.buildGroupPath(group));
+ }
+ userRep.setGroups(groups);
}
- userRep.setGroups(groups);
-
return userRep;
}
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
index 625406c..affaf20 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -17,7 +17,6 @@
package org.keycloak.forms.login.freemarker;
import org.jboss.logging.Logger;
-import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
@@ -40,7 +39,6 @@ import org.keycloak.models.*;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.Urls;
import org.keycloak.services.messages.Messages;
-import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.theme.BrowserSecurityHeaderSetup;
import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
@@ -75,7 +73,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
private List<RoleModel> realmRolesRequested;
private MultivaluedMap<String, RoleModel> resourceRolesRequested;
private List<ProtocolMapperModel> protocolMappersRequested;
- private MultivaluedMap<String, String> queryParams;
private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
private String accessRequestMessage;
private URI actionUri;
@@ -146,8 +143,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
ClientModel client = session.getContext().getClient();
UriInfo uriInfo = session.getContext().getUri();
- MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : new MultivaluedMapImpl<String, String>();
-
String requestURI = uriInfo.getBaseUri().getPath();
UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
if (page == LoginFormsPages.OAUTH_GRANT) {
@@ -155,19 +150,17 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
uriBuilder.replaceQuery(null);
}
+ if (client != null) {
+ uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
+ }
+
URI baseUri = uriBuilder.build();
if (accessCode != null) {
uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
}
- URI baseUriWithCode = uriBuilder.build();
-
- for (String k : queryParameterMap.keySet()) {
- Object[] objects = queryParameterMap.get(k).toArray();
- if (objects.length == 1 && objects[0] == null) continue; //
- uriBuilder.replaceQueryParam(k, objects);
- }
+ URI baseUriWithCodeAndClientId = uriBuilder.build();
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme;
@@ -220,7 +213,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
- attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCode));
+ attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCodeAndClientId));
attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
@@ -301,16 +294,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
ClientModel client = session.getContext().getClient();
UriInfo uriInfo = session.getContext().getUri();
- MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : new MultivaluedMapImpl<String, String>();
-
String requestURI = uriInfo.getBaseUri().getPath();
UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
- for (String k : queryParameterMap.keySet()) {
-
- Object[] objects = queryParameterMap.get(k).toArray();
- if (objects.length == 1 && objects[0] == null) continue; //
- uriBuilder.replaceQueryParam(k, objects);
+ if (client != null) {
+ uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
}
URI baseUri = uriBuilder.build();
@@ -318,6 +306,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
if (accessCode != null) {
uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
}
+
URI baseUriWithCode = uriBuilder.build();
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 83570ef..6aa13e2 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -53,6 +53,7 @@ import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.Cors;
import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.util.TokenUtil;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
@@ -340,10 +341,16 @@ public class TokenEndpoint {
AccessToken token = tokenManager.createClientAccessToken(session, parseResult.getCode().getRequestedRoles(), realm, client, user, userSession, clientSession);
- AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
+ TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
.accessToken(token)
- .generateIDToken()
- .generateRefreshToken().build();
+ .generateRefreshToken();
+
+ String scopeParam = clientSession.getNote(OAuth2Constants.SCOPE);
+ if (TokenUtil.isOIDCRequest(scopeParam)) {
+ responseBuilder.generateIDToken();
+ }
+
+ AccessTokenResponse res = responseBuilder.build();
event.success();
@@ -450,11 +457,16 @@ public class TokenEndpoint {
UserSessionModel userSession = processor.getUserSession();
updateUserSessionFromClientAuth(userSession);
- AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
+ TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
.generateAccessToken()
- .generateRefreshToken()
- .generateIDToken()
- .build();
+ .generateRefreshToken();
+
+ String scopeParam = clientSession.getNote(OAuth2Constants.SCOPE);
+ if (TokenUtil.isOIDCRequest(scopeParam)) {
+ responseBuilder.generateIDToken();
+ }
+
+ AccessTokenResponse res = responseBuilder.build();
event.success();
@@ -515,11 +527,16 @@ public class TokenEndpoint {
updateUserSessionFromClientAuth(userSession);
- AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
+ TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
.generateAccessToken()
- .generateRefreshToken()
- .generateIDToken()
- .build();
+ .generateRefreshToken();
+
+ String scopeParam = clientSession.getNote(OAuth2Constants.SCOPE);
+ if (TokenUtil.isOIDCRequest(scopeParam)) {
+ responseBuilder.generateIDToken();
+ }
+
+ AccessTokenResponse res = responseBuilder.build();
event.success();
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
index 6ee2be3..1b8817d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
@@ -31,7 +31,6 @@ import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
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 9782b48..07aec65 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -250,11 +250,16 @@ public class TokenManager {
validation.clientSession.setTimestamp(currentTime);
validation.userSession.setLastSessionRefresh(currentTime);
- AccessTokenResponse res = responseBuilder(realm, authorizedClient, event, session, validation.userSession, validation.clientSession)
+ AccessTokenResponseBuilder responseBuilder = responseBuilder(realm, authorizedClient, event, session, validation.userSession, validation.clientSession)
.accessToken(validation.newToken)
- .generateIDToken()
- .generateRefreshToken()
- .build();
+ .generateRefreshToken();
+
+ String scopeParam = validation.clientSession.getNote(OAuth2Constants.SCOPE);
+ if (TokenUtil.isOIDCRequest(scopeParam)) {
+ responseBuilder.generateIDToken();
+ }
+
+ AccessTokenResponse res = responseBuilder.build();
return new RefreshResult(res, TokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType()));
}
diff --git a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
index 0e488e1..e259738 100644
--- a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
+++ b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
@@ -114,12 +114,12 @@ public class RestartLoginCookie {
public RestartLoginCookie() {
}
- public RestartLoginCookie(AuthenticationSessionModel clientSession) {
- this.action = clientSession.getAction();
- this.clientId = clientSession.getClient().getClientId();
- this.authMethod = clientSession.getProtocol();
- this.redirectUri = clientSession.getRedirectUri();
- for (Map.Entry<String, String> entry : clientSession.getClientNotes().entrySet()) {
+ public RestartLoginCookie(AuthenticationSessionModel authSession) {
+ this.action = authSession.getAction();
+ this.clientId = authSession.getClient().getClientId();
+ this.authMethod = authSession.getProtocol();
+ this.redirectUri = authSession.getRedirectUri();
+ for (Map.Entry<String, String> entry : authSession.getClientNotes().entrySet()) {
notes.put(entry.getKey(), entry.getValue());
}
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
index b90a165..c0be2ba 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
@@ -43,6 +43,7 @@ import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeaderElement;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -53,8 +54,8 @@ public class SamlEcpProfileService extends SamlService {
private static final String NS_PREFIX_SAML_PROTOCOL = "samlp";
private static final String NS_PREFIX_SAML_ASSERTION = "saml";
- public SamlEcpProfileService(RealmModel realm, EventBuilder event) {
- super(realm, event);
+ public SamlEcpProfileService(RealmModel realm, EventBuilder event, Map<String, Integer> knownPorts, Map<Integer, String> knownProtocols) {
+ super(realm, event, knownPorts, knownProtocols);
}
public Response authenticate(InputStream inputStream) {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
index 21ccc81..87d6615 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
@@ -39,7 +39,11 @@ import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConsta
import javax.xml.crypto.dsig.CanonicalizationMethod;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -47,9 +51,26 @@ import java.util.List;
*/
public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
+ private static final Pattern PROTOCOL_MAP_PATTERN = Pattern.compile("\\s*([a-zA-Z][a-zA-Z\\d+-.]*)\\s*=\\s*(\\d+)\\s*");
+ private static final String[] DEFAULT_PROTOCOL_TO_PORT_MAP = new String[] { "http=80", "https=443" };
+
+ private final Map<Integer, String> knownPorts = new HashMap<>();
+ private final Map<String, Integer> knownProtocols = new HashMap<>();
+
+ private void addToProtocolPortMaps(String protocolMapping) {
+ Matcher m = PROTOCOL_MAP_PATTERN.matcher(protocolMapping);
+ if (m.matches()) {
+ Integer port = Integer.valueOf(m.group(2));
+ String proto = m.group(1);
+
+ knownPorts.put(port, proto);
+ knownProtocols.put(proto, port);
+ }
+ }
+
@Override
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event) {
- return new SamlService(realm, event);
+ return new SamlService(realm, event, knownProtocols, knownPorts);
}
@Override
@@ -61,6 +82,15 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
public void init(Config.Scope config) {
//PicketLinkCoreSTS sts = PicketLinkCoreSTS.instance();
//sts.installDefaultConfiguration();
+
+ String[] protocolMappings = config.getArray("knownProtocols");
+ if (protocolMappings == null) {
+ protocolMappings = DEFAULT_PROTOCOL_TO_PORT_MAP;
+ }
+
+ for (String protocolMapping : protocolMappings) {
+ addToProtocolPortMaps(protocolMapping);
+ }
}
@Override
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 9a6790b..e0ac524 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -67,7 +67,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.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@@ -87,6 +86,7 @@ import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.SPMetadataDescriptor;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.keycloak.sessions.AuthenticationSessionModel;
+import java.util.Map;
/**
* Resource class for the saml connect token service
@@ -98,8 +98,13 @@ public class SamlService extends AuthorizationEndpointBase {
protected static final Logger logger = Logger.getLogger(SamlService.class);
- public SamlService(RealmModel realm, EventBuilder event) {
+ private final Map<String, Integer> knownPorts;
+ private final Map<Integer, String> knownProtocols;
+
+ public SamlService(RealmModel realm, EventBuilder event, Map<String, Integer> knownPorts, Map<Integer, String> knownProtocols) {
super(realm, event);
+ this.knownPorts = knownPorts;
+ this.knownProtocols = knownProtocols;
}
public abstract class BindingProtocol {
@@ -239,7 +244,7 @@ public class SamlService extends AuthorizationEndpointBase {
protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) {
SamlClient samlClient = new SamlClient(client);
// validate destination
- if (requestAbstractType.getDestination() != null && !uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
+ if (! isValidDestination(requestAbstractType.getDestination())) {
event.detail(Details.REASON, "invalid_destination");
event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
@@ -341,7 +346,7 @@ public class SamlService extends AuthorizationEndpointBase {
protected Response logoutRequest(LogoutRequestType logoutRequest, ClientModel client, String relayState) {
SamlClient samlClient = new SamlClient(client);
// validate destination
- if (logoutRequest.getDestination() != null && !uriInfo.getAbsolutePath().equals(logoutRequest.getDestination())) {
+ if (! isValidDestination(logoutRequest.getDestination())) {
event.detail(Details.REASON, "invalid_destination");
event.error(Errors.INVALID_SAML_LOGOUT_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
@@ -683,11 +688,35 @@ public class SamlService extends AuthorizationEndpointBase {
@NoCache
@Consumes({"application/soap+xml",MediaType.TEXT_XML})
public Response soapBinding(InputStream inputStream) {
- SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event);
+ SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event, knownPorts, knownProtocols);
ResteasyProviderFactory.getInstance().injectProperties(bindingService);
return bindingService.authenticate(inputStream);
}
+ private boolean isValidDestination(URI destination) {
+ if (destination == null) {
+ return false;
+ }
+
+ URI expected = uriInfo.getAbsolutePath();
+
+ if (Objects.equals(expected, destination)) {
+ return true;
+ }
+
+ Integer portByScheme = knownPorts.get(expected.getScheme());
+ if (expected.getPort() < 0 && portByScheme != null) {
+ return Objects.equals(uriInfo.getRequestUriBuilder().port(portByScheme).build(), destination);
+ }
+
+ String protocolByPort = knownProtocols.get(expected.getPort());
+ if (expected.getPort() >= 0 && Objects.equals(protocolByPort, expected.getScheme())) {
+ return Objects.equals(uriInfo.getRequestUriBuilder().port(-1).build(), destination);
+ }
+
+ return false;
+ }
+
}
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 31217f1..07bd1f6 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -78,9 +78,6 @@ public class AuthenticationManager {
public static final String END_AFTER_REQUIRED_ACTIONS = "END_AFTER_REQUIRED_ACTIONS";
public static final String INVALIDATE_ACTION_TOKEN = "INVALIDATE_ACTION_TOKEN";
- // Last authenticated client in userSession.
- public static final String LAST_AUTHENTICATED_CLIENT = "LAST_AUTHENTICATED_CLIENT";
-
// userSession note with authTime (time when authentication flow including requiredActions was finished)
public static final String AUTH_TIME = "AUTH_TIME";
// clientSession note with flag that clientSession was authenticated through SSO cookie
@@ -95,7 +92,6 @@ public class AuthenticationManager {
public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
public static final String KEYCLOAK_LOGOUT_PROTOCOL = "KEYCLOAK_LOGOUT_PROTOCOL";
- public static final String CURRENT_REQUIRED_ACTION = "CURRENT_REQUIRED_ACTION";
public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
if (userSession == null) {
@@ -463,8 +459,6 @@ public class AuthenticationManager {
userSession.setNote(AUTH_TIME, String.valueOf(authTime));
}
- userSession.setNote(LAST_AUTHENTICATED_CLIENT, clientSession.getClient().getId());
-
return protocol.authenticated(userSession, clientSession);
}
@@ -488,7 +482,7 @@ public class AuthenticationManager {
public static Response redirectToRequiredActions(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, UriInfo uriInfo, String requiredAction) {
// redirect to non-action url so browser refresh button works without reposting past data
ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authSession);
- accessCode.setAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name());
+ accessCode.setAction(AuthenticationSessionModel.Action.REQUIRED_ACTIONS.name());
authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, LoginActionsService.REQUIRED_ACTION);
authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, requiredAction);
@@ -496,9 +490,11 @@ public class AuthenticationManager {
.path(LoginActionsService.REQUIRED_ACTION);
if (requiredAction != null) {
- uriBuilder.queryParam("execution", requiredAction);
+ uriBuilder.queryParam(Constants.EXECUTION, requiredAction);
}
+ uriBuilder.queryParam(Constants.CLIENT_ID, authSession.getClient().getClientId());
+
URI redirect = uriBuilder.build(realm.getName());
return Response.status(302).location(redirect).build();
@@ -526,7 +522,7 @@ public class AuthenticationManager {
}
} else {
- infoPage.setAttribute("skipLink", true);
+ infoPage.setAttribute(Constants.SKIP_LINK, true);
}
Response response = infoPage
.createInfoPage();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java
index 2154ad6..c6064bb 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java
@@ -18,6 +18,7 @@ package org.keycloak.services.resources.admin;
import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.common.ClientConnection;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
@@ -83,7 +84,11 @@ public class AttackDetectionResource {
@Produces(MediaType.APPLICATION_JSON)
public Map<String, Object> bruteForceUserStatus(@PathParam("userId") String userId) {
UserModel user = session.users().getUserById(userId, realm);
- auth.users().requireView(user);
+ if (user == null) {
+ auth.users().requireView();
+ } else {
+ auth.users().requireView(user);
+ }
Map<String, Object> data = new HashMap<>();
data.put("disabled", false);
@@ -114,10 +119,14 @@ public class AttackDetectionResource {
@Path("brute-force/users/{userId}")
@DELETE
public void clearBruteForceForUser(@PathParam("userId") String userId) {
+ UserModel user = session.users().getUserById(userId, realm);
+ if (user == null) {
+ auth.users().requireManage();
+ } else {
+ auth.users().requireManage(user);
+ }
UserLoginFailureModel model = session.sessions().getUserLoginFailure(realm, userId);
if (model != null) {
- UserModel user = session.users().getUserById(userId, realm);
- auth.users().requireView(user);
session.sessions().removeUserLoginFailure(realm, userId);
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
index 1de2c9d..e3dda51 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
@@ -42,6 +42,7 @@ import org.keycloak.services.util.CertificateInfoHelper;
import org.keycloak.util.JWKSUtils;
import org.keycloak.util.JsonSerialization;
+import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
@@ -182,7 +183,9 @@ public class ClientAttributeCertificateResource {
auth.clients().requireManage(client);
CertificateRepresentation info = new CertificateRepresentation();
Map<String, List<InputPart>> uploadForm = input.getFormDataMap();
- String keystoreFormat = uploadForm.get("keystoreFormat").get(0).getBodyAsString();
+ List<InputPart> keystoreFormatPart = uploadForm.get("keystoreFormat");
+ if (keystoreFormatPart == null) throw new BadRequestException();
+ String keystoreFormat = keystoreFormatPart.get(0).getBodyAsString();
List<InputPart> inputParts = uploadForm.get("file");
if (keystoreFormat.equals(CERTIFICATE_PEM)) {
String pem = StreamUtil.readString(inputParts.get(0).getBody(InputStream.class, null));
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index ba6bd03..58ae623 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -307,9 +307,7 @@ public class ClientResource {
@Path("roles")
public RoleContainerResource getRoleContainerResource() {
- AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.clients().requireManage(client);
- AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.clients().requireView(client);
- return new RoleContainerResource(session, uriInfo, realm, auth, client, adminEvent, manageCheck, viewCheck);
+ return new RoleContainerResource(session, uriInfo, realm, auth, client, adminEvent);
}
/**
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
index e02f225..5ff2e72 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
@@ -203,6 +203,8 @@ public class ClientsResource {
*/
@Path("{id}")
public ClientResource getClient(final @PathParam("id") String id) {
+ auth.clients().requireList();
+
ClientModel clientModel = realm.getClientById(id);
if (clientModel == null) {
throw new NotFoundException("Could not find client");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java
index 954f0c8..839aa41 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplatesResource.java
@@ -76,15 +76,13 @@ public class ClientTemplatesResource {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public List<ClientTemplateRepresentation> getClientTemplates() {
+ auth.clients().requireView();
List<ClientTemplateRepresentation> rep = new ArrayList<>();
List<ClientTemplateModel> clientModels = realm.getClientTemplates();
- boolean view = auth.clients().canViewTemplates();
for (ClientTemplateModel clientModel : clientModels) {
- if (view || auth.clients().canView(clientModel)) {
- rep.add(ModelToRepresentation.toRepresentation(clientModel));
- }
+ rep.add(ModelToRepresentation.toRepresentation(clientModel));
}
return rep;
}
@@ -122,6 +120,7 @@ public class ClientTemplatesResource {
*/
@Path("{id}")
public ClientTemplateResource getClient(final @PathParam("id") String id) {
+ auth.clients().requireListTemplates();
ClientTemplateModel clientModel = realm.getClientTemplateById(id);
if (clientModel == null) {
throw new NotFoundException("Could not find client template");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
index 1f0a34d..2a94132 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.services.resources.admin.permissions;
+import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.ResourceServer;
/**
@@ -26,6 +27,8 @@ public interface AdminPermissionManagement {
public static final String MANAGE_SCOPE = "manage";
public static final String VIEW_SCOPE = "view";
+ AuthorizationProvider authz();
+
RolePermissionManagement roles();
UserPermissionManagement users();
GroupPermissionManagement groups();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java
index 8f0af4b..3da715a 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java
@@ -28,6 +28,8 @@ public interface ClientPermissionEvaluator {
void setPermissionsEnabled(ClientModel client, boolean enable);
+ void requireListTemplates();
+
boolean canManage();
void requireManage();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
index da1d24b..8209d2e 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
@@ -160,6 +160,12 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
return root.hasAnyAdminRole();
}
+ @Override
+ public void requireListTemplates() {
+ if (!canListTemplates()) {
+ throw new ForbiddenException();
+ }
+ }
public boolean canManageClientDefault() {
return root.hasOneAdminRole(AdminRoles.MANAGE_CLIENTS);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
index 46d8fb6..e1c0356 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
@@ -111,14 +111,12 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
String manageMembersPermissionName = getManageMembersPermissionGroup(group);
Policy manageMembersPermission = authz.getStoreFactory().getPolicyStore().findByName(manageMembersPermissionName, server.getId());
if (manageMembersPermission == null) {
- Policy manageUsersPolicy = root.roles().manageUsersPolicy(server);
- Helper.addScopePermission(authz, server, manageMembersPermissionName, groupResource, manageMembersScope, manageUsersPolicy);
+ Helper.addEmptyScopePermission(authz, server, manageMembersPermissionName, groupResource, manageMembersScope);
}
String viewMembersPermissionName = getViewMembersPermissionGroup(group);
Policy viewMembersPermission = authz.getStoreFactory().getPolicyStore().findByName(viewMembersPermissionName, server.getId());
if (viewMembersPermission == null) {
- Policy viewUsersPolicy = root.roles().viewUsersPolicy(server);
- Helper.addScopePermission(authz, server, viewMembersPermissionName, groupResource, viewMembersScope, viewUsersPolicy);
+ Helper.addEmptyScopePermission(authz, server, viewMembersPermissionName, groupResource, viewMembersScope);
}
}
@@ -310,6 +308,10 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
@Override
public boolean canViewMembers(GroupModel group) {
+ return canViewMembersEvaluation(group) || canManageMembers(group);
+ }
+
+ private boolean canViewMembersEvaluation(GroupModel group) {
if (!root.isAdminSameRealm()) {
return root.users().canView();
}
@@ -336,6 +338,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
return root.evaluatePermission(resource, scope, server);
}
+
@Override
public void requireViewMembers(GroupModel group) {
if (!canViewMembers(group)) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
index 9434283..a356d05 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
@@ -110,6 +110,11 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
return client;
}
+ @Override
+ public AuthorizationProvider authz() {
+ return authz;
+ }
+
public boolean hasAnyAdminRole() {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java
index 84b6ceb..1d54c55 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RealmPermissions.java
@@ -148,13 +148,13 @@ class RealmPermissions implements RealmPermissionEvaluator {
@Override
public void requireManageAuthorization() {
- if (!canManageEvents()) {
+ if (!canManageAuthorization()) {
throw new ForbiddenException();
}
}
@Override
public void requireViewAuthorization() {
- if (!canManageEvents()) {
+ if (!canViewAuthorization()) {
throw new ForbiddenException();
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java
index 3a87cb3..a4a8b71 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissionEvaluator.java
@@ -44,4 +44,13 @@ public interface RolePermissionEvaluator {
boolean canMapComposite(RoleModel role);
void requireMapComposite(RoleModel role);
+
+ boolean canManage(RoleContainerModel container);
+
+ void requireManage(RoleContainerModel container);
+
+ boolean canView(RoleContainerModel container);
+
+ void requireView(RoleContainerModel container);
+
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
index d2531ec..4167a12 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
@@ -29,6 +29,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.services.ForbiddenException;
import java.util.HashMap;
@@ -179,6 +180,38 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
}
@Override
+ public boolean canManage(RoleContainerModel container) {
+ if (container instanceof RealmModel) {
+ return root.realm().canManageRealm();
+ } else {
+ return root.clients().canManage((ClientModel)container);
+ }
+ }
+
+ @Override
+ public void requireManage(RoleContainerModel container) {
+ if (!canManage(container)) {
+ throw new ForbiddenException();
+ }
+ }
+
+ @Override
+ public boolean canView(RoleContainerModel container) {
+ if (container instanceof RealmModel) {
+ return root.realm().canViewRealm();
+ } else {
+ return root.clients().canView((ClientModel)container);
+ }
+ }
+
+ @Override
+ public void requireView(RoleContainerModel container) {
+ if (!canView(container)) {
+ throw new ForbiddenException();
+ }
+ }
+
+ @Override
public boolean canMapComposite(RoleModel role) {
if (!root.isAdminSameRealm()) {
return canManage(role);
@@ -334,12 +367,14 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
resource.setType("Role");
Scope mapRoleScope = getMapRoleScope(server);
Policy policy = manageUsersPolicy(server);
- Helper.addScopePermission(authz, server, getMapRolePermissionName(role), resource, mapRoleScope, policy);
+ Policy mapRolePermission = Helper.addScopePermission(authz, server, getMapRolePermissionName(role), resource, mapRoleScope, policy);
+ mapRolePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
Scope mapClientScope = getMapClientScope(server);
RoleModel mngClients = root.getRealmManagementClient().getRole(AdminRoles.MANAGE_CLIENTS);
Policy mngClientsPolicy = rolePolicy(server, mngClients);
- Helper.addScopePermission(authz, server, getMapClientScopePermissionName(role), resource, mapClientScope, mngClientsPolicy);
+ Policy mapClientScopePermission = Helper.addScopePermission(authz, server, getMapClientScopePermissionName(role), resource, mapClientScope, mngClientsPolicy);
+ mapClientScopePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
Scope mapCompositeScope = getMapCompositeScope(server);
if (role.getContainer() instanceof RealmModel) {
@@ -349,7 +384,8 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
policy = mngClientsPolicy;
}
- Helper.addScopePermission(authz, server, getMapCompositePermissionName(role), resource, mapCompositeScope, policy);
+ Policy mapCompositePermission = Helper.addScopePermission(authz, server, getMapCompositePermissionName(role), resource, mapCompositeScope, policy);
+ mapCompositePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
return resource;
}
@@ -362,7 +398,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
}
private String getMapCompositePermissionName(RoleModel role) {
- return MAP_ROLE_CLIENT_SCOPE_SCOPE + ".permission." + role.getName();
+ return MAP_ROLE_COMPOSITE_SCOPE + ".permission." + role.getName();
}
private ResourceServer getResourceServer(RoleModel role) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
index a0a7281..5e7931b 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
@@ -281,7 +281,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
@Override
public boolean canQuery() {
- return canViewDefault();
+ return canView();
}
@Override
@@ -312,6 +312,10 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
return canViewDefault();
}
+ return hasViewPermission() || canManage();
+ }
+
+ public boolean hasViewPermission() {
ResourceServer server = root.realmResourceServer();
if (server == null) return canViewDefault();
@@ -346,7 +350,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
*/
@Override
public boolean canView(UserModel user) {
- return canView() || canManage() || canViewByGroup(user) || canManageByGroup(user);
+ return canView() || canViewByGroup(user);
}
@Override
@@ -358,7 +362,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
@Override
public void requireView() {
- if (!(canView() || canManage())) {
+ if (!(canView())) {
throw new ForbiddenException();
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
index 3aac331..709197d 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
@@ -239,6 +239,8 @@ public class ProtocolMappersResource {
ProtocolMapper mapper = (ProtocolMapper)session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, model.getProtocolMapper());
if (mapper != null) {
mapper.validateConfig(session, realm, client, model);
+ } else {
+ throw new NotFoundException("ProtocolMapper provider not found");
}
} catch (ProtocolMapperConfigException ex) {
logger.error(ex.getMessage());
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 0a84001..28392f7 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -39,6 +39,8 @@ import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.exportimport.ClientDescriptionConverter;
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
+import org.keycloak.exportimport.util.ExportOptions;
+import org.keycloak.exportimport.util.ExportUtils;
import org.keycloak.keys.PublicKeyStorageProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
@@ -102,6 +104,8 @@ import java.util.List;
import java.util.Map;
import java.util.regex.PatternSyntaxException;
+import static org.keycloak.models.utils.StripSecretsUtils.stripForExport;
+
/**
* Base resource class for the admin REST api of one realm
*
@@ -234,9 +238,7 @@ public class RealmAdminResource {
*/
@Path("roles")
public RoleContainerResource getRoleContainerResource() {
- AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.realm().requireManageRealm();
- AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.realm().requireViewRealm();
- return new RoleContainerResource(session, uriInfo, realm, auth, realm, adminEvent, manageCheck, viewCheck);
+ return new RoleContainerResource(session, uriInfo, realm, auth, realm, adminEvent);
}
/**
@@ -326,8 +328,6 @@ public class RealmAdminResource {
}
return Response.noContent().build();
- } catch (PatternSyntaxException e) {
- return ErrorResponse.error("Specified regex pattern(s) is invalid.", Response.Status.BAD_REQUEST);
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Realm with same name exists");
} catch (ModelException e) {
@@ -904,6 +904,27 @@ public class RealmAdminResource {
}
/**
+ * Partial export of existing realm into a JSON file.
+ *
+ * @param exportGroupsAndRoles
+ * @param exportClients
+ * @return
+ */
+ @Path("partial-export")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ public RealmRepresentation partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles,
+ @QueryParam("exportClients") Boolean exportClients) {
+
+ boolean groupsAndRolesExported = exportGroupsAndRoles != null && exportGroupsAndRoles;
+ boolean clientsExported = exportClients != null && exportClients;
+
+ ExportOptions options = new ExportOptions(false, clientsExported, groupsAndRolesExported);
+ RealmRepresentation rep = ExportUtils.exportRealm(session, realm, options);
+ return stripForExport(session, rep);
+ }
+
+ /**
* Clear realm cache
*
*/
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
index 7f4d495..79bb6c8 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
@@ -62,8 +62,6 @@ import java.util.Set;
public class RoleContainerResource extends RoleResource {
private final RealmModel realm;
protected AdminPermissionEvaluator auth;
- protected AdminPermissionEvaluator.RequirePermissionCheck managePermission;
- protected AdminPermissionEvaluator.RequirePermissionCheck viewPermission;
protected RoleContainerModel roleContainer;
private AdminEventBuilder adminEvent;
@@ -71,9 +69,7 @@ public class RoleContainerResource extends RoleResource {
private KeycloakSession session;
public RoleContainerResource(KeycloakSession session, UriInfo uriInfo, RealmModel realm,
- AdminPermissionEvaluator auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent,
- AdminPermissionEvaluator.RequirePermissionCheck managePermission,
- AdminPermissionEvaluator.RequirePermissionCheck viewPermission) {
+ AdminPermissionEvaluator auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent) {
super(realm);
this.uriInfo = uriInfo;
this.realm = realm;
@@ -81,8 +77,6 @@ public class RoleContainerResource extends RoleResource {
this.roleContainer = roleContainer;
this.adminEvent = adminEvent;
this.session = session;
- this.managePermission = managePermission;
- this.viewPermission = viewPermission;
}
/**
@@ -113,7 +107,7 @@ public class RoleContainerResource extends RoleResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response createRole(final RoleRepresentation rep) {
- managePermission.require();
+ auth.roles().requireManage(roleContainer);
if (rep.getName() == null) {
throw new BadRequestException();
@@ -152,12 +146,12 @@ public class RoleContainerResource extends RoleResource {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public RoleRepresentation getRole(final @PathParam("role-name") String roleName) {
+ auth.roles().requireView(roleContainer);
RoleModel roleModel = roleContainer.getRole(roleName);
if (roleModel == null) {
throw new NotFoundException("Could not find role");
}
- auth.roles().requireView(roleModel);
return getRole(roleModel);
}
@@ -171,11 +165,11 @@ public class RoleContainerResource extends RoleResource {
@DELETE
@NoCache
public void deleteRole(final @PathParam("role-name") String roleName) {
+ auth.roles().requireManage(roleContainer);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role");
}
- auth.roles().requireManage(role);
deleteRole(role);
if (role.isClientRole()) {
@@ -199,11 +193,11 @@ public class RoleContainerResource extends RoleResource {
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response updateRole(final @PathParam("role-name") String roleName, final RoleRepresentation rep) {
+ auth.roles().requireManage(roleContainer);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role");
}
- auth.roles().requireManage(role);
try {
updateRole(rep, role);
@@ -231,11 +225,11 @@ public class RoleContainerResource extends RoleResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
public void addComposites(final @PathParam("role-name") String roleName, List<RoleRepresentation> roles) {
- RoleModel role = roleContainer.getRole(roleName);
+ auth.roles().requireManage(roleContainer);
+ RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role");
}
- auth.roles().requireManage(role);
addComposites(auth, adminEvent, uriInfo, roles, role);
}
@@ -250,11 +244,11 @@ public class RoleContainerResource extends RoleResource {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public Set<RoleRepresentation> getRoleComposites(final @PathParam("role-name") String roleName) {
+ auth.roles().requireView(roleContainer);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role");
}
- auth.roles().requireView(role);
return getRoleComposites(role);
}
@@ -269,11 +263,11 @@ public class RoleContainerResource extends RoleResource {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public Set<RoleRepresentation> getRealmRoleComposites(final @PathParam("role-name") String roleName) {
+ auth.roles().requireView(roleContainer);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role");
}
- auth.roles().requireView(role);
return getRealmRoleComposites(role);
}
@@ -291,11 +285,11 @@ public class RoleContainerResource extends RoleResource {
public Set<RoleRepresentation> getClientRoleComposites(@Context final UriInfo uriInfo,
final @PathParam("role-name") String roleName,
final @PathParam("client") String client) {
+ auth.roles().requireView(roleContainer);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role");
}
- auth.roles().requireView(role);
ClientModel clientModel = realm.getClientById(client);
if (client == null) {
throw new NotFoundException("Could not find client");
@@ -318,11 +312,11 @@ public class RoleContainerResource extends RoleResource {
final @PathParam("role-name") String roleName,
List<RoleRepresentation> roles) {
+ auth.roles().requireManage(roleContainer);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role");
}
- auth.roles().requireManage(role);
deleteComposites(adminEvent, uriInfo, roles, role);
}
@@ -338,11 +332,11 @@ public class RoleContainerResource extends RoleResource {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public ManagementPermissionReference getManagementPermissions(final @PathParam("role-name") String roleName) {
+ auth.roles().requireView(roleContainer);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role");
}
- auth.roles().requireView(role);
AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
if (!permissions.roles().isPermissionsEnabled(role)) {
@@ -364,11 +358,11 @@ public class RoleContainerResource extends RoleResource {
@Consumes(MediaType.APPLICATION_JSON)
@NoCache
public ManagementPermissionReference setManagementPermissionsEnabled(final @PathParam("role-name") String roleName, ManagementPermissionReference ref) {
+ auth.roles().requireManage(roleContainer);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role");
}
- auth.roles().requireManage(role);
if (ref.isEnabled()) {
AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
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
new file mode 100755
index 0000000..44e4892
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
@@ -0,0 +1,792 @@
+/*
+ * 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.services.resources.admin;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.BadRequestException;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.authentication.actiontoken.execactions.ExecuteActionsActionToken;
+import org.keycloak.common.ClientConnection;
+import org.keycloak.common.Profile;
+import org.keycloak.common.util.Time;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.email.EmailException;
+import org.keycloak.email.EmailTemplateProvider;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
+import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserLoginFailureModel;
+import org.keycloak.models.UserManager;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.FederatedIdentityRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.UserConsentRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.UserSessionRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.ServicesLogger;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.BruteForceProtector;
+import org.keycloak.services.managers.UserSessionManager;
+import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.LoginActionsService;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.validation.Validation;
+import org.keycloak.storage.ReadOnlyException;
+import org.keycloak.utils.ProfileHelper;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base resource for managing users
+ *
+ * @resource Users
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserResource {
+ private static final Logger logger = Logger.getLogger(UserResource.class);
+
+ protected RealmModel realm;
+
+ private AdminPermissionEvaluator auth;
+
+ private AdminEventBuilder adminEvent;
+ private UserModel user;
+
+ @Context
+ protected ClientConnection clientConnection;
+
+ @Context
+ protected UriInfo uriInfo;
+
+ @Context
+ protected KeycloakSession session;
+
+ @Context
+ protected HttpHeaders headers;
+
+ public UserResource(RealmModel realm, UserModel user, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+ this.auth = auth;
+ this.realm = realm;
+ this.user = user;
+ this.adminEvent = adminEvent.resource(ResourceType.USER);
+ }
+
+ /**
+ * Update the user
+ *
+ * @param rep
+ * @return
+ */
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response updateUser(final UserRepresentation rep) {
+
+ auth.users().requireManage(user);
+ try {
+ Set<String> attrsToRemove;
+ if (rep.getAttributes() != null) {
+ attrsToRemove = new HashSet<>(user.getAttributes().keySet());
+ attrsToRemove.removeAll(rep.getAttributes().keySet());
+ } else {
+ attrsToRemove = Collections.emptySet();
+ }
+
+ if (rep.isEnabled() != null && rep.isEnabled()) {
+ UserLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, user.getId());
+ if (failureModel != null) {
+ failureModel.clearFailures();
+ }
+ }
+
+ updateUserFromRep(user, rep, attrsToRemove, realm, session, true);
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
+
+ if (session.getTransactionManager().isActive()) {
+ session.getTransactionManager().commit();
+ }
+ return Response.noContent().build();
+ } catch (ModelDuplicateException e) {
+ return ErrorResponse.exists("User exists with same username or email");
+ } catch (ReadOnlyException re) {
+ return ErrorResponse.exists("User is read only!");
+ } catch (ModelException me) {
+ logger.warn("Could not update user!", me);
+ return ErrorResponse.exists("Could not update user!");
+ } catch (ForbiddenException fe) {
+ throw fe;
+ } catch (Exception me) { // JPA
+ logger.warn("Could not update user!", me);// may be committed by JTA which can't
+ return ErrorResponse.exists("Could not update user!");
+ }
+ }
+
+ public static void updateUserFromRep(UserModel user, UserRepresentation rep, Set<String> attrsToRemove, RealmModel realm, KeycloakSession session, boolean removeMissingRequiredActions) {
+ if (rep.getUsername() != null && realm.isEditUsernameAllowed()) {
+ user.setUsername(rep.getUsername());
+ }
+ if (rep.getEmail() != null) user.setEmail(rep.getEmail());
+ if (rep.getFirstName() != null) user.setFirstName(rep.getFirstName());
+ if (rep.getLastName() != null) user.setLastName(rep.getLastName());
+
+ if (rep.isEnabled() != null) user.setEnabled(rep.isEnabled());
+ if (rep.isEmailVerified() != null) user.setEmailVerified(rep.isEmailVerified());
+
+ List<String> reqActions = rep.getRequiredActions();
+
+ if (reqActions != null) {
+ Set<String> allActions = new HashSet<>();
+ for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class)) {
+ allActions.add(factory.getId());
+ }
+ for (String action : allActions) {
+ if (reqActions.contains(action)) {
+ user.addRequiredAction(action);
+ } else if (removeMissingRequiredActions) {
+ user.removeRequiredAction(action);
+ }
+ }
+ }
+
+ if (rep.getAttributes() != null) {
+ for (Map.Entry<String, List<String>> attr : rep.getAttributes().entrySet()) {
+ user.setAttribute(attr.getKey(), attr.getValue());
+ }
+
+ for (String attr : attrsToRemove) {
+ user.removeAttribute(attr);
+ }
+ }
+ }
+
+ /**
+ * Get representation of the user
+ *
+ * @return
+ */
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public UserRepresentation getUser() {
+ auth.users().requireView(user);
+
+ UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, user);
+
+ if (realm.isIdentityFederationEnabled()) {
+ List<FederatedIdentityRepresentation> reps = getFederatedIdentities(user);
+ rep.setFederatedIdentities(reps);
+ }
+
+ if (session.getProvider(BruteForceProtector.class).isTemporarilyDisabled(session, realm, user)) {
+ rep.setEnabled(false);
+ }
+
+ return rep;
+ }
+
+ /**
+ * Impersonate the user
+ *
+ * @return
+ */
+ @Path("impersonation")
+ @POST
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public Map<String, Object> impersonate() {
+ ProfileHelper.requireFeature(Profile.Feature.IMPERSONATION);
+
+ auth.users().requireImpersonate(user);
+ RealmModel authenticatedRealm = auth.adminAuth().getRealm();
+ // if same realm logout before impersonation
+ boolean sameRealm = false;
+ if (authenticatedRealm.getId().equals(realm.getId())) {
+ sameRealm = true;
+ UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, auth.adminAuth().getToken().getSessionState());
+ AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
+ AuthenticationManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
+ AuthenticationManager.backchannelLogout(session, authenticatedRealm, userSession, uriInfo, clientConnection, headers, true);
+ }
+ EventBuilder event = new EventBuilder(realm, session, clientConnection);
+
+ String sessionId = KeycloakModelUtils.generateId();
+ UserSessionModel userSession = session.sessions().createUserSession(sessionId, realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
+ AuthenticationManager.createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
+ URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName());
+ Map<String, Object> result = new HashMap<>();
+ result.put("sameRealm", sameRealm);
+ result.put("redirect", redirect.toString());
+ event.event(EventType.IMPERSONATE)
+ .session(userSession)
+ .user(user)
+ .detail(Details.IMPERSONATOR_REALM,authenticatedRealm.getName())
+ .detail(Details.IMPERSONATOR, auth.adminAuth().getUser().getUsername()).success();
+
+ return result;
+ }
+
+
+ /**
+ * Get sessions associated with the user
+ *
+ * @return
+ */
+ @Path("sessions")
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<UserSessionRepresentation> getSessions() {
+ auth.users().requireView(user);
+ List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
+ List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
+ for (UserSessionModel session : sessions) {
+ UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
+ reps.add(rep);
+ }
+ return reps;
+ }
+
+ /**
+ * Get offline sessions associated with the user and client
+ *
+ * @return
+ */
+ @Path("offline-sessions/{clientId}")
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<UserSessionRepresentation> getOfflineSessions(final @PathParam("clientId") String clientId) {
+ auth.users().requireView(user);
+ ClientModel client = realm.getClientById(clientId);
+ if (client == null) {
+ throw new NotFoundException("Client not found");
+ }
+ List<UserSessionModel> sessions = new UserSessionManager(session).findOfflineSessions(realm, user);
+ List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
+ for (UserSessionModel session : sessions) {
+ UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
+
+ // Update lastSessionRefresh with the timestamp from clientSession
+ AuthenticatedClientSessionModel clientSession = session.getAuthenticatedClientSessions().get(clientId);
+
+ // Skip if userSession is not for this client
+ if (clientSession == null) {
+ continue;
+ }
+
+ rep.setLastAccess(clientSession.getTimestamp());
+
+ reps.add(rep);
+ }
+ return reps;
+ }
+
+ /**
+ * Get social logins associated with the user
+ *
+ * @return
+ */
+ @Path("federated-identity")
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<FederatedIdentityRepresentation> getFederatedIdentity() {
+ auth.users().requireView(user);
+
+ return getFederatedIdentities(user);
+ }
+
+ private List<FederatedIdentityRepresentation> getFederatedIdentities(UserModel user) {
+ Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
+ List<FederatedIdentityRepresentation> result = new ArrayList<FederatedIdentityRepresentation>();
+
+ for (FederatedIdentityModel identity : identities) {
+ for (IdentityProviderModel identityProviderModel : realm.getIdentityProviders()) {
+ if (identityProviderModel.getAlias().equals(identity.getIdentityProvider())) {
+ FederatedIdentityRepresentation rep = ModelToRepresentation.toRepresentation(identity);
+ result.add(rep);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Add a social login provider to the user
+ *
+ * @param provider Social login provider id
+ * @param rep
+ * @return
+ */
+ @Path("federated-identity/{provider}")
+ @POST
+ @NoCache
+ public Response addFederatedIdentity(final @PathParam("provider") String provider, FederatedIdentityRepresentation rep) {
+ auth.users().requireManage(user);
+ if (session.users().getFederatedIdentity(user, provider, realm) != null) {
+ return ErrorResponse.exists("User is already linked with provider");
+ }
+
+ FederatedIdentityModel socialLink = new FederatedIdentityModel(provider, rep.getUserId(), rep.getUserName());
+ session.users().addFederatedIdentity(realm, user, socialLink);
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(rep).success();
+ return Response.noContent().build();
+ }
+
+ /**
+ * Remove a social login provider from user
+ *
+ * @param provider Social login provider id
+ */
+ @Path("federated-identity/{provider}")
+ @DELETE
+ @NoCache
+ public void removeFederatedIdentity(final @PathParam("provider") String provider) {
+ auth.users().requireManage(user);
+ if (!session.users().removeFederatedIdentity(realm, user, provider)) {
+ throw new NotFoundException("Link not found");
+ }
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
+ }
+
+ /**
+ * Get consents granted by the user
+ *
+ * @return
+ */
+ @Path("consents")
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<Map<String, Object>> getConsents() {
+ auth.users().requireView(user);
+ List<Map<String, Object>> result = new LinkedList<>();
+
+ Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
+
+ for (ClientModel client : realm.getClients()) {
+ UserConsentModel consent = session.users().getConsentByClient(realm, user.getId(), client.getId());
+ boolean hasOfflineToken = offlineClients.contains(client);
+
+ if (consent == null && !hasOfflineToken) {
+ continue;
+ }
+
+ UserConsentRepresentation rep = (consent == null) ? null : ModelToRepresentation.toRepresentation(consent);
+
+ Map<String, Object> currentRep = new HashMap<>();
+ currentRep.put("clientId", client.getClientId());
+ currentRep.put("grantedProtocolMappers", (rep==null ? Collections.emptyMap() : rep.getGrantedProtocolMappers()));
+ currentRep.put("grantedRealmRoles", (rep==null ? Collections.emptyList() : rep.getGrantedRealmRoles()));
+ currentRep.put("grantedClientRoles", (rep==null ? Collections.emptyMap() : rep.getGrantedClientRoles()));
+ currentRep.put("createdDate", (rep==null ? null : rep.getCreatedDate()));
+ currentRep.put("lastUpdatedDate", (rep==null ? null : rep.getLastUpdatedDate()));
+
+ List<Map<String, String>> additionalGrants = new LinkedList<>();
+ if (hasOfflineToken) {
+ Map<String, String> offlineTokens = new HashMap<>();
+ offlineTokens.put("client", client.getId());
+ // TODO: translate
+ offlineTokens.put("key", "Offline Token");
+ additionalGrants.add(offlineTokens);
+ }
+ currentRep.put("additionalGrants", additionalGrants);
+
+ result.add(currentRep);
+ }
+
+ return result;
+ }
+
+ /**
+ * Revoke consent and offline tokens for particular client from user
+ *
+ * @param clientId Client id
+ */
+ @Path("consents/{client}")
+ @DELETE
+ @NoCache
+ public void revokeConsent(final @PathParam("client") String clientId) {
+ auth.users().requireManage(user);
+
+ ClientModel client = realm.getClientByClientId(clientId);
+ if (client == null) {
+ throw new NotFoundException("Client not found");
+ }
+ boolean revokedConsent = session.users().revokeConsentForClient(realm, user.getId(), client.getId());
+ boolean revokedOfflineToken = new UserSessionManager(session).revokeOfflineToken(user, client);
+
+ if (revokedConsent) {
+ // Logout clientSessions for this user and client
+ AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
+ }
+
+ if (!revokedConsent && !revokedOfflineToken) {
+ throw new NotFoundException("Consent nor offline token not found");
+ }
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+ }
+
+ /**
+ * Remove all user sessions associated with the user
+ *
+ * Also send notification to all clients that have an admin URL to invalidate the sessions for the particular user.
+ *
+ */
+ @Path("logout")
+ @POST
+ public void logout() {
+ auth.users().requireManage(user);
+
+ List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
+ for (UserSessionModel userSession : userSessions) {
+ AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
+ }
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+ }
+
+ /**
+ * Delete the user
+ */
+ @DELETE
+ @NoCache
+ public Response deleteUser() {
+ auth.users().requireManage(user);
+
+ boolean removed = new UserManager(session).removeUser(realm, user);
+ if (removed) {
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
+ return Response.noContent().build();
+ } else {
+ return ErrorResponse.error("User couldn't be deleted", Status.BAD_REQUEST);
+ }
+ }
+
+ @Path("role-mappings")
+ public RoleMapperResource getRoleMappings() {
+ AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.users().requireManage(user);
+ AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.users().requireView(user);
+ RoleMapperResource resource = new RoleMapperResource(realm, auth, user, adminEvent, manageCheck, viewCheck);
+ ResteasyProviderFactory.getInstance().injectProperties(resource);
+ return resource;
+
+ }
+
+ /**
+ * Disable all credentials for a user of a specific type
+ *
+ * @param credentialTypes
+ */
+ @Path("disable-credential-types")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void disableCredentialType(List<String> credentialTypes) {
+ auth.users().requireManage(user);
+ if (credentialTypes == null) return;
+ for (String type : credentialTypes) {
+ session.userCredentialManager().disableCredentialType(realm, user, type);
+
+ }
+
+
+ }
+
+ /**
+ * Set up a temporary password for the user
+ *
+ * User will have to reset the temporary password next time they log in.
+ *
+ * @param pass A Temporary password
+ */
+ @Path("reset-password")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void resetPassword(CredentialRepresentation pass) {
+ auth.users().requireManage(user);
+ if (pass == null || pass.getValue() == null || !CredentialRepresentation.PASSWORD.equals(pass.getType())) {
+ throw new BadRequestException("No password provided");
+ }
+ if (Validation.isBlank(pass.getValue())) {
+ throw new BadRequestException("Empty password not allowed");
+ }
+
+ UserCredentialModel cred = UserCredentialModel.password(pass.getValue(), true);
+ try {
+ session.userCredentialManager().updateCredential(realm, user, cred);
+ } catch (IllegalStateException ise) {
+ throw new BadRequestException("Resetting to N old passwords is not allowed.");
+ } catch (ReadOnlyException mre) {
+ throw new BadRequestException("Can't reset password as account is read only");
+ } catch (ModelException e) {
+ Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
+ throw new ErrorResponseException(e.getMessage(), MessageFormat.format(messages.getProperty(e.getMessage(), e.getMessage()), e.getParameters()),
+ Status.BAD_REQUEST);
+ }
+ if (pass.isTemporary() != null && pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+ }
+
+ /**
+ * Remove TOTP from the user
+ *
+ */
+ @Path("remove-totp")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void removeTotp() {
+ auth.users().requireManage(user);
+
+ session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+ }
+
+ /**
+ * Send an email to the user with a link they can click to reset their password.
+ * The redirectUri and clientId parameters are optional. The default for the
+ * redirect is the account client.
+ *
+ * This endpoint has been deprecated. Please use the execute-actions-email passing a list with
+ * UPDATE_PASSWORD within it.
+ *
+ * @param redirectUri redirect uri
+ * @param clientId client id
+ * @return
+ */
+ @Deprecated
+ @Path("reset-password-email")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response resetPasswordEmail(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri,
+ @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) {
+ List<String> actions = new LinkedList<>();
+ actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+ return executeActionsEmail(redirectUri, clientId, null, actions);
+ }
+
+
+ /**
+ * Send a update account email to the user
+ *
+ * An email contains a link the user can click to perform a set of required actions.
+ * The redirectUri and clientId parameters are optional. If no redirect is given, then there will
+ * be no link back to click after actions have completed. Redirect uri must be a valid uri for the
+ * particular clientId.
+ *
+ * @param redirectUri Redirect uri
+ * @param clientId Client id
+ * @param lifespan Number of seconds after which the generated token expires
+ * @param actions required actions the user needs to complete
+ * @return
+ */
+ @Path("execute-actions-email")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response executeActionsEmail(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri,
+ @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId,
+ @QueryParam("lifespan") Integer lifespan,
+ List<String> actions) {
+ auth.users().requireManage(user);
+
+ if (user.getEmail() == null) {
+ return ErrorResponse.error("User email missing", Status.BAD_REQUEST);
+ }
+
+ if (!user.isEnabled()) {
+ throw new WebApplicationException(
+ ErrorResponse.error("User is disabled", Status.BAD_REQUEST));
+ }
+
+ if (redirectUri != null && clientId == null) {
+ throw new WebApplicationException(
+ ErrorResponse.error("Client id missing", Status.BAD_REQUEST));
+ }
+
+ if (clientId == null) {
+ clientId = Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
+ }
+
+ ClientModel client = realm.getClientByClientId(clientId);
+ if (client == null || !client.isEnabled()) {
+ throw new WebApplicationException(
+ ErrorResponse.error(clientId + " not enabled", Status.BAD_REQUEST));
+ }
+
+ String redirect;
+ if (redirectUri != null) {
+ redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
+ if (redirect == null) {
+ throw new WebApplicationException(
+ ErrorResponse.error("Invalid redirect uri.", Status.BAD_REQUEST));
+ }
+ }
+
+ if (lifespan == null) {
+ lifespan = realm.getActionTokenGeneratedByAdminLifespan();
+ }
+ int expiration = Time.currentTime() + lifespan;
+ ExecuteActionsActionToken token = new ExecuteActionsActionToken(user.getId(), expiration, actions, redirectUri, clientId);
+
+ try {
+ UriBuilder builder = LoginActionsService.actionTokenProcessor(uriInfo);
+ builder.queryParam("key", token.serialize(session, realm, uriInfo));
+
+ String link = builder.build(realm.getName()).toString();
+
+ this.session.getProvider(EmailTemplateProvider.class)
+ .setRealm(realm)
+ .setUser(user)
+ .sendExecuteActions(link, TimeUnit.SECONDS.toMinutes(lifespan));
+
+ //audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+
+ return Response.ok().build();
+ } catch (EmailException e) {
+ ServicesLogger.LOGGER.failedToSendActionsEmail(e);
+ return ErrorResponse.error("Failed to send execute actions email", Status.INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Send an email-verification email to the user
+ *
+ * An email contains a link the user can click to verify their email address.
+ * The redirectUri and clientId parameters are optional. The default for the
+ * redirect is the account client.
+ *
+ * @param redirectUri Redirect uri
+ * @param clientId Client id
+ * @return
+ */
+ @Path("send-verify-email")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response sendVerifyEmail(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) {
+ List<String> actions = new LinkedList<>();
+ actions.add(UserModel.RequiredAction.VERIFY_EMAIL.name());
+ return executeActionsEmail(redirectUri, clientId, null, actions);
+ }
+
+ @GET
+ @Path("groups")
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<GroupRepresentation> groupMembership() {
+ auth.users().requireView(user);
+ List<GroupRepresentation> memberships = new LinkedList<>();
+ for (GroupModel group : user.getGroups()) {
+ memberships.add(ModelToRepresentation.toRepresentation(group, false));
+ }
+ return memberships;
+ }
+
+ @DELETE
+ @Path("groups/{groupId}")
+ @NoCache
+ public void removeMembership(@PathParam("groupId") String groupId) {
+ auth.users().requireManage(user);
+
+ GroupModel group = session.realms().getGroupById(groupId, realm);
+ if (group == null) {
+ throw new NotFoundException("Group not found");
+ }
+
+ try {
+ if (user.isMemberOf(group)){
+ user.leaveGroup(group);
+ adminEvent.operation(OperationType.DELETE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(uriInfo).success();
+ }
+ } catch (ModelException me) {
+ Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
+ throw new ErrorResponseException(me.getMessage(), MessageFormat.format(messages.getProperty(me.getMessage(), me.getMessage()), me.getParameters()),
+ Status.BAD_REQUEST);
+ }
+ }
+
+ @PUT
+ @Path("groups/{groupId}")
+ @NoCache
+ public void joinGroup(@PathParam("groupId") String groupId) {
+ auth.users().requireManage(user);
+ GroupModel group = session.realms().getGroupById(groupId, realm);
+ if (group == null) {
+ throw new NotFoundException("Group not found");
+ }
+ if (!user.isMemberOf(group)){
+ user.joinGroup(group);
+ adminEvent.operation(OperationType.CREATE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(uriInfo).success();
+ }
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 502fbe3..b1cd2e6 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -138,62 +138,6 @@ public class UsersResource {
}
/**
- * Update the user
- *
- * @param id User id
- * @param rep
- * @return
- */
- @Path("{id}")
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- public Response updateUser(final @PathParam("id") String id, final UserRepresentation rep) {
-
- try {
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- return Response.status(Status.NOT_FOUND).build();
- }
- auth.users().requireManage(user);
-
- Set<String> attrsToRemove;
- if (rep.getAttributes() != null) {
- attrsToRemove = new HashSet<>(user.getAttributes().keySet());
- attrsToRemove.removeAll(rep.getAttributes().keySet());
- } else {
- attrsToRemove = Collections.emptySet();
- }
-
- if (rep.isEnabled() != null && rep.isEnabled()) {
- UserLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, id);
- if (failureModel != null) {
- failureModel.clearFailures();
- }
- }
-
- updateUserFromRep(user, rep, attrsToRemove, realm, session, true);
- adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
-
- if (session.getTransactionManager().isActive()) {
- session.getTransactionManager().commit();
- }
- return Response.noContent().build();
- } catch (ModelDuplicateException e) {
- return ErrorResponse.exists("User exists with same username or email");
- } catch (ReadOnlyException re) {
- return ErrorResponse.exists("User is read only!");
- } catch (ModelException me) {
- logger.warn("Could not update user!", me);
- return ErrorResponse.exists("Could not update user!");
- } catch (ForbiddenException fe) {
- throw fe;
- } catch (Exception me) { // JPA
- logger.warn("Could not update user!", me);// may be committed by JTA which can't
- return ErrorResponse.exists("Could not update user!");
- }
- }
-
- /**
* Create a new user
*
* Username must be unique.
@@ -218,7 +162,7 @@ public class UsersResource {
try {
UserModel user = session.users().addUser(realm, rep.getUsername());
Set<String> emptySet = Collections.emptySet();
- updateUserFromRep(user, rep, emptySet, realm, session, false);
+ UserResource.updateUserFromRep(user, rep, emptySet, realm, session, false);
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success();
@@ -240,45 +184,6 @@ public class UsersResource {
return ErrorResponse.exists("Could not create user");
}
}
-
- public static void updateUserFromRep(UserModel user, UserRepresentation rep, Set<String> attrsToRemove, RealmModel realm, KeycloakSession session, boolean removeMissingRequiredActions) {
- if (rep.getUsername() != null && realm.isEditUsernameAllowed()) {
- user.setUsername(rep.getUsername());
- }
- if (rep.getEmail() != null) user.setEmail(rep.getEmail());
- if (rep.getFirstName() != null) user.setFirstName(rep.getFirstName());
- if (rep.getLastName() != null) user.setLastName(rep.getLastName());
-
- if (rep.isEnabled() != null) user.setEnabled(rep.isEnabled());
- if (rep.isEmailVerified() != null) user.setEmailVerified(rep.isEmailVerified());
-
- List<String> reqActions = rep.getRequiredActions();
-
- if (reqActions != null) {
- Set<String> allActions = new HashSet<>();
- for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class)) {
- allActions.add(factory.getId());
- }
- for (String action : allActions) {
- if (reqActions.contains(action)) {
- user.addRequiredAction(action);
- } else if (removeMissingRequiredActions) {
- user.removeRequiredAction(action);
- }
- }
- }
-
- if (rep.getAttributes() != null) {
- for (Map.Entry<String, List<String>> attr : rep.getAttributes().entrySet()) {
- user.setAttribute(attr.getKey(), attr.getValue());
- }
-
- for (String attr : attrsToRemove) {
- user.removeAttribute(attr);
- }
- }
- }
-
/**
* Get representation of the user
*
@@ -286,360 +191,17 @@ public class UsersResource {
* @return
*/
@Path("{id}")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public UserRepresentation getUser(final @PathParam("id") String id) {
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
-
- auth.users().requireView(user);
-
- UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, user);
-
- if (realm.isIdentityFederationEnabled()) {
- List<FederatedIdentityRepresentation> reps = getFederatedIdentities(user);
- rep.setFederatedIdentities(reps);
- }
-
- if (session.getProvider(BruteForceProtector.class).isTemporarilyDisabled(session, realm, user)) {
- rep.setEnabled(false);
- }
-
- return rep;
- }
-
- /**
- * Impersonate the user
- *
- * @param id User id
- * @return
- */
- @Path("{id}/impersonation")
- @POST
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public Map<String, Object> impersonate(final @PathParam("id") String id) {
- ProfileHelper.requireFeature(Profile.Feature.IMPERSONATION);
-
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireImpersonate(user);
- RealmModel authenticatedRealm = auth.adminAuth().getRealm();
- // if same realm logout before impersonation
- boolean sameRealm = false;
- if (authenticatedRealm.getId().equals(realm.getId())) {
- sameRealm = true;
- UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, auth.adminAuth().getToken().getSessionState());
- AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
- AuthenticationManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
- AuthenticationManager.backchannelLogout(session, authenticatedRealm, userSession, uriInfo, clientConnection, headers, true);
- }
- EventBuilder event = new EventBuilder(realm, session, clientConnection);
-
- String sessionId = KeycloakModelUtils.generateId();
- UserSessionModel userSession = session.sessions().createUserSession(sessionId, realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
- AuthenticationManager.createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
- URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName());
- Map<String, Object> result = new HashMap<>();
- result.put("sameRealm", sameRealm);
- result.put("redirect", redirect.toString());
- event.event(EventType.IMPERSONATE)
- .session(userSession)
- .user(user)
- .detail(Details.IMPERSONATOR_REALM,authenticatedRealm.getName())
- .detail(Details.IMPERSONATOR, auth.adminAuth().getUser().getUsername()).success();
-
- return result;
- }
-
-
- /**
- * Get sessions associated with the user
- *
- * @param id User id
- * @return
- */
- @Path("{id}/sessions")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public List<UserSessionRepresentation> getSessions(final @PathParam("id") String id) {
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireView(user);
- List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
- List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
- for (UserSessionModel session : sessions) {
- UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
- reps.add(rep);
- }
- return reps;
- }
-
- /**
- * Get offline sessions associated with the user and client
- *
- * @param id User id
- * @return
- */
- @Path("{id}/offline-sessions/{clientId}")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public List<UserSessionRepresentation> getOfflineSessions(final @PathParam("id") String id, final @PathParam("clientId") String clientId) {
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireView(user);
- ClientModel client = realm.getClientById(clientId);
- if (client == null) {
- throw new NotFoundException("Client not found");
- }
- List<UserSessionModel> sessions = new UserSessionManager(session).findOfflineSessions(realm, user);
- List<UserSessionRepresentation> reps = new ArrayList<UserSessionRepresentation>();
- for (UserSessionModel session : sessions) {
- UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
-
- // Update lastSessionRefresh with the timestamp from clientSession
- AuthenticatedClientSessionModel clientSession = session.getAuthenticatedClientSessions().get(clientId);
-
- // Skip if userSession is not for this client
- if (clientSession == null) {
- continue;
- }
-
- rep.setLastAccess(clientSession.getTimestamp());
-
- reps.add(rep);
- }
- return reps;
- }
-
- /**
- * Get social logins associated with the user
- *
- * @param id User id
- * @return
- */
- @Path("{id}/federated-identity")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public List<FederatedIdentityRepresentation> getFederatedIdentity(final @PathParam("id") String id) {
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireView(user);
-
- return getFederatedIdentities(user);
- }
-
- private List<FederatedIdentityRepresentation> getFederatedIdentities(UserModel user) {
- Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
- List<FederatedIdentityRepresentation> result = new ArrayList<FederatedIdentityRepresentation>();
-
- for (FederatedIdentityModel identity : identities) {
- for (IdentityProviderModel identityProviderModel : realm.getIdentityProviders()) {
- if (identityProviderModel.getAlias().equals(identity.getIdentityProvider())) {
- FederatedIdentityRepresentation rep = ModelToRepresentation.toRepresentation(identity);
- result.add(rep);
- }
- }
- }
- return result;
- }
-
- /**
- * Add a social login provider to the user
- *
- * @param id User id
- * @param provider Social login provider id
- * @param rep
- * @return
- */
- @Path("{id}/federated-identity/{provider}")
- @POST
- @NoCache
- public Response addFederatedIdentity(final @PathParam("id") String id, final @PathParam("provider") String provider, FederatedIdentityRepresentation rep) {
-
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireManage(user);
- if (session.users().getFederatedIdentity(user, provider, realm) != null) {
- return ErrorResponse.exists("User is already linked with provider");
- }
-
- FederatedIdentityModel socialLink = new FederatedIdentityModel(provider, rep.getUserId(), rep.getUserName());
- session.users().addFederatedIdentity(realm, user, socialLink);
- adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(rep).success();
- return Response.noContent().build();
- }
-
- /**
- * Remove a social login provider from user
- *
- * @param id User id
- * @param provider Social login provider id
- */
- @Path("{id}/federated-identity/{provider}")
- @DELETE
- @NoCache
- public void removeFederatedIdentity(final @PathParam("id") String id, final @PathParam("provider") String provider) {
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireManage(user);
- if (!session.users().removeFederatedIdentity(realm, user, provider)) {
- throw new NotFoundException("Link not found");
- }
- adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
- }
-
- /**
- * Get consents granted by the user
- *
- * @param id User id
- * @return
- */
- @Path("{id}/consents")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public List<Map<String, Object>> getConsents(final @PathParam("id") String id) {
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireView(user);
- List<Map<String, Object>> result = new LinkedList<>();
-
- Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
-
- for (ClientModel client : realm.getClients()) {
- UserConsentModel consent = session.users().getConsentByClient(realm, user.getId(), client.getId());
- boolean hasOfflineToken = offlineClients.contains(client);
-
- if (consent == null && !hasOfflineToken) {
- continue;
- }
-
- UserConsentRepresentation rep = (consent == null) ? null : ModelToRepresentation.toRepresentation(consent);
-
- Map<String, Object> currentRep = new HashMap<>();
- currentRep.put("clientId", client.getClientId());
- currentRep.put("grantedProtocolMappers", (rep==null ? Collections.emptyMap() : rep.getGrantedProtocolMappers()));
- currentRep.put("grantedRealmRoles", (rep==null ? Collections.emptyList() : rep.getGrantedRealmRoles()));
- currentRep.put("grantedClientRoles", (rep==null ? Collections.emptyMap() : rep.getGrantedClientRoles()));
- currentRep.put("createdDate", (rep==null ? null : rep.getCreatedDate()));
- currentRep.put("lastUpdatedDate", (rep==null ? null : rep.getLastUpdatedDate()));
-
- List<Map<String, String>> additionalGrants = new LinkedList<>();
- if (hasOfflineToken) {
- Map<String, String> offlineTokens = new HashMap<>();
- offlineTokens.put("client", client.getId());
- // TODO: translate
- offlineTokens.put("key", "Offline Token");
- additionalGrants.add(offlineTokens);
- }
- currentRep.put("additionalGrants", additionalGrants);
-
- result.add(currentRep);
- }
-
- return result;
- }
-
- /**
- * Revoke consent and offline tokens for particular client from user
- *
- * @param id User id
- * @param clientId Client id
- */
- @Path("{id}/consents/{client}")
- @DELETE
- @NoCache
- public void revokeConsent(final @PathParam("id") String id, final @PathParam("client") String clientId) {
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireManage(user);
-
- ClientModel client = realm.getClientByClientId(clientId);
- if (client == null) {
- throw new NotFoundException("Client not found");
- }
- boolean revokedConsent = session.users().revokeConsentForClient(realm, user.getId(), client.getId());
- boolean revokedOfflineToken = new UserSessionManager(session).revokeOfflineToken(user, client);
-
- if (revokedConsent) {
- // Logout clientSessions for this user and client
- AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
- }
-
- if (!revokedConsent && !revokedOfflineToken) {
- throw new NotFoundException("Consent nor offline token not found");
- }
- adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
- }
-
- /**
- * Remove all user sessions associated with the user
- *
- * Also send notification to all clients that have an admin URL to invalidate the sessions for the particular user.
- *
- * @param id User id
- */
- @Path("{id}/logout")
- @POST
- public void logout(final @PathParam("id") String id) {
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireManage(user);
-
- List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
- for (UserSessionModel userSession : userSessions) {
- AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
- }
- adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
- }
+ public UserResource user(final @PathParam("id") String id) {
+ auth.users().requireQuery();
- /**
- * Delete the user
- *
- * @param id User id
- */
- @Path("{id}")
- @DELETE
- @NoCache
- public Response deleteUser(final @PathParam("id") String id) {
UserModel user = session.users().getUserById(id, realm);
if (user == null) {
throw new NotFoundException("User not found");
}
- auth.users().requireManage(user);
-
- boolean removed = new UserManager(session).removeUser(realm, user);
- if (removed) {
- adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
- return Response.noContent().build();
- } else {
- return ErrorResponse.error("User couldn't be deleted", Response.Status.BAD_REQUEST);
- }
+ UserResource resource = new UserResource(realm, user, auth, adminEvent);
+ ResteasyProviderFactory.getInstance().injectProperties(resource);
+ //resourceContext.initResource(users);
+ return resource;
}
/**
@@ -711,315 +273,4 @@ public class UsersResource {
return session.users().getUsersCount(realm);
}
-
- @Path("{id}/role-mappings")
- public RoleMapperResource getRoleMappings(@PathParam("id") String id) {
-
- UserModel user = session.users().getUserById(id, realm);
-
- if (user == null) {
- throw new NotFoundException("User not found");
- }
-
- AdminPermissionEvaluator.RequirePermissionCheck manageCheck = () -> auth.users().requireManage(user);
- AdminPermissionEvaluator.RequirePermissionCheck viewCheck = () -> auth.users().requireView(user);
- RoleMapperResource resource = new RoleMapperResource(realm, auth, user, adminEvent, manageCheck, viewCheck);
- ResteasyProviderFactory.getInstance().injectProperties(resource);
- return resource;
-
- }
-
- /**
- * Disable all credentials for a user of a specific type
- *
- * @param id
- * @param credentialTypes
- */
- @Path("{id}/disable-credential-types")
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- public void disableCredentialType(@PathParam("id") String id, List<String> credentialTypes) {
-
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireManage(user);
- if (credentialTypes == null) return;
- for (String type : credentialTypes) {
- session.userCredentialManager().disableCredentialType(realm, user, type);
-
- }
-
-
- }
-
- /**
- * Set up a temporary password for the user
- *
- * User will have to reset the temporary password next time they log in.
- *
- * @param id User id
- * @param pass A Temporary password
- */
- @Path("{id}/reset-password")
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- public void resetPassword(@PathParam("id") String id, CredentialRepresentation pass) {
-
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireManage(user);
- if (pass == null || pass.getValue() == null || !CredentialRepresentation.PASSWORD.equals(pass.getType())) {
- throw new BadRequestException("No password provided");
- }
- if (Validation.isBlank(pass.getValue())) {
- throw new BadRequestException("Empty password not allowed");
- }
-
- UserCredentialModel cred = UserCredentialModel.password(pass.getValue(), true);
- try {
- session.userCredentialManager().updateCredential(realm, user, cred);
- } catch (IllegalStateException ise) {
- throw new BadRequestException("Resetting to N old passwords is not allowed.");
- } catch (ReadOnlyException mre) {
- throw new BadRequestException("Can't reset password as account is read only");
- } catch (ModelException e) {
- Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
- throw new ErrorResponseException(e.getMessage(), MessageFormat.format(messages.getProperty(e.getMessage(), e.getMessage()), e.getParameters()),
- Status.BAD_REQUEST);
- }
- if (pass.isTemporary() != null && pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
-
- adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
- }
-
- /**
- * Remove TOTP from the user
- *
- * @param id User id
- */
- @Path("{id}/remove-totp")
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- public void removeTotp(@PathParam("id") String id) {
-
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
-
- auth.users().requireManage(user);
-
-
- session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
- adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
- }
-
- /**
- * Send an email to the user with a link they can click to reset their password.
- * The redirectUri and clientId parameters are optional. The default for the
- * redirect is the account client.
- *
- * This endpoint has been deprecated. Please use the execute-actions-email passing a list with
- * UPDATE_PASSWORD within it.
- *
- * @param id
- * @param redirectUri redirect uri
- * @param clientId client id
- * @return
- */
- @Deprecated
- @Path("{id}/reset-password-email")
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- public Response resetPasswordEmail(@PathParam("id") String id,
- @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri,
- @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) {
- List<String> actions = new LinkedList<>();
- actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
- return executeActionsEmail(id, redirectUri, clientId, null, actions);
- }
-
-
- /**
- * Send a update account email to the user
- *
- * An email contains a link the user can click to perform a set of required actions.
- * The redirectUri and clientId parameters are optional. If no redirect is given, then there will
- * be no link back to click after actions have completed. Redirect uri must be a valid uri for the
- * particular clientId.
- *
- * @param id User is
- * @param redirectUri Redirect uri
- * @param clientId Client id
- * @param lifespan Number of seconds after which the generated token expires
- * @param actions required actions the user needs to complete
- * @return
- */
- @Path("{id}/execute-actions-email")
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- public Response executeActionsEmail(@PathParam("id") String id,
- @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri,
- @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId,
- @QueryParam("lifespan") Integer lifespan,
- List<String> actions) {
-
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- return ErrorResponse.error("User not found", Response.Status.NOT_FOUND);
- }
- auth.users().requireManage(user);
-
- if (user.getEmail() == null) {
- return ErrorResponse.error("User email missing", Response.Status.BAD_REQUEST);
- }
-
- if (!user.isEnabled()) {
- throw new WebApplicationException(
- ErrorResponse.error("User is disabled", Response.Status.BAD_REQUEST));
- }
-
- if (redirectUri != null && clientId == null) {
- throw new WebApplicationException(
- ErrorResponse.error("Client id missing", Response.Status.BAD_REQUEST));
- }
-
- if (clientId == null) {
- clientId = Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
- }
-
- ClientModel client = realm.getClientByClientId(clientId);
- if (client == null || !client.isEnabled()) {
- throw new WebApplicationException(
- ErrorResponse.error(clientId + " not enabled", Response.Status.BAD_REQUEST));
- }
-
- String redirect;
- if (redirectUri != null) {
- redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
- if (redirect == null) {
- throw new WebApplicationException(
- ErrorResponse.error("Invalid redirect uri.", Response.Status.BAD_REQUEST));
- }
- }
-
- if (lifespan == null) {
- lifespan = realm.getActionTokenGeneratedByAdminLifespan();
- }
- int expiration = Time.currentTime() + lifespan;
- ExecuteActionsActionToken token = new ExecuteActionsActionToken(id, expiration, actions, redirectUri, clientId);
-
- try {
- UriBuilder builder = LoginActionsService.actionTokenProcessor(uriInfo);
- builder.queryParam("key", token.serialize(session, realm, uriInfo));
-
- String link = builder.build(realm.getName()).toString();
-
- this.session.getProvider(EmailTemplateProvider.class)
- .setRealm(realm)
- .setUser(user)
- .sendExecuteActions(link, TimeUnit.SECONDS.toMinutes(lifespan));
-
- //audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
-
- adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
-
- return Response.ok().build();
- } catch (EmailException e) {
- ServicesLogger.LOGGER.failedToSendActionsEmail(e);
- return ErrorResponse.error("Failed to send execute actions email", Response.Status.INTERNAL_SERVER_ERROR);
- }
- }
-
- /**
- * Send an email-verification email to the user
- *
- * An email contains a link the user can click to verify their email address.
- * The redirectUri and clientId parameters are optional. The default for the
- * redirect is the account client.
- *
- * @param id User id
- * @param redirectUri Redirect uri
- * @param clientId Client id
- * @return
- */
- @Path("{id}/send-verify-email")
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- public Response sendVerifyEmail(@PathParam("id") String id, @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) {
- List<String> actions = new LinkedList<>();
- actions.add(UserModel.RequiredAction.VERIFY_EMAIL.name());
- return executeActionsEmail(id, redirectUri, clientId, null, actions);
- }
-
- @GET
- @Path("{id}/groups")
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public List<GroupRepresentation> groupMembership(@PathParam("id") String id) {
-
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireView(user);
- List<GroupRepresentation> memberships = new LinkedList<>();
- for (GroupModel group : user.getGroups()) {
- memberships.add(ModelToRepresentation.toRepresentation(group, false));
- }
- return memberships;
- }
-
- @DELETE
- @Path("{id}/groups/{groupId}")
- @NoCache
- public void removeMembership(@PathParam("id") String id, @PathParam("groupId") String groupId) {
-
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireManage(user);
-
- GroupModel group = session.realms().getGroupById(groupId, realm);
- if (group == null) {
- throw new NotFoundException("Group not found");
- }
-
- try {
- if (user.isMemberOf(group)){
- user.leaveGroup(group);
- adminEvent.operation(OperationType.DELETE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(uriInfo).success();
- }
- } catch (ModelException me) {
- Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale());
- throw new ErrorResponseException(me.getMessage(), MessageFormat.format(messages.getProperty(me.getMessage(), me.getMessage()), me.getParameters()),
- Response.Status.BAD_REQUEST);
- }
- }
-
- @PUT
- @Path("{id}/groups/{groupId}")
- @NoCache
- public void joinGroup(@PathParam("id") String id, @PathParam("groupId") String groupId) {
-
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- auth.users().requireManage(user);
- GroupModel group = session.realms().getGroupById(groupId, realm);
- if (group == null) {
- throw new NotFoundException("Group not found");
- }
- if (!user.isMemberOf(group)){
- user.joinGroup(group);
- adminEvent.operation(OperationType.CREATE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(uriInfo).success();
- }
- }
-
}
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 333a1cf..eed2858 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -30,6 +30,7 @@ import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.provider.IdentityProviderMapper;
+import org.keycloak.broker.provider.util.IdentityBrokerState;
import org.keycloak.broker.saml.SAMLEndpoint;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.ClientConnection;
@@ -56,6 +57,8 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
+import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
@@ -338,14 +341,14 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@POST
@Path("/{provider_id}/login")
- public Response performPostLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
- return performLogin(providerId, code);
+ public Response performPostLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code, @QueryParam("client_id") String clientId) {
+ return performLogin(providerId, code, clientId);
}
@GET
@NoCache
@Path("/{provider_id}/login")
- public Response performLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
+ public Response performLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code, @QueryParam("client_id") String clientId) {
this.event.detail(Details.IDENTITY_PROVIDER, providerId);
if (isDebugEnabled()) {
@@ -353,7 +356,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
try {
- ParsedCodeContext parsedCode = parseClientSessionCode(code);
+ ParsedCodeContext parsedCode = parseSessionCode(code, clientId);
if (parsedCode.response != null) {
return parsedCode.response;
}
@@ -479,7 +482,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
if (context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID) != null) {
parsedCode = samlIdpInitiatedSSO((String) context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID));
} else {
- parsedCode = parseClientSessionCode(context.getCode());
+ parsedCode = parseEncodedSessionCode(context.getCode());
}
if (parsedCode.response != null) {
return parsedCode.response;
@@ -549,6 +552,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
ctx.saveToAuthenticationSession(authenticationSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
+ .queryParam(Constants.CLIENT_ID, authenticationSession.getClient().getClientId())
.build(realmModel.getName());
return Response.status(302).location(redirect).build();
@@ -584,8 +588,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@GET
@NoCache
@Path("/after-first-broker-login")
- public Response afterFirstBrokerLogin(@QueryParam("code") String code) {
- ParsedCodeContext parsedCode = parseClientSessionCode(code);
+ public Response afterFirstBrokerLogin(@QueryParam("code") String code, @QueryParam("client_id") String clientId) {
+ ParsedCodeContext parsedCode = parseSessionCode(code, clientId);
if (parsedCode.response != null) {
return parsedCode.response;
}
@@ -701,6 +705,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
authSession.setAuthNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN, String.valueOf(wasFirstBrokerLogin));
URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo)
+ .queryParam(Constants.CLIENT_ID, authSession.getClient().getClientId())
.build(realmModel.getName());
return Response.status(302).location(redirect).build();
}
@@ -711,8 +716,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@GET
@NoCache
@Path("/after-post-broker-login")
- public Response afterPostBrokerLoginFlow(@QueryParam("code") String code) {
- ParsedCodeContext parsedCode = parseClientSessionCode(code);
+ public Response afterPostBrokerLoginFlow(@QueryParam("code") String code, @QueryParam("client_id") String clientId) {
+ ParsedCodeContext parsedCode = parseSessionCode(code, clientId);
if (parsedCode.response != null) {
return parsedCode.response;
}
@@ -804,7 +809,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@Override
public Response cancelled(String code) {
- ParsedCodeContext parsedCode = parseClientSessionCode(code);
+ ParsedCodeContext parsedCode = parseEncodedSessionCode(code);
if (parsedCode.response != null) {
return parsedCode.response;
}
@@ -820,7 +825,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@Override
public Response error(String code, String message) {
- ParsedCodeContext parsedCode = parseClientSessionCode(code);
+ ParsedCodeContext parsedCode = parseEncodedSessionCode(code);
if (parsedCode.response != null) {
return parsedCode.response;
}
@@ -960,14 +965,21 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
}
- private ParsedCodeContext parseClientSessionCode(String code) {
- if (code == null) {
- logger.debugf("Invalid request. Authorization code was null");
+ private ParsedCodeContext parseEncodedSessionCode(String encodedCode) {
+ IdentityBrokerState state = IdentityBrokerState.encoded(encodedCode);
+ String code = state.getDecodedState();
+ String clientId = state.getClientId();
+ return parseSessionCode(code, clientId);
+ }
+
+ private ParsedCodeContext parseSessionCode(String code, String clientId) {
+ if (code == null || clientId == null) {
+ logger.debugf("Invalid request. Authorization code or clientId was null. Code=" + code + ", clientId=" + clientId);
Response staleCodeError = redirectToErrorPage(Messages.INVALID_REQUEST);
return ParsedCodeContext.response(staleCodeError);
}
- SessionCodeChecks checks = new SessionCodeChecks(realmModel, uriInfo, clientConnection, session, event, code, null, LoginActionsService.AUTHENTICATE_PATH);
+ SessionCodeChecks checks = new SessionCodeChecks(realmModel, uriInfo, clientConnection, session, event, code, null, clientId, LoginActionsService.AUTHENTICATE_PATH);
checks.initialVerify();
if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
@@ -1017,7 +1029,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return ParsedCodeContext.response(redirectToErrorPage(Messages.CLIENT_NOT_FOUND));
}
- SamlService samlService = new SamlService(realmModel, event);
+ LoginProtocolFactory factory = (LoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, SamlProtocol.LOGIN_PROTOCOL);
+ SamlService samlService = (SamlService) factory.createProtocolEndpoint(realmModel, event);
ResteasyProviderFactory.getInstance().injectProperties(samlService);
AuthenticationSessionModel authSession = samlService.getOrCreateLoginSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);
@@ -1041,14 +1054,15 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
private AuthenticationRequest createAuthenticationRequest(String providerId, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
AuthenticationSessionModel authSession = null;
- String relayState = null;
+ IdentityBrokerState encodedState = null;
if (clientSessionCode != null) {
authSession = clientSessionCode.getClientSession();
- relayState = clientSessionCode.getCode();
+ String relayState = clientSessionCode.getCode();
+ encodedState = IdentityBrokerState.decoded(relayState, authSession.getClient().getClientId());
}
- return new AuthenticationRequest(this.session, this.realmModel, authSession, this.request, this.uriInfo, relayState, getRedirectUri(providerId));
+ return new AuthenticationRequest(this.session, this.realmModel, authSession, this.request, this.uriInfo, encodedState, getRedirectUri(providerId));
}
private String getRedirectUri(String providerId) {
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 8f0d39e..b1bd354 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -45,7 +45,6 @@ import org.keycloak.exceptions.TokenNotActiveException;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
@@ -179,16 +178,16 @@ public class LoginActionsService {
}
}
- private SessionCodeChecks checksForCode(String code, String execution, String flowPath) {
- SessionCodeChecks res = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, code, execution, flowPath);
+ private SessionCodeChecks checksForCode(String code, String execution, String clientId, String flowPath) {
+ SessionCodeChecks res = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, code, execution, clientId, flowPath);
res.initialVerify();
return res;
}
- protected URI getLastExecutionUrl(String flowPath, String executionId) {
+ protected URI getLastExecutionUrl(String flowPath, String executionId, String clientId) {
return new AuthenticationFlowURLHelper(session, realm, uriInfo)
- .getLastExecutionUrl(flowPath, executionId);
+ .getLastExecutionUrl(flowPath, executionId, clientId);
}
@@ -199,9 +198,9 @@ public class LoginActionsService {
*/
@Path(RESTART_PATH)
@GET
- public Response restartSession() {
+ public Response restartSession(@QueryParam("client_id") String clientId) {
event.event(EventType.RESTART_AUTHENTICATION);
- SessionCodeChecks checks = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, null, null, null);
+ SessionCodeChecks checks = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, null, null, clientId, null);
AuthenticationSessionModel authSession = checks.initialVerifyAuthSession();
if (authSession == null) {
@@ -215,7 +214,7 @@ public class LoginActionsService {
AuthenticationProcessor.resetFlow(authSession, flowPath);
- URI redirectUri = getLastExecutionUrl(flowPath, null);
+ URI redirectUri = getLastExecutionUrl(flowPath, null, authSession.getClient().getClientId());
logger.debugf("Flow restart requested. Redirecting to %s", redirectUri);
return Response.status(Response.Status.FOUND).location(redirectUri).build();
}
@@ -230,11 +229,12 @@ public class LoginActionsService {
@Path(AUTHENTICATE_PATH)
@GET
public Response authenticate(@QueryParam("code") String code,
- @QueryParam("execution") String execution) {
+ @QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId) {
event.event(EventType.LOGIN);
- SessionCodeChecks checks = checksForCode(code, execution, AUTHENTICATE_PATH);
- if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+ SessionCodeChecks checks = checksForCode(code, execution, clientId, AUTHENTICATE_PATH);
+ if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.getResponse();
}
@@ -298,26 +298,28 @@ public class LoginActionsService {
@Path(AUTHENTICATE_PATH)
@POST
public Response authenticateForm(@QueryParam("code") String code,
- @QueryParam("execution") String execution) {
- return authenticate(code, execution);
+ @QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId) {
+ return authenticate(code, execution, clientId);
}
@Path(RESET_CREDENTIALS_PATH)
@POST
public Response resetCredentialsPOST(@QueryParam("code") String code,
@QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId,
@QueryParam(Constants.KEY) String key) {
if (key != null) {
- return handleActionToken(key, execution);
+ return handleActionToken(key, execution, clientId);
}
event.event(EventType.RESET_PASSWORD);
- return resetCredentials(code, execution);
+ return resetCredentials(code, execution, clientId);
}
/**
- * Endpoint for executing reset credentials flow. If token is null, a client session is created with the account
+ * Endpoint for executing reset credentials flow. If token is null, a authentication session is created with the account
* service as the client. Successful reset sends you to the account page. Note, account service must be enabled.
*
* @param code
@@ -327,7 +329,8 @@ public class LoginActionsService {
@Path(RESET_CREDENTIALS_PATH)
@GET
public Response resetCredentialsGET(@QueryParam("code") String code,
- @QueryParam("execution") String execution) {
+ @QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId) {
AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm);
// we allow applications to link to reset credentials without going through OAuth or SAML handshakes
@@ -343,7 +346,7 @@ public class LoginActionsService {
}
event.event(EventType.RESET_PASSWORD);
- return resetCredentials(code, execution);
+ return resetCredentials(code, execution, clientId);
}
AuthenticationSessionModel createAuthenticationSessionForClient()
@@ -353,7 +356,7 @@ public class LoginActionsService {
// set up the account service as the endpoint to call.
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, true);
- authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+ authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
//authSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
@@ -370,9 +373,9 @@ public class LoginActionsService {
* @param execution
* @return
*/
- protected Response resetCredentials(String code, String execution) {
- SessionCodeChecks checks = checksForCode(code, execution, RESET_CREDENTIALS_PATH);
- if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
+ protected Response resetCredentials(String code, String execution, String clientId) {
+ SessionCodeChecks checks = checksForCode(code, execution, clientId, RESET_CREDENTIALS_PATH);
+ if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
return checks.getResponse();
}
final AuthenticationSessionModel authSession = checks.getAuthenticationSession();
@@ -397,11 +400,12 @@ public class LoginActionsService {
@Path("action-token")
@GET
public Response executeActionToken(@QueryParam("key") String key,
- @QueryParam("execution") String execution) {
- return handleActionToken(key, execution);
+ @QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId) {
+ return handleActionToken(key, execution, clientId);
}
- protected <T extends DefaultActionToken> Response handleActionToken(String tokenString, String execution) {
+ protected <T extends DefaultActionToken> Response handleActionToken(String tokenString, String execution, String clientId) {
T token;
ActionTokenHandler<T> handler;
ActionTokenContext<T> tokenContext;
@@ -411,6 +415,15 @@ public class LoginActionsService {
event.event(EventType.EXECUTE_ACTION_TOKEN);
+ // Setup client, so error page will contain "back to application" link
+ ClientModel client = null;
+ if (clientId != null) {
+ client = realm.getClientByClientId(clientId);
+ }
+ if (client != null) {
+ session.getContext().setClient(client);
+ }
+
// First resolve action token handler
try {
if (tokenString == null) {
@@ -570,8 +583,9 @@ public class LoginActionsService {
@Path(REGISTRATION_PATH)
@GET
public Response registerPage(@QueryParam("code") String code,
- @QueryParam("execution") String execution) {
- return registerRequest(code, execution, false);
+ @QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId) {
+ return registerRequest(code, execution, clientId, false);
}
@@ -584,20 +598,21 @@ public class LoginActionsService {
@Path(REGISTRATION_PATH)
@POST
public Response processRegister(@QueryParam("code") String code,
- @QueryParam("execution") String execution) {
- return registerRequest(code, execution, true);
+ @QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId) {
+ return registerRequest(code, execution, clientId, true);
}
- private Response registerRequest(String code, String execution, boolean isPostRequest) {
+ private Response registerRequest(String code, String execution, String clientId, boolean isPostRequest) {
event.event(EventType.REGISTER);
if (!realm.isRegistrationAllowed()) {
event.error(Errors.REGISTRATION_DISABLED);
return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED);
}
- SessionCodeChecks checks = checksForCode(code, execution, REGISTRATION_PATH);
- if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+ SessionCodeChecks checks = checksForCode(code, execution, clientId, REGISTRATION_PATH);
+ if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.getResponse();
}
@@ -612,40 +627,44 @@ public class LoginActionsService {
@Path(FIRST_BROKER_LOGIN_PATH)
@GET
public Response firstBrokerLoginGet(@QueryParam("code") String code,
- @QueryParam("execution") String execution) {
- return brokerLoginFlow(code, execution, FIRST_BROKER_LOGIN_PATH);
+ @QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId) {
+ return brokerLoginFlow(code, execution, clientId, FIRST_BROKER_LOGIN_PATH);
}
@Path(FIRST_BROKER_LOGIN_PATH)
@POST
public Response firstBrokerLoginPost(@QueryParam("code") String code,
- @QueryParam("execution") String execution) {
- return brokerLoginFlow(code, execution, FIRST_BROKER_LOGIN_PATH);
+ @QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId) {
+ return brokerLoginFlow(code, execution, clientId, FIRST_BROKER_LOGIN_PATH);
}
@Path(POST_BROKER_LOGIN_PATH)
@GET
public Response postBrokerLoginGet(@QueryParam("code") String code,
- @QueryParam("execution") String execution) {
- return brokerLoginFlow(code, execution, POST_BROKER_LOGIN_PATH);
+ @QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId) {
+ return brokerLoginFlow(code, execution, clientId, POST_BROKER_LOGIN_PATH);
}
@Path(POST_BROKER_LOGIN_PATH)
@POST
public Response postBrokerLoginPost(@QueryParam("code") String code,
- @QueryParam("execution") String execution) {
- return brokerLoginFlow(code, execution, POST_BROKER_LOGIN_PATH);
+ @QueryParam("execution") String execution,
+ @QueryParam("client_id") String clientId) {
+ return brokerLoginFlow(code, execution, clientId, POST_BROKER_LOGIN_PATH);
}
- protected Response brokerLoginFlow(String code, String execution, String flowPath) {
+ protected Response brokerLoginFlow(String code, String execution, String clientId, String flowPath) {
boolean firstBrokerLogin = flowPath.equals(FIRST_BROKER_LOGIN_PATH);
EventType eventType = firstBrokerLogin ? EventType.IDENTITY_PROVIDER_FIRST_LOGIN : EventType.IDENTITY_PROVIDER_POST_LOGIN;
event.event(eventType);
- SessionCodeChecks checks = checksForCode(code, execution, flowPath);
- if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+ SessionCodeChecks checks = checksForCode(code, execution, clientId, flowPath);
+ if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.getResponse();
}
event.detail(Details.CODE_ID, code);
@@ -655,7 +674,7 @@ public class LoginActionsService {
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, noteKey);
if (serializedCtx == null) {
ServicesLogger.LOGGER.notFoundSerializedCtxInClientSession(noteKey);
- throw new WebApplicationException(ErrorPage.error(session, "Not found serialized context in clientSession."));
+ throw new WebApplicationException(ErrorPage.error(session, "Not found serialized context in authenticationSession."));
}
BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, authSession);
final String identityProviderAlias = brokerContext.getIdpConfig().getAlias();
@@ -702,8 +721,9 @@ public class LoginActionsService {
ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authSession);
authSession.setTimestamp(Time.currentTime());
- URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) :
- Urls.identityProviderAfterPostBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) ;
+ String clientId = authSession.getClient().getClientId();
+ URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode(), clientId) :
+ Urls.identityProviderAfterPostBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode(), clientId) ;
logger.debugf("Redirecting to '%s' ", redirect);
return Response.status(302).location(redirect).build();
@@ -722,8 +742,9 @@ public class LoginActionsService {
public Response processConsent(final MultivaluedMap<String, String> formData) {
event.event(EventType.LOGIN);
String code = formData.getFirst("code");
- SessionCodeChecks checks = checksForCode(code, null, REQUIRED_ACTION);
- if (!checks.verifyRequiredAction(ClientSessionModel.Action.OAUTH_GRANT.name())) {
+ String clientId = uriInfo.getQueryParameters().getFirst(Constants.CLIENT_ID);
+ SessionCodeChecks checks = checksForCode(code, null, clientId, REQUIRED_ACTION);
+ if (!checks.verifyRequiredAction(AuthenticationSessionModel.Action.OAUTH_GRANT.name())) {
return checks.getResponse();
}
@@ -811,21 +832,23 @@ public class LoginActionsService {
@Path(REQUIRED_ACTION)
@POST
public Response requiredActionPOST(@QueryParam("code") final String code,
- @QueryParam("execution") String action) {
- return processRequireAction(code, action);
+ @QueryParam("execution") String action,
+ @QueryParam("client_id") String clientId) {
+ return processRequireAction(code, action, clientId);
}
@Path(REQUIRED_ACTION)
@GET
public Response requiredActionGET(@QueryParam("code") final String code,
- @QueryParam("execution") String action) {
- return processRequireAction(code, action);
+ @QueryParam("execution") String action,
+ @QueryParam("client_id") String clientId) {
+ return processRequireAction(code, action, clientId);
}
- private Response processRequireAction(final String code, String action) {
+ private Response processRequireAction(final String code, String action, String clientId) {
event.event(EventType.CUSTOM_REQUIRED_ACTION);
- SessionCodeChecks checks = checksForCode(code, action, REQUIRED_ACTION);
+ SessionCodeChecks checks = checksForCode(code, action, clientId, REQUIRED_ACTION);
if (!checks.verifyRequiredAction(action)) {
return checks.getResponse();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
index 87eaf20..9edc513 100644
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
@@ -82,7 +82,7 @@ public class LoginActionsServiceChecks {
private final ActionTokenContext<?> context;
- private final ClientSessionModel.Action expectedAction;
+ private final AuthenticationSessionModel.Action expectedAction;
public IsActionRequired(ActionTokenContext<?> context, Action expectedAction) {
this.context = context;
@@ -94,7 +94,7 @@ public class LoginActionsServiceChecks {
AuthenticationSessionModel authSession = context.getAuthenticationSession();
if (authSession != null && ! Objects.equals(authSession.getAction(), this.expectedAction.name())) {
- if (Objects.equals(ClientSessionModel.Action.REQUIRED_ACTIONS.name(), authSession.getAction())) {
+ if (Objects.equals(AuthenticationSessionModel.Action.REQUIRED_ACTIONS.name(), authSession.getAction())) {
throw new LoginActionsServiceException(
AuthenticationManager.nextActionAfterAuthentication(context.getSession(), authSession,
context.getClientConnection(), context.getRequest(), context.getUriInfo(), context.getEvent()));
@@ -120,16 +120,8 @@ public class LoginActionsServiceChecks {
LoginFormsProvider loginForm = context.getSession().getProvider(LoginFormsProvider.class)
.setSuccess(Messages.ALREADY_LOGGED_IN);
- ClientModel client = null;
- String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
- if (lastClientUuid != null) {
- client = context.getRealm().getClientById(lastClientUuid);
- }
-
- if (client != null) {
- context.getSession().getContext().setClient(client);
- } else {
- loginForm.setAttribute("skipLink", true);
+ if (context.getSession().getContext().getClient() == null) {
+ loginForm.setAttribute(Constants.SKIP_LINK, true);
}
throw new LoginActionsServiceException(loginForm.createInfoPage());
diff --git a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
index 978ad6f..0f3ebbe 100644
--- a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
+++ b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
@@ -32,7 +32,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@@ -40,7 +40,6 @@ import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.ServicesLogger;
-import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
@@ -66,10 +65,11 @@ public class SessionCodeChecks {
private final String code;
private final String execution;
+ private final String clientId;
private final String flowPath;
- public SessionCodeChecks(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, KeycloakSession session, EventBuilder event, String code, String execution, String flowPath) {
+ public SessionCodeChecks(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, KeycloakSession session, EventBuilder event, String code, String execution, String clientId, String flowPath) {
this.realm = realm;
this.uriInfo = uriInfo;
this.clientConnection = clientConnection;
@@ -78,6 +78,7 @@ public class SessionCodeChecks {
this.code = code;
this.execution = execution;
+ this.clientId = clientId;
this.flowPath = flowPath;
}
@@ -134,6 +135,16 @@ public class SessionCodeChecks {
return authSession;
}
+ // Setup client to be shown on error/info page based on "client_id" parameter
+ logger.debugf("Will use client '%s' in back-to-application link", clientId);
+ ClientModel client = null;
+ if (clientId != null) {
+ client = realm.getClientByClientId(clientId);
+ }
+ if (client != null) {
+ session.getContext().setClient(client);
+ }
+
// See if we are already authenticated and userSession with same ID exists.
String sessionId = new AuthenticationSessionManager(session).getCurrentAuthenticationSessionId(realm);
if (sessionId != null) {
@@ -143,16 +154,8 @@ public class SessionCodeChecks {
LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
.setSuccess(Messages.ALREADY_LOGGED_IN);
- ClientModel client = null;
- String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
- if (lastClientUuid != null) {
- client = realm.getClientById(lastClientUuid);
- }
-
- if (client != null) {
- session.getContext().setClient(client);
- } else {
- loginForm.setAttribute("skipLink", true);
+ if (client == null) {
+ loginForm.setAttribute(Constants.SKIP_LINK, true);
}
response = loginForm.createInfoPage();
@@ -210,7 +213,7 @@ public class SessionCodeChecks {
logger.debugf("Transition between flows! Current flow: %s, Previous flow: %s", flowPath, lastFlow);
// Don't allow moving to different flow if I am on requiredActions already
- if (ClientSessionModel.Action.AUTHENTICATE.name().equals(authSession.getAction())) {
+ if (AuthenticationSessionModel.Action.AUTHENTICATE.name().equals(authSession.getAction())) {
authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, flowPath);
authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
lastExecFromSession = null;
@@ -234,7 +237,7 @@ public class SessionCodeChecks {
// In case that is replayed action, but sent to the same FORM like actual FORM, we just re-render the page
if (ObjectUtil.isEqualOrBothNull(execution, authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION))) {
String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
- URI redirectUri = getLastExecutionUrl(latestFlowPath, execution);
+ URI redirectUri = getLastExecutionUrl(latestFlowPath, execution, client.getClientId());
logger.debugf("Invalid action code, but execution matches. So just redirecting to %s", redirectUri);
authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.EXPIRED_ACTION);
@@ -266,7 +269,7 @@ public class SessionCodeChecks {
if (!clientCode.isValidAction(expectedAction)) {
AuthenticationSessionModel authSession = getAuthenticationSession();
- if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(authSession.getAction())) {
+ if (AuthenticationSessionModel.Action.REQUIRED_ACTIONS.name().equals(authSession.getAction())) {
logger.debugf("Incorrect action '%s' . User authenticated already.", authSession.getAction());
response = showPageExpired(authSession);
return false;
@@ -289,7 +292,7 @@ public class SessionCodeChecks {
authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.LOGIN_TIMEOUT);
- URI redirectUri = getLastExecutionUrl(LoginActionsService.AUTHENTICATE_PATH, null);
+ URI redirectUri = getLastExecutionUrl(LoginActionsService.AUTHENTICATE_PATH, null, authSession.getClient().getClientId());
logger.debugf("Flow restart after timeout. Redirecting to %s", redirectUri);
response = Response.status(Response.Status.FOUND).location(redirectUri).build();
return false;
@@ -303,7 +306,7 @@ public class SessionCodeChecks {
return false;
}
- if (!clientCode.isValidAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
+ if (!clientCode.isValidAction(AuthenticationSessionModel.Action.REQUIRED_ACTIONS.name())) {
logger.debugf("Expected required action, but session action is '%s' . Showing expired page now.", authSession.getAction());
event.error(Errors.INVALID_CODE);
@@ -351,7 +354,7 @@ public class SessionCodeChecks {
flowPath = LoginActionsService.AUTHENTICATE_PATH;
}
- URI redirectUri = getLastExecutionUrl(flowPath, null);
+ URI redirectUri = getLastExecutionUrl(flowPath, null, authSession.getClient().getClientId());
logger.debugf("Authentication session restart from cookie succeeded. Redirecting to %s", redirectUri);
return Response.status(Response.Status.FOUND).location(redirectUri).build();
} else {
@@ -367,16 +370,20 @@ public class SessionCodeChecks {
.path(LoginActionsService.REQUIRED_ACTION);
if (action != null) {
- uriBuilder.queryParam("execution", action);
+ uriBuilder.queryParam(Constants.EXECUTION, action);
}
+
+ ClientModel client = authSession.getClient();
+ uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
+
URI redirect = uriBuilder.build(realm.getName());
return Response.status(302).location(redirect).build();
}
- private URI getLastExecutionUrl(String flowPath, String executionId) {
+ private URI getLastExecutionUrl(String flowPath, String executionId, String clientId) {
return new AuthenticationFlowURLHelper(session, realm, uriInfo)
- .getLastExecutionUrl(flowPath, executionId);
+ .getLastExecutionUrl(flowPath, executionId, clientId);
}
diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java
index e92aa05..51f505e 100755
--- a/services/src/main/java/org/keycloak/services/Urls.java
+++ b/services/src/main/java/org/keycloak/services/Urls.java
@@ -18,6 +18,7 @@ package org.keycloak.services;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.Version;
+import org.keycloak.models.Constants;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.services.resources.AccountService;
@@ -73,13 +74,16 @@ public class Urls {
.build(realmName, providerId);
}
- public static URI identityProviderAuthnRequest(URI baseUri, String providerId, String realmName, String accessCode) {
+ public static URI identityProviderAuthnRequest(URI baseUri, String providerId, String realmName, String accessCode, String clientId) {
UriBuilder uriBuilder = realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
.path(IdentityBrokerService.class, "performLogin");
if (accessCode != null) {
uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
}
+ if (clientId != null) {
+ uriBuilder.replaceQueryParam(Constants.CLIENT_ID, clientId);
+ }
return uriBuilder.build(realmName, providerId);
}
@@ -99,20 +103,22 @@ public class Urls {
}
public static URI identityProviderAuthnRequest(URI baseURI, String providerId, String realmName) {
- return identityProviderAuthnRequest(baseURI, providerId, realmName, null);
+ return identityProviderAuthnRequest(baseURI, providerId, realmName, null, null);
}
- public static URI identityProviderAfterFirstBrokerLogin(URI baseUri, String realmName, String accessCode) {
+ public static URI identityProviderAfterFirstBrokerLogin(URI baseUri, String realmName, String accessCode, String clientId) {
return realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
.path(IdentityBrokerService.class, "afterFirstBrokerLogin")
.replaceQueryParam(OAuth2Constants.CODE, accessCode)
+ .replaceQueryParam(Constants.CLIENT_ID, clientId)
.build(realmName);
}
- public static URI identityProviderAfterPostBrokerLogin(URI baseUri, String realmName, String accessCode) {
+ public static URI identityProviderAfterPostBrokerLogin(URI baseUri, String realmName, String accessCode, String clientId) {
return realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
.path(IdentityBrokerService.class, "afterPostBrokerLoginFlow")
.replaceQueryParam(OAuth2Constants.CODE, accessCode)
+ .replaceQueryParam(Constants.CLIENT_ID, clientId)
.build(realmName);
}
diff --git a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
index b97963e..3726b99 100644
--- a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
@@ -26,6 +26,7 @@ import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.AuthorizationEndpointBase;
@@ -61,13 +62,15 @@ public class AuthenticationFlowURLHelper {
}
- public URI getLastExecutionUrl(String flowPath, String executionId) {
+ public URI getLastExecutionUrl(String flowPath, String executionId, String clientId) {
UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
.path(flowPath);
if (executionId != null) {
- uriBuilder.queryParam("execution", executionId);
+ uriBuilder.queryParam(Constants.EXECUTION, executionId);
}
+ uriBuilder.queryParam(Constants.CLIENT_ID, clientId);
+
return uriBuilder.build(realm.getName());
}
@@ -84,7 +87,7 @@ public class AuthenticationFlowURLHelper {
latestFlowPath = LoginActionsService.AUTHENTICATE_PATH;
}
- return getLastExecutionUrl(latestFlowPath, executionId);
+ return getLastExecutionUrl(latestFlowPath, executionId, authSession.getClient().getClientId());
}
}
diff --git a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
index 00be27c..77009e7 100755
--- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -74,7 +74,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(getConfig().getClientId(), getConfig().getClientSecret());
- URI uri = new URI(request.getRedirectUri() + "?state=" + request.getState());
+ URI uri = new URI(request.getRedirectUri() + "?state=" + request.getState().getEncodedState());
RequestToken requestToken = twitter.getOAuthRequestToken(uri.toString());
AuthenticationSessionModel authSession = request.getAuthenticationSession();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index 4a2ce96..a2ec8bb 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -19,20 +19,18 @@ package org.keycloak.testsuite.adapter;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
-import org.keycloak.common.util.Encode;
-import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import java.net.URL;
-import java.security.PublicKey;
/**
* Tests Undertow Adapter
*
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ * @author <a href="mailto:john.ament@spartasystems.com">John Ament</a>
*/
public class AdapterTest {
@@ -93,6 +91,12 @@ public class AdapterTest {
.name("input-portal").contextPath("/input-portal")
.servletClass(InputServlet.class).adapterConfigPath(url.getPath())
.role("user").constraintUrl("/secured/*").deployApplication();
+
+ url = getClass().getResource("/adapter-test/no-access-token.json");
+ createApplicationDeployment()
+ .name("no-access-token").contextPath("/no-access-token")
+ .servletClass(InputServlet.class).adapterConfigPath(url.getPath())
+ .role("user").constraintUrl("/secured/*").deployApplication();
}
};
@@ -237,4 +241,9 @@ public class AdapterTest {
testStrategy.testRestCallWithAccessTokenAsQueryParameter();
}
+
+ @Test
+ public void testCallURLWithAccessToken() throws Exception {
+ testStrategy.checkThatAccessTokenCanBeSentPublicly();
+ }
}
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 bd0a144..e006820 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
@@ -67,6 +67,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* Tests Undertow Adapter
*
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ * @author <a href="mailto:john.ament@spartasystems.com">John Ament</a>
*/
public class AdapterTestStrategy extends ExternalResource {
@@ -814,4 +815,13 @@ public class AdapterTestStrategy extends ExternalResource {
}
+ void checkThatAccessTokenCanBeSentPublicly() {
+ // test login to customer-portal which does a bearer request to customer-db
+ final String applicationURL = APP_SERVER_BASE_URL + "/no-access-token?access_token=invalid_token";
+ driver.navigate().to(applicationURL);
+ System.out.println("Current url: " + driver.getCurrentUrl());
+ Assert.assertEquals(applicationURL, driver.getCurrentUrl());
+ inputPage.execute("hello");
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java
index 086bf25..49e0621 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java
@@ -61,7 +61,7 @@ public class LDAPLegacyImportTest {
// This test is executed just for the embedded LDAP server
private static LDAPRule ldapRule = new LDAPRule((Map<String, String> ldapConfig) -> {
- return Boolean.parseBoolean(ldapConfig.get("startEmbeddedLdapServer"));
+ return !Boolean.parseBoolean(ldapConfig.get("startEmbeddedLdapServer"));
});
private static ComponentModel ldapModel = null;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/PasswordPolicyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/PasswordPolicyTest.java
index e21fb0b..93ee368 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/PasswordPolicyTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/PasswordPolicyTest.java
@@ -20,12 +20,14 @@ package org.keycloak.testsuite.model;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.keycloak.models.ModelException;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.policy.PasswordPolicyManagerProvider;
import java.util.regex.PatternSyntaxException;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
@@ -123,7 +125,8 @@ public class PasswordPolicyTest extends AbstractModelTest {
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "noSuchPolicy"));
Assert.fail("Expected exception");
- } catch (IllegalArgumentException e) {
+ } catch (ModelException e) {
+ assertEquals("Password policy not found", e.getMessage());
}
}
@@ -133,22 +136,22 @@ public class PasswordPolicyTest extends AbstractModelTest {
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern"));
fail("Expected NullPointerException: Regex Pattern cannot be null.");
- } catch (IllegalArgumentException e) {
- // Expected NPE as regex pattern is null.
+ } catch (ModelException e) {
+ assertEquals("Invalid config for regexPattern: Config required", e.getMessage());
}
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(*)"));
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
- } catch (PatternSyntaxException e) {
- // Expected PSE as regex pattern(or any of its token) is not quantifiable.
+ } catch (ModelException e) {
+ assertEquals("Invalid config for regexPattern: Not a valid regular expression", e.getMessage());
}
try {
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(*,**)"));
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
- } catch (PatternSyntaxException e) {
- // Expected PSE as regex pattern(or any of its token) is not quantifiable.
+ } catch (ModelException e) {
+ assertEquals("Invalid config for regexPattern: Not a valid regular expression", e.getMessage());
}
//Fails to match one of the regex pattern
diff --git a/testsuite/integration/src/test/resources/adapter-test/no-access-token.json b/testsuite/integration/src/test/resources/adapter-test/no-access-token.json
new file mode 100644
index 0000000..9c8cb7e
--- /dev/null
+++ b/testsuite/integration/src/test/resources/adapter-test/no-access-token.json
@@ -0,0 +1,11 @@
+{
+ "realm" : "demo",
+ "resource" : "no-access-token",
+ "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url" : "http://${my.host.name}:8081/auth",
+ "ssl-required" : "external",
+ "credentials" : {
+ "secret": "password"
+ },
+ "ignore-oauth-query-parameter": true
+}
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index 2c6e884..6439950 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -82,8 +82,13 @@ log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error
#log4j.logger.org.apache.http.impl.conn=debug
# Enable to view details from identity provider authenticator
-log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
-log4j.logger.org.keycloak.services.resources.IdentityBrokerService=trace
-log4j.logger.org.keycloak.broker=trace
+#log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
+#log4j.logger.org.keycloak.services.resources.IdentityBrokerService=trace
+#log4j.logger.org.keycloak.broker=trace
-# log4j.logger.io.undertow=trace
+#log4j.logger.io.undertow=trace
+
+#log4j.logger.org.keycloak.protocol=debug
+#log4j.logger.org.keycloak.services.resources.LoginActionsService=debug
+#log4j.logger.org.keycloak.services.managers=debug
+#log4j.logger.org.keycloak.services.resources.SessionCodeChecks=debug
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/common/install-patch.bat b/testsuite/integration-arquillian/servers/app-server/jboss/common/install-patch.bat
index ee0ed4f..5c7ce9f 100644
--- a/testsuite/integration-arquillian/servers/app-server/jboss/common/install-patch.bat
+++ b/testsuite/integration-arquillian/servers/app-server/jboss/common/install-patch.bat
@@ -1,9 +1,14 @@
set NOPAUSE=true
+setlocal EnableDelayedExpansion
-for %%a in ("%APP_PATCH_ZIPS:,=" "%") do (
- call %JBOSS_HOME%\bin\jboss-cli.bat --command="patch apply %%~a"
-
- if %ERRORLEVEL% neq 0 set ERROR=%ERRORLEVEL%
-)
-
-exit /b %ERROR%
\ No newline at end of file
+ for %%a in (%APP_PATCH_ZIPS%) do (
+ set patch=%%a
+ if "!patch:~0,4!"=="http" (
+ powershell -command "& { iwr %%a -OutFile %cd%\patch.zip }"
+ call %JBOSS_HOME%\bin\jboss-cli.bat --command="patch apply %cd%\patch.zip
+ ) else (
+ call %JBOSS_HOME%\bin\jboss-cli.bat --command="patch apply %%a"
+ )
+ if %ERRORLEVEL% neq 0 set ERROR=%ERRORLEVEL%
+ )
+ exit /b %ERROR%
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/common/install-patch.sh b/testsuite/integration-arquillian/servers/app-server/jboss/common/install-patch.sh
index eb23344..bf091d5 100755
--- a/testsuite/integration-arquillian/servers/app-server/jboss/common/install-patch.sh
+++ b/testsuite/integration-arquillian/servers/app-server/jboss/common/install-patch.sh
@@ -12,6 +12,11 @@ RESULT=0
patches=$(echo $APP_PATCH_ZIPS | tr "," "\n")
for patch in $patches
do
+ if [[ $patch == http* ]];
+ then
+ wget -O ./patch.zip $patch >/dev/null 2>&1
+ patch=./patch.zip
+ fi
./jboss-cli.sh --command="patch apply $patch"
if [ $? -ne 0 ]; then exit 1; fi
done
diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml
index 5c6ef26..975a8c5 100644
--- a/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml
+++ b/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml
@@ -34,6 +34,8 @@
<assembly.xml>${project.parent.basedir}/assembly.xml</assembly.xml>
<app.server.jboss.home>${containers.home}/${app.server.jboss.unpacked.folder.name}</app.server.jboss.home>
<security.xslt>security.xsl</security.xslt>
+ <oidc-adapter.version>${project.version}</oidc-adapter.version>
+ <saml-adapter.version>${project.version}</saml-adapter.version>
</properties>
<profiles>
@@ -92,7 +94,7 @@
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>${app.server.oidc.adapter.artifactId}</artifactId>
- <version>${project.version}</version>
+ <version>${oidc-adapter.version}</version>
<type>zip</type>
<outputDirectory>${app.server.jboss.home}</outputDirectory>
</artifactItem>
@@ -273,7 +275,7 @@
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>${app.server.saml.adapter.artifactId}</artifactId>
- <version>${project.version}</version>
+ <version>${saml-adapter.version}</version>
<type>zip</type>
<outputDirectory>${app.server.jboss.home}</outputDirectory>
</artifactItem>
@@ -456,6 +458,6 @@
<app.server.elytron.adapter.supported>true</app.server.elytron.adapter.supported>
</properties>
</profile>
- </profiles>
+ </profiles>
</project>
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-keycloak.bat b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-keycloak.bat
index 735b5f2..496c837 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-keycloak.bat
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-keycloak.bat
@@ -1,7 +1,9 @@
set NOPAUSE=true
call %JBOSS_HOME%\bin\jboss-cli.bat --file=keycloak-install.cli
+call %JBOSS_HOME%\bin\jboss-cli.bat --file=keycloak-install-ha.cli
if %ERRORLEVEL% neq 0 set ERROR=%ERRORLEVEL%
+
exit /b %ERROR%
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-keycloak.sh b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-keycloak.sh
index af7cebe..8f95237 100755
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-keycloak.sh
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-keycloak.sh
@@ -10,8 +10,8 @@ cd $JBOSS_HOME/bin
RESULT=0
./jboss-cli.sh --file=keycloak-install.cli
-if [ $? -ne 0 ]; then RESULT=1; fi
- exit $RESULT
-fi
+if [ $? -ne 0 ]; then exit 1; fi
+./jboss-cli.sh --file=keycloak-install-ha.cli
+if [ $? -ne 0 ]; then exit 1; fi
-exit 1
+exit 0
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.bat b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.bat
index ee0ed4f..d1c48ae 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.bat
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.bat
@@ -1,9 +1,14 @@
set NOPAUSE=true
+setlocal EnableDelayedExpansion
-for %%a in ("%APP_PATCH_ZIPS:,=" "%") do (
- call %JBOSS_HOME%\bin\jboss-cli.bat --command="patch apply %%~a"
-
- if %ERRORLEVEL% neq 0 set ERROR=%ERRORLEVEL%
-)
-
-exit /b %ERROR%
\ No newline at end of file
+ for %%a in (%AUTH_PATCH_ZIPS%) do (
+ set patch=%%a
+ if "!patch:~0,4!"=="http" (
+ powershell -command "& { iwr %%a -OutFile %cd%\patch.zip }"
+ call %JBOSS_HOME%\bin\jboss-cli.bat --command="patch apply %cd%\patch.zip
+ ) else (
+ call %JBOSS_HOME%\bin\jboss-cli.bat --command="patch apply %%a"
+ )
+ if %ERRORLEVEL% neq 0 set ERROR=%ERRORLEVEL%
+ )
+ exit /b %ERROR%
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.sh b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.sh
index 3d1820b..f1dff06 100755
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.sh
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/install-patch.sh
@@ -12,6 +12,11 @@ RESULT=0
patches=$(echo $AUTH_PATCH_ZIPS | tr "," "\n")
for patch in $patches
do
+ if [[ $patch == http* ]];
+ then
+ wget -O ./patch.zip $patch >/dev/null 2>&1
+ patch=./patch.zip
+ fi
./jboss-cli.sh --command="patch apply $patch"
if [ $? -ne 0 ]; then exit 1; fi
done
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
index ab955f2..775aa5a 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
@@ -100,6 +100,7 @@
<outputDirectory>${project.build.directory}/unpacked</outputDirectory>
</artifactItem>
</artifactItems>
+ <excludes>**/product.conf</excludes>
</configuration>
</execution>
<execution>
@@ -309,6 +310,7 @@
<artifactId>${auth.server.overlay.artifactId}</artifactId>
<version>${auth.server.overlay.version}</version>
<type>zip</type>
+ <overWrite>true</overWrite>
<outputDirectory>${project.build.directory}/unpacked/${overlaid.container.unpacked.folder.name}</outputDirectory>
</artifactItem>
</artifactItems>
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java
index 83bb19b..87a37d2 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java
@@ -233,15 +233,17 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
Reflections.setAccessible(containerField);
ServletContainer container = (ServletContainer) Reflections.getFieldValue(containerField, undertow);
- DeploymentManager deployment = container.getDeployment(archive.getName());
- if (deployment != null) {
+ DeploymentManager deploymentMgr = container.getDeployment(archive.getName());
+ if (deploymentMgr != null) {
+ DeploymentInfo deployment = deploymentMgr.getDeployment().getDeploymentInfo();
+
try {
- deployment.stop();
+ deploymentMgr.stop();
} catch (ServletException se) {
throw new DeploymentException(se.getMessage(), se);
}
- deployment.undeploy();
+ deploymentMgr.undeploy();
Field rootField = Reflections.findDeclaredField(UndertowJaxrsServer.class, "root");
Reflections.setAccessible(rootField);
@@ -249,6 +251,8 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
String path = deployedArchivesToContextPath.get(archive.getName());
root.removePrefixPath(path);
+
+ container.removeDeployment(deployment);
} else {
log.warnf("Deployment '%s' not found", archive.getName());
}
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SimpleWebXmlParser.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SimpleWebXmlParser.java
index b26bc3d..47bdcea 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SimpleWebXmlParser.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SimpleWebXmlParser.java
@@ -33,6 +33,7 @@ import io.undertow.servlet.api.LoginConfig;
import io.undertow.servlet.api.SecurityConstraint;
import io.undertow.servlet.api.SecurityInfo;
import io.undertow.servlet.api.ServletInfo;
+import io.undertow.servlet.api.ServletSessionConfig;
import io.undertow.servlet.api.WebResourceCollection;
import org.jboss.logging.Logger;
import org.w3c.dom.Document;
@@ -170,6 +171,19 @@ class SimpleWebXmlParser {
}
}
+ // COOKIE CONFIG
+ ElementWrapper sessionCfg = document.getElementByTagName("session-config");
+ if (sessionCfg != null) {
+ ElementWrapper cookieConfig = sessionCfg.getElementByTagName("cookie-config");
+ String httpOnly = cookieConfig.getElementByTagName("http-only").getText();
+ String cookieName = cookieConfig.getElementByTagName("name").getText();
+
+ ServletSessionConfig cfg = new ServletSessionConfig();
+ cfg.setHttpOnly(Boolean.parseBoolean(httpOnly));
+ cfg.setName(cookieName);
+ di.setServletSessionConfig(cfg);
+ }
+
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException(cnfe);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ActionURIUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ActionURIUtils.java
new file mode 100644
index 0000000..7c8a99c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ActionURIUtils.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.ws.rs.core.UriBuilder;
+
+/**
+ * Helper for parse action-uri from the HTML login page and do something with it (eg. open in new browser, parse code parameter and use it somewhere else etc)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ActionURIUtils {
+
+ private static final Pattern ACTION_URI_PATTERN = Pattern.compile("action=\"([^\"]+)\"");
+
+ private static final Pattern QUERY_STRING_PATTERN = Pattern.compile("[^\\?]+\\?([^#]+).*");
+
+ private static final Pattern PARAMS_PATTERN = Pattern.compile("[=\\&]");
+
+ public static String getActionURIFromPageSource(String htmlPageSource) {
+ Matcher m = ACTION_URI_PATTERN.matcher(htmlPageSource);
+ if (m.find()) {
+ return m.group(1).replaceAll("&", "&");
+ } else {
+ return null;
+ }
+ }
+
+ public static Map<String, String> parseQueryParamsFromActionURI(String actionURI) {
+ Matcher m = QUERY_STRING_PATTERN.matcher(actionURI);
+ if (m.find()) {
+ String queryString = m.group(1);
+
+ String[] params = PARAMS_PATTERN.split(queryString, 0);
+ Map<String, String> result = new HashMap<>(); // Don't take multivalued into account for now
+
+ for (int i=0 ; i<params.length ; i+=2) {
+ String paramName = params[i];
+ String paramValue = params[i+1];
+ result.put(paramName, paramValue);
+ }
+ return result;
+ } else {
+ return Collections.emptyMap();
+ }
+ }
+
+ public static String removeQueryParamFromURI(String actionURI, String paramName) {
+ return UriBuilder.fromUri(actionURI)
+ .replaceQueryParam(paramName, null)
+ .build().toString();
+ }
+
+
+ /*
+ private static final String TEST = "<form id=\"kc-form-login\" class=\"form-horizontal\" action=\"http://localhost:8180/auth/realms/child/login-actions/authenticate?code=1WnqOmapgo0cj3mpRQ-vbleIKUJdwFzonzy1fjvnWQQ&execution=3ac92a20-9c31-49de-a3c8-f2a4fff80986&client_id=client-linking\" method=\"post\">";
+
+ public static void main(String[] args) {
+ String actionURI = getActionURIFromPageSource(TEST);
+ System.out.println("action uri: " + actionURI);
+
+ Map<String, String> params = parseQueryParamsFromActionURI(actionURI);
+ System.out.println("params: " + params);
+
+ String actionURI2 = removeQueryParamFromURI(actionURI, "execution");
+ System.out.println("action uri 2: " + actionURI2);
+ }*/
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java
index 874b1e8..82a29fe 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java
@@ -27,6 +27,7 @@ import java.net.URL;
*/
public class SalesPostEncServlet extends SAMLServlet {
public static final String DEPLOYMENT_NAME = "sales-post-enc";
+ public static final String CLIENT_NAME = "http://localhost:8081/sales-post-enc/";
@ArquillianResource
@OperateOnDeployment(DEPLOYMENT_NAME)
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncSignAssertionsOnlyServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncSignAssertionsOnlyServlet.java
new file mode 100644
index 0000000..cb44ac2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncSignAssertionsOnlyServlet.java
@@ -0,0 +1,40 @@
+/*
+ * 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 java.net.URL;
+
+/**
+ * @author mhajas
+ */
+public class SalesPostEncSignAssertionsOnlyServlet extends SAMLServlet {
+ public static final String DEPLOYMENT_NAME = "sales-post-enc-sign-assertions-only";
+ public static final String CLIENT_NAME = "http://localhost:8081/sales-post-enc-sign-assertions-only/";
+
+ @ArquillianResource
+ @OperateOnDeployment(DEPLOYMENT_NAME)
+ private URL url;
+
+ @Override
+ public URL getInjectedUrl() {
+ return url;
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ErrorPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ErrorPage.java
index 5b4a116..fc1c078 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ErrorPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ErrorPage.java
@@ -43,6 +43,14 @@ public class ErrorPage extends AbstractPage {
backToApplicationLink.click();
}
+ public String getBackToApplicationLink() {
+ if (backToApplicationLink == null) {
+ return null;
+ } else {
+ return backToApplicationLink.getAttribute("href");
+ }
+ }
+
public boolean isCurrent() {
return driver.getTitle() != null && driver.getTitle().equals("We're sorry...");
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/GoogleLoginPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/GoogleLoginPage.java
index 4a0eaeb..529a225 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/GoogleLoginPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/social/GoogleLoginPage.java
@@ -17,7 +17,8 @@
package org.keycloak.testsuite.pages.social;
-import org.keycloak.testsuite.util.WaitUtils;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -25,33 +26,26 @@ import org.openqa.selenium.support.FindBy;
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public class GoogleLoginPage extends AbstractSocialLoginPage {
- @FindBy(id = "Email")
+ @FindBy(xpath = ".//p[@role='heading'][1]")
+ private WebElement firstAccount;
+
+ @FindBy(id = "identifierId")
private WebElement emailInput;
- @FindBy(id = "Passwd")
+ @FindBy(xpath = ".//input[@type='password']")
private WebElement passwordInput;
- @FindBy(id = "next")
- private WebElement nextButton;
-
- @FindBy(id = "signIn")
- private WebElement signInButton;
-
- @FindBy(id = "submit_approve_access")
- private WebElement approveAccessButton;
-
- @FindBy(id = "PersistentCookie")
- private WebElement persisentCookieCheckbox;
-
@Override
public void login(String user, String password) {
- emailInput.sendKeys(user);
- nextButton.click();
- passwordInput.sendKeys(password);
- persisentCookieCheckbox.click();
- signInButton.click();
+ try {
+ firstAccount.click();
+ }
+ catch (NoSuchElementException e) {
+ emailInput.sendKeys(user);
+ emailInput.sendKeys(Keys.RETURN);
+ }
- WaitUtils.waitUntilElement(approveAccessButton).is().enabled();
- approveAccessButton.click();
+ passwordInput.sendKeys(password);
+ passwordInput.sendKeys(Keys.RETURN);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/ClientAttributeUpdater.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/ClientAttributeUpdater.java
new file mode 100644
index 0000000..d3effb9
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/ClientAttributeUpdater.java
@@ -0,0 +1,55 @@
+package org.keycloak.testsuite.util;
+
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class ClientAttributeUpdater {
+
+ private final Map<String, String> originalAttributes = new HashMap<>();
+
+ private final ClientResource clientResource;
+
+ private final ClientRepresentation rep;
+
+ public ClientAttributeUpdater(ClientResource clientResource) {
+ this.clientResource = clientResource;
+ this.rep = clientResource.toRepresentation();
+ if (this.rep.getAttributes() == null) {
+ this.rep.setAttributes(new HashMap<>());
+ }
+ }
+
+ public ClientAttributeUpdater setAttribute(String name, String value) {
+ if (! originalAttributes.containsKey(name)) {
+ this.originalAttributes.put(name, this.rep.getAttributes().put(name, value));
+ } else {
+ this.rep.getAttributes().put(name, value);
+ }
+ return this;
+ }
+
+ public ClientAttributeUpdater removeAttribute(String name) {
+ if (! originalAttributes.containsKey(name)) {
+ this.originalAttributes.put(name, this.rep.getAttributes().put(name, null));
+ } else {
+ this.rep.getAttributes().put(name, null);
+ }
+ return this;
+ }
+
+ public Closeable update() {
+ clientResource.update(rep);
+
+ return () -> {
+ rep.getAttributes().putAll(originalAttributes);
+ clientResource.update(rep);
+ };
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/RealmAttributeUpdater.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/RealmAttributeUpdater.java
new file mode 100644
index 0000000..909bfca
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/RealmAttributeUpdater.java
@@ -0,0 +1,55 @@
+package org.keycloak.testsuite.util;
+
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.RealmRepresentation;
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class RealmAttributeUpdater {
+
+ private final Map<String, String> originalAttributes = new HashMap<>();
+
+ private final RealmResource realmResource;
+
+ private final RealmRepresentation rep;
+
+ public RealmAttributeUpdater(RealmResource realmResource) {
+ this.realmResource = realmResource;
+ this.rep = realmResource.toRepresentation();
+ if (this.rep.getAttributes() == null) {
+ this.rep.setAttributes(new HashMap<>());
+ }
+ }
+
+ public RealmAttributeUpdater setAttribute(String name, String value) {
+ if (! originalAttributes.containsKey(name)) {
+ this.originalAttributes.put(name, this.rep.getAttributes().put(name, value));
+ } else {
+ this.rep.getAttributes().put(name, value);
+ }
+ return this;
+ }
+
+ public RealmAttributeUpdater removeAttribute(String name) {
+ if (! originalAttributes.containsKey(name)) {
+ this.originalAttributes.put(name, this.rep.getAttributes().put(name, null));
+ } else {
+ this.rep.getAttributes().put(name, null);
+ }
+ return this;
+ }
+
+ public Closeable update() {
+ realmResource.update(rep);
+
+ return () -> {
+ rep.getAttributes().putAll(originalAttributes);
+ realmResource.update(rep);
+ };
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
index 1739370..1601189 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
@@ -175,7 +175,7 @@ public abstract class AbstractKeycloakTest {
// Cleanup objects
for (TestCleanup cleanup : testContext.getCleanups().values()) {
- cleanup.executeCleanup();
+ if (cleanup != null) cleanup.executeCleanup();
}
testContext.getCleanups().clear();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
index 9c18070..d829d5b 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
@@ -25,6 +25,7 @@ import org.junit.Test;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AssertEvents;
@@ -32,6 +33,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType;
+import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
import org.keycloak.testsuite.util.UserBuilder;
@@ -53,6 +55,9 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
@Page
protected LoginUpdateProfileEditUsernameAllowedPage updateProfilePage;
+ @Page
+ protected ErrorPage errorPage;
+
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
ActionUtil.addRequiredActionForUser(testRealm, "test-user@localhost", UserModel.RequiredAction.UPDATE_PROFILE.name());
@@ -294,4 +299,23 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
events.assertEmpty();
}
+ @Test
+ public void updateProfileExpiredCookies() {
+ loginPage.open();
+ loginPage.login("john-doh@localhost", "password");
+
+ updateProfilePage.assertCurrent();
+
+ // Expire cookies and assert the page with "back to application" link present
+ driver.manage().deleteAllCookies();
+
+ updateProfilePage.update("New first", "New last", "keycloak-user@localhost", "test-user@localhost");
+ errorPage.assertCurrent();
+
+ String backToAppLink = errorPage.getBackToApplicationLink();
+
+ ClientRepresentation client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app").toRepresentation();
+ Assert.assertEquals(backToAppLink, client.getBaseUrl());
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java
index 750df03..ea9937e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractClientInitiatedAccountLinkTest.java
@@ -24,6 +24,7 @@ import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.util.Base64Url;
@@ -37,6 +38,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.ActionURIUtils;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
@@ -57,6 +59,7 @@ import javax.ws.rs.core.UriBuilder;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -496,23 +499,11 @@ public abstract class AbstractClientInitiatedAccountLinkTest extends AbstractSer
// ok, now scrape the code from page
String pageSource = driver.getPageSource();
- Pattern p = Pattern.compile("action=\"(.+)\"");
- Matcher m = p.matcher(pageSource);
- String action = null;
- if (m.find()) {
- action = m.group(1);
+ String action = ActionURIUtils.getActionURIFromPageSource(pageSource);
+ System.out.println("action uri: " + action);
- }
- System.out.println("action: " + action);
-
- p = Pattern.compile("code=(.+)&");
- m = p.matcher(action);
- String code = null;
- if (m.find()) {
- code = m.group(1);
-
- }
- System.out.println("code: " + code);
+ Map<String, String> queryParams = ActionURIUtils.parseQueryParamsFromActionURI(action);
+ System.out.println("query params: " + queryParams);
// now try and use the code to login to remote link-only idp
@@ -520,7 +511,8 @@ public abstract class AbstractClientInitiatedAccountLinkTest extends AbstractSer
uri = UriBuilder.fromUri(AuthServerTestEnricher.getAuthServerContextRoot())
.path(uri)
- .queryParam("code", code)
+ .queryParam(OAuth2Constants.CODE, queryParams.get(OAuth2Constants.CODE))
+ .queryParam(Constants.CLIENT_ID, queryParams.get(Constants.CLIENT_ID))
.build().toString();
System.out.println("hack uri: " + uri);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
index ce92fb8..70ef820 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
@@ -24,6 +24,7 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS
salesMetadataServletPage.checkRoles(true);
salesPostServletPage.checkRoles(true);
salesPostEncServletPage.checkRoles(true);
+ salesPostEncSignAssertionsOnlyServletPage.checkRoles(true);
salesPostSigServletPage.checkRoles(true);
salesPostPassiveServletPage.checkRoles(true);
salesPostSigPersistentServletPage.checkRoles(true);
@@ -56,6 +57,7 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS
salesMetadataServletPage.checkRoles(false);
salesPostServletPage.checkRoles(false);
salesPostEncServletPage.checkRoles(false);
+ salesPostEncSignAssertionsOnlyServletPage.checkRoles(false);
salesPostSigServletPage.checkRoles(false);
salesPostPassiveServletPage.checkRoles(false);
salesPostSigEmailServletPage.checkRoles(false);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
index 2795a2d..ecf48bf 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
@@ -86,14 +86,13 @@ import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.ByteArrayInputStream;
+import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.security.KeyPair;
import java.security.PublicKey;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.*;
@@ -107,7 +106,6 @@ import static org.keycloak.testsuite.util.IOUtil.loadXML;
import static org.keycloak.testsuite.util.IOUtil.modifyDocElementAttribute;
import static org.keycloak.testsuite.util.Matchers.bodyHC;
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
-import static org.keycloak.testsuite.util.SamlClient.Binding.POST;
import static org.keycloak.testsuite.util.SamlClient.idpInitiatedLogin;
import static org.keycloak.testsuite.util.SamlClient.login;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
@@ -157,6 +155,9 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
protected SalesPostEncServlet salesPostEncServletPage;
@Page
+ protected SalesPostEncSignAssertionsOnlyServlet salesPostEncSignAssertionsOnlyServletPage;
+
+ @Page
protected SalesPostPassiveServlet salesPostPassiveServletPage;
@Page
@@ -259,6 +260,11 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
return samlServletDeployment(SalesPostEncServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
}
+ @Deployment(name = SalesPostEncSignAssertionsOnlyServlet.DEPLOYMENT_NAME)
+ protected static WebArchive salesPostEncSignAssertionsOnly() {
+ return samlServletDeployment(SalesPostEncSignAssertionsOnlyServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+ }
+
@Deployment(name = SalesPostPassiveServlet.DEPLOYMENT_NAME)
protected static WebArchive salesPostPassive() {
return samlServletDeployment(SalesPostPassiveServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
@@ -626,6 +632,24 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
}
@Test
+ public void salesPostEncSignedAssertionsOnlyTest() throws Exception {
+ testSuccessfulAndUnauthorizedLogin(salesPostEncSignAssertionsOnlyServletPage, testRealmSAMLPostLoginPage);
+ }
+
+ @Test
+ public void salesPostEncSignedAssertionsAndDocumentTest() throws Exception {
+ ClientRepresentation salesPostEncClient = testRealmResource().clients().findByClientId(SalesPostEncServlet.CLIENT_NAME).get(0);
+ try (Closeable client = new ClientAttributeUpdater(testRealmResource().clients().get(salesPostEncClient.getId()))
+ .setAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE, "true")
+ .setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, "true")
+ .update()) {
+ testSuccessfulAndUnauthorizedLogin(salesPostEncServletPage, testRealmSAMLPostLoginPage);
+ } finally {
+ salesPostEncServletPage.logout();
+ }
+ }
+
+ @Test
public void salesPostPassiveTest() {
salesPostPassiveServletPage.navigateTo();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoServletsAdapterTest.java
index 5849a11..860c3d3 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/undertow/servlet/UndertowDemoServletsAdapterTest.java
@@ -27,10 +27,4 @@ import org.junit.Ignore;
*/
@AppServerContainer("auth-server-undertow")
public class UndertowDemoServletsAdapterTest extends AbstractDemoServletsAdapterTest {
-
- @Ignore
- @Override
- public void testAuthenticatedWithCustomSessionConfig() {
- // Undertow deployment ignores session cookie settings in web.xml, see org.keycloak.testsuite.arquillian.undertow.SimpleWebXmlParser class
- }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
index 257eb7d..9b001ac 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
@@ -479,8 +479,8 @@ public class ClientTest extends AbstractAdminTest {
// Create foo mapper
ProtocolMapperRepresentation fooMapper = new ProtocolMapperRepresentation();
fooMapper.setName("foo");
- fooMapper.setProtocol("fooProtocol");
- fooMapper.setProtocolMapper("fooMapper");
+ fooMapper.setProtocol("openid-connect");
+ fooMapper.setProtocolMapper("oidc-hardcoded-claim-mapper");
fooMapper.setConsentRequired(true);
Response response = mappersResource.createMapper(fooMapper);
String location = response.getLocation().toString();
@@ -493,13 +493,13 @@ public class ClientTest extends AbstractAdminTest {
assertEquals(fooMapper.getName(), "foo");
// Update foo mapper
- fooMapper.setProtocolMapper("foo-mapper-updated");
+ fooMapper.setConsentRequired(false);
mappersResource.update(fooMapperId, fooMapper);
assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.clientProtocolMapperPath(clientDbId, fooMapperId), fooMapper, ResourceType.PROTOCOL_MAPPER);
fooMapper = mappersResource.getMapperById(fooMapperId);
- assertEquals(fooMapper.getProtocolMapper(), "foo-mapper-updated");
+ assertFalse(fooMapper.isConsentRequired());
// Remove foo mapper
mappersResource.delete(fooMapperId);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java
index 4634bfb..62b9cbe 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ComponentsTest.java
@@ -17,13 +17,13 @@
package org.keycloak.testsuite.admin;
+import org.keycloak.admin.client.resource.ComponentResource;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.ComponentsResource;
+import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.util.MultivaluedHashMap;
-import org.keycloak.representations.idm.AdminEventRepresentation;
-import org.keycloak.representations.idm.ComponentRepresentation;
-import org.keycloak.representations.idm.ErrorRepresentation;
+import org.keycloak.representations.idm.*;
import org.keycloak.testsuite.components.TestProvider;
import javax.ws.rs.WebApplicationException;
@@ -33,6 +33,10 @@ import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
+import java.util.concurrent.*;
+import java.util.function.BiConsumer;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.hamcrest.Matchers;
import static org.junit.Assert.*;
/**
@@ -47,6 +51,44 @@ public class ComponentsTest extends AbstractAdminTest {
components = adminClient.realm(REALM_NAME).components();
}
+ private volatile CountDownLatch remainingDeleteSubmissions;
+
+ private static final int NUMBER_OF_THREADS = 4;
+ private static final int NUMBER_OF_TASKS = NUMBER_OF_THREADS * 5;
+ private static final int NUMBER_OF_CHILDREN = 3;
+
+ private void testConcurrency(BiConsumer<ExecutorService, Integer> taskCreator) throws InterruptedException {
+ ExecutorService s = Executors.newFixedThreadPool(NUMBER_OF_THREADS,
+ new BasicThreadFactory.Builder().daemon(true).uncaughtExceptionHandler((t, e) -> log.error(e.getMessage(), e)).build());
+ this.remainingDeleteSubmissions = new CountDownLatch(NUMBER_OF_TASKS);
+
+ for (int i = 0; i < NUMBER_OF_TASKS; i++) {
+ taskCreator.accept(s, i);
+ }
+
+ try {
+ assertTrue("Did not create all components in time", this.remainingDeleteSubmissions.await(30, TimeUnit.SECONDS));
+ s.shutdown();
+ assertTrue("Did not finish before timeout", s.awaitTermination(30, TimeUnit.SECONDS));
+ } finally {
+ s.shutdownNow();
+ }
+ }
+
+ @Test
+ public void testConcurrencyWithoutChildren() throws InterruptedException {
+ testConcurrency((s, i) -> s.submit(new CreateAndDeleteComponent(s, i)));
+
+ assertThat(realm.components().query(realm.toRepresentation().getId(), TestProvider.class.getName()), Matchers.hasSize(0));
+ }
+
+ @Test
+ public void testConcurrencyWithChildren() throws InterruptedException {
+ testConcurrency((s, i) -> s.submit(new CreateAndDeleteComponentWithFlatChildren(s, i)));
+
+ assertThat(realm.components().query(realm.toRepresentation().getId(), TestProvider.class.getName()), Matchers.hasSize(0));
+ }
+
@Test
public void testNotDeadlocked() {
for (int i = 0; i < 50; i++) {
@@ -67,7 +109,7 @@ public class ComponentsTest extends AbstractAdminTest {
try {
createComponent(rep);
} catch (WebApplicationException e) {
- assertErrror(e.getResponse(), "'Required' is required");
+ assertError(e.getResponse(), "'Required' is required");
}
rep.getConfig().putSingle("required", "Required");
@@ -77,7 +119,7 @@ public class ComponentsTest extends AbstractAdminTest {
try {
createComponent(rep);
} catch (WebApplicationException e) {
- assertErrror(e.getResponse(), "'Number' should be a number");
+ assertError(e.getResponse(), "'Number' should be a number");
}
}
@@ -259,16 +301,27 @@ public class ComponentsTest extends AbstractAdminTest {
assertEquals(value, returned.getConfig().getFirst("val1"));
}
- private String createComponent(ComponentRepresentation rep) {
- ComponentsResource components = realm.components();
- Response response = components.add(rep);
- String id = ApiUtil.getCreatedId(response);
- getCleanup().addComponentId(id);
- response.close();
- return id;
+ private java.lang.String createComponent(ComponentRepresentation rep) {
+ return createComponent(realm, rep);
+ }
+
+ private String createComponent(RealmResource realm, ComponentRepresentation rep) {
+ Response response = null;
+ try {
+ ComponentsResource components = realm.components();
+ response = components.add(rep);
+ String id = ApiUtil.getCreatedId(response);
+ getCleanup(realm.toRepresentation().getRealm()).addComponentId(id);
+ return id;
+ } finally {
+ if (response != null) {
+ response.bufferEntity();
+ response.close();
+ }
+ }
}
- private void assertErrror(Response response, String error) {
+ private void assertError(Response response, String error) {
if (!response.hasEntity()) {
fail("No error message set");
}
@@ -290,4 +343,119 @@ public class ComponentsTest extends AbstractAdminTest {
return rep;
}
+ private class CreateComponent implements Runnable {
+
+ protected final ExecutorService s;
+ protected final int i;
+ protected final RealmResource realm;
+
+ public CreateComponent(ExecutorService s, int i, RealmResource realm) {
+ this.s = s;
+ this.i = i;
+ this.realm = realm;
+ }
+
+ public CreateComponent(ExecutorService s, int i) {
+ this(s, i, ComponentsTest.this.realm);
+ }
+
+ @Override
+ public void run() {
+ log.debugf("Started for i=%d ", i);
+ ComponentRepresentation rep = createComponentRepresentation("test-" + i);
+ rep.getConfig().putSingle("required", "required-value");
+ rep.setParentId(this.realm.toRepresentation().getId());
+
+ String id = createComponent(this.realm, rep);
+ assertThat(id, Matchers.notNullValue());
+
+ createChildren(id);
+
+ log.debugf("Finished: i=%d, id=%s", i, id);
+
+ scheduleDeleteComponent(id);
+ remainingDeleteSubmissions.countDown();
+ }
+
+ protected void scheduleDeleteComponent(String id) {
+ }
+
+ protected void createChildren(String id) {
+ }
+ }
+
+ private class CreateAndDeleteComponent extends CreateComponent {
+
+ public CreateAndDeleteComponent(ExecutorService s, int i) {
+ super(s, i);
+ }
+
+ @Override
+ protected void scheduleDeleteComponent(String id) {
+ s.submit(new DeleteComponent(id));
+ }
+ }
+
+ private class CreateComponentWithFlatChildren extends CreateComponent {
+
+ public CreateComponentWithFlatChildren(ExecutorService s, int i, RealmResource realm) {
+ super(s, i, realm);
+ }
+
+ public CreateComponentWithFlatChildren(ExecutorService s, int i) {
+ super(s, i);
+ }
+
+ @Override
+ protected void createChildren(String id) {
+ for (int j = 0; j < NUMBER_OF_CHILDREN; j ++) {
+ ComponentRepresentation rep = createComponentRepresentation("test-" + i + ":" + j);
+ rep.setParentId(id);
+ rep.getConfig().putSingle("required", "required-value");
+
+ assertThat(createComponent(this.realm, rep), Matchers.notNullValue());
+ }
+ }
+
+ }
+
+ private class CreateAndDeleteComponentWithFlatChildren extends CreateAndDeleteComponent {
+
+ public CreateAndDeleteComponentWithFlatChildren(ExecutorService s, int i) {
+ super(s, i);
+ }
+
+ @Override
+ protected void createChildren(String id) {
+ for (int j = 0; j < NUMBER_OF_CHILDREN; j ++) {
+ ComponentRepresentation rep = createComponentRepresentation("test-" + i + ":" + j);
+ rep.setParentId(id);
+ rep.getConfig().putSingle("required", "required-value");
+
+ assertThat(createComponent(this.realm, rep), Matchers.notNullValue());
+ }
+ }
+
+ }
+
+ private class DeleteComponent implements Runnable {
+
+ private final String id;
+
+ public DeleteComponent(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public void run() {
+ log.debugf("Started, id=%s", id);
+
+ ComponentResource c = realm.components().component(id);
+ assertThat(c.toRepresentation(), Matchers.notNullValue());
+ c.remove();
+
+ log.debugf("Finished, id=%s", id);
+ }
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java
index b6bdc4c..2b34700 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java
@@ -19,6 +19,9 @@ package org.keycloak.testsuite.admin;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
@@ -69,6 +72,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
RoleModel realmRole2 = realm.addRole("realm-role2");
ClientModel client1 = realm.addClient("role-namespace");
RoleModel client1Role = client1.addRole("client-role");
+ GroupModel group = realm.createGroup("top");
RoleModel mapperRole = realm.addRole("mapper");
RoleModel managerRole = realm.addRole("manager");
@@ -107,6 +111,9 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
permission.addAssociatedPolicy(managerPolicy);
permission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
}
+ {
+ permissions.groups().setPermissionsEnabled(group, true);
+ }
}
@@ -156,6 +163,35 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
UserModel user4 = session.users().addUser(realm, "user4");
user4.setEnabled(true);
+ // group management
+ AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
+
+ GroupModel group = KeycloakModelUtils.findGroupByPath(realm, "top");
+ UserModel groupMember = session.users().addUser(realm, "groupMember");
+ groupMember.joinGroup(group);
+ groupMember.setEnabled(true);
+ UserModel groupManager = session.users().addUser(realm, "groupManager");
+ groupManager.setEnabled(true);
+ groupManager.grantRole(mapperRole);
+ session.userCredentialManager().updateCredential(realm, groupManager, UserCredentialModel.password("password"));
+
+ UserModel groupManagerNoMapper = session.users().addUser(realm, "noMapperGroupManager");
+ groupManagerNoMapper.setEnabled(true);
+ session.userCredentialManager().updateCredential(realm, groupManagerNoMapper, UserCredentialModel.password("password"));
+
+ UserPolicyRepresentation groupManagerRep = new UserPolicyRepresentation();
+ groupManagerRep.setName("groupManagers");
+ groupManagerRep.addUser("groupManager");
+ groupManagerRep.addUser("noMapperGroupManager");
+ ResourceServer server = permissions.realmResourceServer();
+ Policy groupManagerPolicy = permissions.authz().getStoreFactory().getPolicyStore().create(groupManagerRep, server);
+ Policy groupManagerPermission = permissions.groups().manageMembersPermission(group);
+ groupManagerPermission.addAssociatedPolicy(groupManagerPolicy);
+
+
+
+
+
}
public static void evaluateLocally(KeycloakSession session) {
@@ -171,7 +207,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, user);
Assert.assertTrue(permissionsForAdmin.users().canManage());
Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole));
- Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole2));
+ Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole));
}
// test composite role
@@ -180,7 +216,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, user);
Assert.assertTrue(permissionsForAdmin.users().canManage());
Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole));
- Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole2));
+ Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole));
}
@@ -191,9 +227,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
Assert.assertFalse(permissionsForAdmin.users().canManage());
Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole));
Assert.assertFalse(permissionsForAdmin.roles().canMapRole(clientRole));
-
- // will result to true because realmRole2 does not have any policies attached to this permission
- Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole2));
+ Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
}
// test unauthorized mapper
{
@@ -203,7 +237,22 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole));
Assert.assertFalse(permissionsForAdmin.roles().canMapRole(clientRole));
// will result to true because realmRole2 does not have any policies attached to this permission
- Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole2));
+ Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
+ }
+ // test group management
+ {
+ UserModel admin = session.users().getUserByUsername("groupManager", realm);
+ AdminPermissionEvaluator permissionsForAdmin = AdminPermissions.evaluator(session, realm, realm, admin);
+ UserModel user = session.users().getUserByUsername("authorized", realm);
+ Assert.assertFalse(permissionsForAdmin.users().canManage(user));
+ Assert.assertFalse(permissionsForAdmin.users().canView(user));
+ UserModel member = session.users().getUserByUsername("groupMember", realm);
+ Assert.assertTrue(permissionsForAdmin.users().canManage(member));
+ Assert.assertTrue(permissionsForAdmin.users().canView(member));
+ Assert.assertTrue(permissionsForAdmin.roles().canMapRole(realmRole));
+ Assert.assertTrue(permissionsForAdmin.roles().canMapRole(clientRole));
+ Assert.assertFalse(permissionsForAdmin.roles().canMapRole(realmRole2));
+
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java
new file mode 100644
index 0000000..ad9fbc7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java
@@ -0,0 +1,272 @@
+package org.keycloak.testsuite.admin.partialexport;
+
+import org.junit.Test;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ComponentExportRepresentation;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.ScopeMappingRepresentation;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.admin.AbstractAdminTest;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class PartialExportTest extends AbstractAdminTest {
+
+ private static final String EXPORT_TEST_REALM = "partial-export-test";
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ super.addTestRealms(testRealms);
+
+ RealmRepresentation realmRepresentation = loadJson(getClass().getResourceAsStream("/export/partialexport-testrealm.json"), RealmRepresentation.class);
+ testRealms.add(realmRepresentation);
+ }
+
+ @Test
+ public void testExport() {
+
+ // exportGroupsAndRoles == false, exportClients == false
+ RealmRepresentation rep = adminClient.realm(EXPORT_TEST_REALM).partialExport(false, false);
+ Assert.assertNull("Default groups are empty", rep.getDefaultGroups());
+ Assert.assertNull("Groups are empty", rep.getGroups());
+
+ Assert.assertNotNull("Default roles not empty", rep.getDefaultRoles());
+ checkDefaultRoles(rep.getDefaultRoles());
+
+ Assert.assertNull("Realm and client roles are empty", rep.getRoles());
+ Assert.assertNull("Clients are empty", rep.getClients());
+
+ Assert.assertNull("Scope mappings empty", rep.getScopeMappings());
+ Assert.assertNull("Client scope mappings empty", rep.getClientScopeMappings());
+
+
+ // exportGroupsAndRoles == true, exportClients == false
+ rep = adminClient.realm(EXPORT_TEST_REALM).partialExport(true, false);
+ Assert.assertNull("Default groups are empty", rep.getDefaultGroups());
+ Assert.assertNotNull("Groups not empty", rep.getGroups());
+ checkGroups(rep.getGroups());
+
+ Assert.assertNotNull("Default roles not empty", rep.getDefaultRoles());
+ checkDefaultRoles(rep.getDefaultRoles());
+
+ Assert.assertNotNull("Realm and client roles not empty", rep.getRoles());
+ Assert.assertNotNull("Realm roles not empty", rep.getRoles().getRealm());
+ checkRealmRoles(rep.getRoles().getRealm());
+
+ Assert.assertNull("Client roles are empty", rep.getRoles().getClient());
+ Assert.assertNull("Clients are empty", rep.getClients());
+
+ Assert.assertNull("Scope mappings empty", rep.getScopeMappings());
+ Assert.assertNull("Client scope mappings empty", rep.getClientScopeMappings());
+
+
+ // exportGroupsAndRoles == false, exportClients == true
+ rep = adminClient.realm(EXPORT_TEST_REALM).partialExport(false, true);
+ Assert.assertNull("Default groups are empty", rep.getDefaultGroups());
+ Assert.assertNull("Groups are empty", rep.getGroups());
+ Assert.assertNotNull("Default roles not empty", rep.getDefaultRoles());
+ checkDefaultRoles(rep.getDefaultRoles());
+
+ Assert.assertNull("Realm and client roles are empty", rep.getRoles());
+ Assert.assertNotNull("Clients not empty", rep.getClients());
+ checkClients(rep.getClients());
+
+ checkScopeMappings(rep.getScopeMappings());
+ checkClientScopeMappings(rep.getClientScopeMappings());
+
+
+ // exportGroupsAndRoles == true, exportClients == true
+ rep = adminClient.realm(EXPORT_TEST_REALM).partialExport(true, true);
+ Assert.assertNull("Default groups are empty", rep.getDefaultGroups());
+ Assert.assertNotNull("Groups not empty", rep.getGroups());
+ checkGroups(rep.getGroups());
+
+ Assert.assertNotNull("Default roles not empty", rep.getDefaultRoles());
+ checkDefaultRoles(rep.getDefaultRoles());
+
+ Assert.assertNotNull("Realm and client roles not empty", rep.getRoles());
+ Assert.assertNotNull("Realm roles not empty", rep.getRoles().getRealm());
+ checkRealmRoles(rep.getRoles().getRealm());
+
+ Assert.assertNotNull("Client roles not empty", rep.getRoles().getClient());
+ checkClientRoles(rep.getRoles().getClient());
+
+ Assert.assertNotNull("Clients not empty", rep.getClients());
+ checkClients(rep.getClients());
+
+ checkScopeMappings(rep.getScopeMappings());
+ checkClientScopeMappings(rep.getClientScopeMappings());
+
+
+ // check that secrets are masked
+ checkSecretsAreMasked(rep);
+ }
+
+ private void checkSecretsAreMasked(RealmRepresentation rep) {
+
+ // Client secret
+ for (ClientRepresentation client: rep.getClients()) {
+ Assert.assertEquals("Client secret masked", ComponentRepresentation.SECRET_VALUE, client.getSecret());
+ }
+
+ // IdentityProvider clientSecret
+ for (IdentityProviderRepresentation idp: rep.getIdentityProviders()) {
+ Assert.assertEquals("IdentityProvider clientSecret masked", ComponentRepresentation.SECRET_VALUE, idp.getConfig().get("clientSecret"));
+ }
+
+ // smtpServer password
+ Assert.assertEquals("SMTP password masked", ComponentRepresentation.SECRET_VALUE, rep.getSmtpServer().get("password"));
+
+ // components rsa KeyProvider privateKey
+ MultivaluedHashMap<String, ComponentExportRepresentation> components = rep.getComponents();
+
+ List<ComponentExportRepresentation> keys = components.get("org.keycloak.keys.KeyProvider");
+ Assert.assertNotNull("Keys not null", keys);
+ Assert.assertTrue("At least one key returned", keys.size() > 0);
+ boolean found = false;
+ for (ComponentExportRepresentation component: keys) {
+ if ("rsa".equals(component.getProviderId())) {
+ Assert.assertEquals("RSA KeyProvider privateKey masked", ComponentRepresentation.SECRET_VALUE, component.getConfig().getFirst("privateKey"));
+ found = true;
+ }
+ }
+ Assert.assertTrue("Found rsa private key", found);
+
+ // components ldap UserStorageProvider bindCredential
+ List<ComponentExportRepresentation> userStorage = components.get("org.keycloak.storage.UserStorageProvider");
+ Assert.assertNotNull("UserStorageProvider not null", userStorage);
+ Assert.assertTrue("At least one UserStorageProvider returned", userStorage.size() > 0);
+ found = false;
+ for (ComponentExportRepresentation component: userStorage) {
+ if ("ldap".equals(component.getProviderId())) {
+ Assert.assertEquals("LDAP provider bindCredential masked", ComponentRepresentation.SECRET_VALUE, component.getConfig().getFirst("bindCredential"));
+ found = true;
+ }
+ }
+ Assert.assertTrue("Found ldap bindCredential", found);
+ }
+
+ private void checkClientScopeMappings(Map<String, List<ScopeMappingRepresentation>> mappings) {
+ Map<String, Set<String>> map = extractScopeMappings(mappings.get("test-app"));
+ Set<String> set = map.get("test-app-scope");
+ Assert.assertTrue("Client test-app / test-app-scope contains customer-admin-composite-role", set.contains("customer-admin-composite-role"));
+
+ set = map.get("third-party");
+ Assert.assertTrue("Client test-app / third-party contains customer-user", set.contains("customer-user"));
+
+ map = extractScopeMappings(mappings.get("test-app-scope"));
+ set = map.get("test-app-scope");
+ Assert.assertTrue("Client test-app-scope / test-app-scope contains test-app-allowed-by-scope", set.contains("test-app-allowed-by-scope"));
+ }
+
+ private void checkScopeMappings(List<ScopeMappingRepresentation> scopeMappings) {
+ Map<String, Set<String>> map = extractScopeMappings(scopeMappings);
+
+ Set<String> set = map.get("test-app");
+ Assert.assertTrue("Client test-app contains user", set.contains("user"));
+
+ set = map.get("test-app-scope");
+ Assert.assertTrue("Client test-app contains user", set.contains("user"));
+ Assert.assertTrue("Client test-app contains admin", set.contains("admin"));
+
+ set = map.get("third-party");
+ Assert.assertTrue("Client test-app contains third-party", set.contains("user"));
+ }
+
+ private Map<String, Set<String>> extractScopeMappings(List<ScopeMappingRepresentation> scopeMappings) {
+ Map<String, Set<String>> map = new HashMap<>();
+ for (ScopeMappingRepresentation r: scopeMappings) {
+ map.put(r.getClient(), r.getRoles());
+ }
+ return map;
+ }
+
+ private void checkClientRoles(Map<String, List<RoleRepresentation>> clientRoles) {
+ Map<String, RoleRepresentation> roles = collectRoles(clientRoles.get("test-app"));
+ Assert.assertTrue("Client role customer-admin for test-app", roles.containsKey("customer-admin"));
+ Assert.assertTrue("Client role sample-client-role for test-app", roles.containsKey("sample-client-role"));
+ Assert.assertTrue("Client role customer-user for test-app", roles.containsKey("customer-user"));
+
+ Assert.assertTrue("Client role customer-admin-composite-role for test-app", roles.containsKey("customer-admin-composite-role"));
+ RoleRepresentation.Composites cmp = roles.get("customer-admin-composite-role").getComposites();
+ Assert.assertTrue("customer-admin-composite-role / realm / customer-user-premium", cmp.getRealm().contains("customer-user-premium"));
+ Assert.assertTrue("customer-admin-composite-role / client['test-app'] / customer-admin", cmp.getClient().get("test-app").contains("customer-admin"));
+
+
+ roles = collectRoles(clientRoles.get("test-app-scope"));
+ Assert.assertTrue("Client role test-app-disallowed-by-scope for test-app-scope", roles.containsKey("test-app-disallowed-by-scope"));
+ Assert.assertTrue("Client role test-app-allowed-by-scope for test-app-scope", roles.containsKey("test-app-allowed-by-scope"));
+ }
+
+ private Map<String, RoleRepresentation> collectRoles(List<RoleRepresentation> roles) {
+ HashMap<String, RoleRepresentation> map = new HashMap<>();
+ if (roles == null) {
+ return map;
+ }
+ for (RoleRepresentation r: roles) {
+ map.put(r.getName(), r);
+ }
+ return map;
+ }
+
+ private void checkClients(List<ClientRepresentation> clients) {
+ HashSet<String> set = new HashSet<>();
+ for (ClientRepresentation c: clients) {
+ set.add(c.getClientId());
+ }
+ Assert.assertTrue("Client test-app", set.contains("test-app"));
+ Assert.assertTrue("Client test-app-scope", set.contains("test-app-scope"));
+ Assert.assertTrue("Client third-party", set.contains("third-party"));
+ }
+
+ private void checkRealmRoles(List<RoleRepresentation> realmRoles) {
+ Set<String> set = new HashSet<>();
+ for (RoleRepresentation r: realmRoles) {
+ set.add(r.getName());
+ }
+ Assert.assertTrue("Role sample-realm-role", set.contains("sample-realm-role"));
+ Assert.assertTrue("Role realm-composite-role", set.contains("realm-composite-role"));
+ Assert.assertTrue("Role customer-user-premium", set.contains("customer-user-premium"));
+ Assert.assertTrue("Role admin", set.contains("admin"));
+ Assert.assertTrue("Role user", set.contains("user"));
+ }
+
+ private void checkGroups(List<GroupRepresentation> groups) {
+ HashSet<String> set = new HashSet<>();
+ for (GroupRepresentation g: groups) {
+ compileGroups(set, g);
+ }
+ Assert.assertTrue("Group /roleRichGroup", set.contains("/roleRichGroup"));
+ Assert.assertTrue("Group /roleRichGroup/level2group", set.contains("/roleRichGroup/level2group"));
+ Assert.assertTrue("Group /topGroup", set.contains("/topGroup"));
+ Assert.assertTrue("Group /topGroup/level2group", set.contains("/topGroup/level2group"));
+ }
+
+ private void compileGroups(Set<String> found, GroupRepresentation g) {
+ found.add(g.getPath());
+ if (g.getSubGroups() != null) {
+ for (GroupRepresentation s: g.getSubGroups()) {
+ compileGroups(found, s);
+ }
+ }
+ }
+ private void checkDefaultRoles(List<String> defaultRoles) {
+ HashSet<String> roles = new HashSet<>(defaultRoles);
+ Assert.assertTrue("Default role 'uma_authorization'", roles.contains("uma_authorization"));
+ Assert.assertTrue("Default role 'offline_access'", roles.contains("offline_access"));
+ Assert.assertTrue("Default role 'user'", roles.contains("user"));
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
index e502ffa..4b0de12 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
@@ -432,14 +432,19 @@ public class PermissionsTest extends AbstractKeycloakTest {
@Test
public void attackDetection() {
+ UserRepresentation newUser = new UserRepresentation();
+ newUser.setUsername("attacked");
+ newUser.setEnabled(true);
+ adminClient.realms().realm(REALM_NAME).users().create(newUser);
+ UserRepresentation user = adminClient.realms().realm(REALM_NAME).users().search("attacked").get(0);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.attackDetection().bruteForceUserStatus("nosuch");
+ realm.attackDetection().bruteForceUserStatus(user.getId());
}
}, Resource.USER, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.attackDetection().clearBruteForceForUser("nosuch");
+ realm.attackDetection().clearBruteForceForUser(user.getId());
}
}, Resource.USER, true);
invoke(new Invocation() {
@@ -447,6 +452,7 @@ public class PermissionsTest extends AbstractKeycloakTest {
realm.attackDetection().clearAllBruteForce();
}
}, Resource.USER, true);
+ adminClient.realms().realm(REALM_NAME).users().get(user.getId()).remove();
}
@Test
@@ -469,183 +475,187 @@ public class PermissionsTest extends AbstractKeycloakTest {
response.set(realm.clients().create(ClientBuilder.create().clientId("foo").build()));
}
}, Resource.CLIENT, true);
+ ClientRepresentation foo = adminClient.realms().realm(REALM_NAME).clients().findByClientId("foo").get(0);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").toRepresentation();
+ realm.clients().get(foo.getId()).toRepresentation();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getInstallationProvider("nosuch");
+ realm.clients().get(foo.getId()).getInstallationProvider("nosuch");
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").update(new ClientRepresentation());
+ realm.clients().get(foo.getId()).update(foo);
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").remove();
+ realm.clients().get(foo.getId()).remove();
+ realm.clients().create(foo);
+ ClientRepresentation temp = realm.clients().findByClientId("foo").get(0);
+ foo.setId(temp.getId());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").generateNewSecret();
+ realm.clients().get(foo.getId()).generateNewSecret();
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").regenerateRegistrationAccessToken();
+ realm.clients().get(foo.getId()).regenerateRegistrationAccessToken();
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getSecret();
+ realm.clients().get(foo.getId()).getSecret();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getServiceAccountUser();
+ realm.clients().get(foo.getId()).getServiceAccountUser();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").pushRevocation();
+ realm.clients().get(foo.getId()).pushRevocation();
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getApplicationSessionCount();
+ realm.clients().get(foo.getId()).getApplicationSessionCount();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getUserSessions(0, 100);
+ realm.clients().get(foo.getId()).getUserSessions(0, 100);
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getOfflineSessionCount();
+ realm.clients().get(foo.getId()).getOfflineSessionCount();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getOfflineUserSessions(0, 100);
+ realm.clients().get(foo.getId()).getOfflineUserSessions(0, 100);
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").registerNode(Collections.<String, String>emptyMap());
+ realm.clients().get(foo.getId()).registerNode(Collections.<String, String>emptyMap());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").unregisterNode("nosuch");
+ realm.clients().get(foo.getId()).unregisterNode("nosuch");
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").testNodesAvailable();
+ realm.clients().get(foo.getId()).testNodesAvailable();
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getCertficateResource("nosuch").generate();
+ realm.clients().get(foo.getId()).getCertficateResource("nosuch").generate();
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getCertficateResource("nosuch").generateAndGetKeystore(new KeyStoreConfig());
+ realm.clients().get(foo.getId()).getCertficateResource("nosuch").generateAndGetKeystore(new KeyStoreConfig());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getCertficateResource("nosuch").getKeyInfo();
+ realm.clients().get(foo.getId()).getCertficateResource("nosuch").getKeyInfo();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getCertficateResource("nosuch").getKeystore(new KeyStoreConfig());
+ realm.clients().get(foo.getId()).getCertficateResource("nosuch").getKeystore(new KeyStoreConfig());
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getCertficateResource("nosuch").uploadJks(new MultipartFormDataOutput());
+ realm.clients().get(foo.getId()).getCertficateResource("nosuch").uploadJks(new MultipartFormDataOutput());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getCertficateResource("nosuch").uploadJksCertificate(new MultipartFormDataOutput());
+ realm.clients().get(foo.getId()).getCertficateResource("nosuch").uploadJksCertificate(new MultipartFormDataOutput());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getProtocolMappers().createMapper(Collections.EMPTY_LIST);
+ realm.clients().get(foo.getId()).getProtocolMappers().createMapper(Collections.EMPTY_LIST);
}
}, Resource.CLIENT, true);
invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) {
- response.set(realm.clients().get("nosuch").getProtocolMappers().createMapper(new ProtocolMapperRepresentation()));
+ response.set(realm.clients().get(foo.getId()).getProtocolMappers().createMapper(new ProtocolMapperRepresentation()));
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getProtocolMappers().getMapperById("nosuch");
+ realm.clients().get(foo.getId()).getProtocolMappers().getMapperById("nosuch");
}
}, Resource.CLIENT, false, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getProtocolMappers().getMappers();
+ realm.clients().get(foo.getId()).getProtocolMappers().getMappers();
}
}, Resource.CLIENT, false, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getProtocolMappers().getMappersPerProtocol("nosuch");
+ realm.clients().get(foo.getId()).getProtocolMappers().getMappersPerProtocol("nosuch");
}
}, Resource.CLIENT, false, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getProtocolMappers().update("nosuch", new ProtocolMapperRepresentation());
+ realm.clients().get(foo.getId()).getProtocolMappers().update("nosuch", new ProtocolMapperRepresentation());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getProtocolMappers().delete("nosuch");
+ realm.clients().get(foo.getId()).getProtocolMappers().delete("nosuch");
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getScopeMappings().getAll();
+ realm.clients().get(foo.getId()).getScopeMappings().getAll();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getScopeMappings().realmLevel().listAll();
+ realm.clients().get(foo.getId()).getScopeMappings().realmLevel().listAll();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getScopeMappings().realmLevel().listEffective();
+ realm.clients().get(foo.getId()).getScopeMappings().realmLevel().listEffective();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getScopeMappings().realmLevel().listAvailable();
+ realm.clients().get(foo.getId()).getScopeMappings().realmLevel().listAvailable();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getScopeMappings().realmLevel().add(Collections.<RoleRepresentation>emptyList());
+ realm.clients().get(foo.getId()).getScopeMappings().realmLevel().add(Collections.<RoleRepresentation>emptyList());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").getScopeMappings().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
+ realm.clients().get(foo.getId()).getScopeMappings().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
}
}, Resource.CLIENT, true);
@@ -656,47 +666,47 @@ public class PermissionsTest extends AbstractKeycloakTest {
}, Resource.CLIENT, false, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").roles().create(new RoleRepresentation());
+ realm.clients().get(foo.getId()).roles().create(new RoleRepresentation());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").roles().get("nosuch").toRepresentation();
+ realm.clients().get(foo.getId()).roles().get("nosuch").toRepresentation();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").roles().deleteRole("nosuch");
+ realm.clients().get(foo.getId()).roles().deleteRole("nosuch");
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").roles().get("nosuch").update(new RoleRepresentation());
+ realm.clients().get(foo.getId()).roles().get("nosuch").update(new RoleRepresentation());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").roles().get("nosuch").addComposites(Collections.<RoleRepresentation>emptyList());
+ realm.clients().get(foo.getId()).roles().get("nosuch").addComposites(Collections.<RoleRepresentation>emptyList());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").roles().get("nosuch").deleteComposites(Collections.<RoleRepresentation>emptyList());
+ realm.clients().get(foo.getId()).roles().get("nosuch").deleteComposites(Collections.<RoleRepresentation>emptyList());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").roles().get("nosuch").getRoleComposites();
+ realm.clients().get(foo.getId()).roles().get("nosuch").getRoleComposites();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").roles().get("nosuch").getRealmRoleComposites();
+ realm.clients().get(foo.getId()).roles().get("nosuch").getRealmRoleComposites();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clients().get("nosuch").roles().get("nosuch").getClientRoleComposites("nosuch");
+ realm.clients().get(foo.getId()).roles().get("nosuch").getClientRoleComposites("nosuch");
}
}, Resource.CLIENT, false);
}
@@ -710,114 +720,120 @@ public class PermissionsTest extends AbstractKeycloakTest {
}, Resource.CLIENT, false);
invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) {
- response.set(realm.clientTemplates().create(new ClientTemplateRepresentation()));
+ ClientTemplateRepresentation template = new ClientTemplateRepresentation();
+ template.setName("template");
+ response.set(realm.clientTemplates().create(template));
}
}, Resource.CLIENT, true);
+
+ ClientTemplateRepresentation template = adminClient.realms().realm(REALM_NAME).clientTemplates().findAll().get(0);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").toRepresentation();
+ realm.clientTemplates().get(template.getId()).toRepresentation();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").update(new ClientTemplateRepresentation());
+ realm.clientTemplates().get(template.getId()).update(template);
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").remove();
+ realm.clientTemplates().get(template.getId()).remove();
+ realm.clientTemplates().create(template);
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getProtocolMappers().getMappers();
+ realm.clientTemplates().get(template.getId()).getProtocolMappers().getMappers();
}
}, Resource.CLIENT, false, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getProtocolMappers().getMappersPerProtocol("nosuch");
+ realm.clientTemplates().get(template.getId()).getProtocolMappers().getMappersPerProtocol("nosuch");
}
}, Resource.CLIENT, false, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getProtocolMappers().getMapperById("nosuch");
+ realm.clientTemplates().get(template.getId()).getProtocolMappers().getMapperById("nosuch");
}
}, Resource.CLIENT, false, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getProtocolMappers().update("nosuch", new ProtocolMapperRepresentation());
+ realm.clientTemplates().get(template.getId()).getProtocolMappers().update("nosuch", new ProtocolMapperRepresentation());
}
}, Resource.CLIENT, true);
invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) {
- response.set(realm.clientTemplates().get("nosuch").getProtocolMappers().createMapper(new ProtocolMapperRepresentation()));
+ response.set(realm.clientTemplates().get(template.getId()).getProtocolMappers().createMapper(new ProtocolMapperRepresentation()));
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getProtocolMappers().createMapper(Collections.<ProtocolMapperRepresentation>emptyList());
+ realm.clientTemplates().get(template.getId()).getProtocolMappers().createMapper(Collections.<ProtocolMapperRepresentation>emptyList());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getProtocolMappers().delete("nosuch");
+ realm.clientTemplates().get(template.getId()).getProtocolMappers().delete("nosuch");
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().getAll();
+ realm.clientTemplates().get(template.getId()).getScopeMappings().getAll();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().realmLevel().listAll();
+ realm.clientTemplates().get(template.getId()).getScopeMappings().realmLevel().listAll();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().realmLevel().listAvailable();
+ realm.clientTemplates().get(template.getId()).getScopeMappings().realmLevel().listAvailable();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().realmLevel().listEffective();
+ realm.clientTemplates().get(template.getId()).getScopeMappings().realmLevel().listEffective();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().realmLevel().add(Collections.<RoleRepresentation>emptyList());
+ realm.clientTemplates().get(template.getId()).getScopeMappings().realmLevel().add(Collections.<RoleRepresentation>emptyList());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
+ realm.clientTemplates().get(template.getId()).getScopeMappings().realmLevel().remove(Collections.<RoleRepresentation>emptyList());
}
}, Resource.CLIENT, true);
+ ClientRepresentation realmAccessClient = adminClient.realms().realm(REALM_NAME).clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().clientLevel("nosuch").listAll();
+ realm.clientTemplates().get(template.getId()).getScopeMappings().clientLevel(realmAccessClient.getId()).listAll();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().clientLevel("nosuch").listAvailable();
+ realm.clientTemplates().get(template.getId()).getScopeMappings().clientLevel(realmAccessClient.getId()).listAvailable();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().clientLevel("nosuch").listEffective();
+ realm.clientTemplates().get(template.getId()).getScopeMappings().clientLevel(realmAccessClient.getId()).listEffective();
}
}, Resource.CLIENT, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().clientLevel("nosuch").add(Collections.<RoleRepresentation>emptyList());
+ realm.clientTemplates().get(template.getId()).getScopeMappings().clientLevel(realmAccessClient.getId()).add(Collections.<RoleRepresentation>emptyList());
}
}, Resource.CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- realm.clientTemplates().get("nosuch").getScopeMappings().clientLevel("nosuch").remove(Collections.<RoleRepresentation>emptyList());
+ realm.clientTemplates().get(template.getId()).getScopeMappings().clientLevel(realmAccessClient.getId()).remove(Collections.<RoleRepresentation>emptyList());
}
}, Resource.CLIENT, true);
}
@@ -844,10 +860,13 @@ public class PermissionsTest extends AbstractKeycloakTest {
@Test
public void clientAuthorization() {
ProfileAssume.assumePreview();
+
+ ClientRepresentation newClient = new ClientRepresentation();
+ newClient.setClientId("foo-authz");
+ adminClient.realms().realm(REALM_NAME).clients().create(newClient);
+ ClientRepresentation foo = adminClient.realms().realm(REALM_NAME).clients().findByClientId("foo-authz").get(0);
invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) {
- realm.clients().create(ClientBuilder.create().clientId("foo-authz").build());
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
foo.setServiceAccountsEnabled(true);
foo.setAuthorizationServicesEnabled(true);
realm.clients().get(foo.getId()).update(foo);
@@ -855,13 +874,11 @@ public class PermissionsTest extends AbstractKeycloakTest {
}, CLIENT, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
realm.clients().get(foo.getId()).authorization().getSettings();
}
}, AUTHORIZATION, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
ResourceServerRepresentation settings = authorization.getSettings();
authorization.update(settings);
@@ -869,42 +886,36 @@ public class PermissionsTest extends AbstractKeycloakTest {
}, AUTHORIZATION, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.resources().resources();
}
}, AUTHORIZATION, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.scopes().scopes();
}
}, AUTHORIZATION, false);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.policies().policies();
}
}, AUTHORIZATION, false);
invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
response.set(authorization.resources().create(new ResourceRepresentation("Test", Collections.emptySet())));
}
}, AUTHORIZATION, true);
invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
response.set(authorization.scopes().create(new ScopeRepresentation("Test")));
}
}, AUTHORIZATION, true);
invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
PolicyRepresentation representation = new PolicyRepresentation();
representation.setName("Test PermissionsTest");
@@ -917,42 +928,36 @@ public class PermissionsTest extends AbstractKeycloakTest {
}, AUTHORIZATION, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.resources().resource("nosuch").update(new ResourceRepresentation());
}
}, AUTHORIZATION, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.scopes().scope("nosuch").update(new ScopeRepresentation());
}
}, AUTHORIZATION, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.policies().policy("nosuch").update(new PolicyRepresentation());
}
}, AUTHORIZATION, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.resources().resource("nosuch").remove();
}
}, AUTHORIZATION, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.scopes().scope("nosuch").remove();
}
}, AUTHORIZATION, true);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
- org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
authorization.policies().policy("nosuch").remove();
}
@@ -1362,12 +1367,12 @@ public class PermissionsTest extends AbstractKeycloakTest {
response.set(realm.users().create(UserBuilder.create().username("testuser").build()));
}
}, Resource.USER, true);
- UserRepresentation user = adminClient.realms().realm(REALM_NAME).users().search("testUser").get(0);
+ UserRepresentation user = adminClient.realms().realm(REALM_NAME).users().search("testuser").get(0);
invoke(new Invocation() {
public void invoke(RealmResource realm) {
realm.users().get(user.getId()).remove();
realm.users().create(user);
- UserRepresentation temp = realm.users().search("testUser").get(0);
+ UserRepresentation temp = realm.users().search("testuser").get(0);
user.setId(temp.getId());
}
}, Resource.USER, true);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
index 8129243..6500ad0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
@@ -387,4 +387,22 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
logoutFromRealm(bc.providerRealmName());
logoutFromRealm(bc.consumerRealmName());
}
+
+
+ // KEYCLOAK-4016
+ @Test
+ public void testExpiredCode() {
+ driver.navigate().to(getAccountUrl(bc.consumerRealmName()));
+
+ log.debug("Expire all browser cookies");
+ driver.manage().deleteAllCookies();
+
+ log.debug("Clicking social " + bc.getIDPAlias());
+ accountLoginPage.clickSocial(bc.getIDPAlias());
+
+ waitForPage(driver, "sorry");
+ errorPage.assertCurrent();
+ String link = errorPage.getBackToApplicationLink();
+ Assert.assertTrue(link.endsWith("/auth/realms/consumer/account"));
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
index 08cfb7e..d590322 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
@@ -41,11 +41,19 @@ public class ClientRegistrationTest extends AbstractClientRegistrationTest {
private static final String CLIENT_ID = "test-client";
private static final String CLIENT_SECRET = "test-client-secret";
- private ClientRepresentation registerClient() throws ClientRegistrationException {
- ClientRepresentation client = new ClientRepresentation();
+ private ClientRepresentation buildClient() {
+ ClientRepresentation client = new ClientRepresentation();
client.setClientId(CLIENT_ID);
client.setSecret(CLIENT_SECRET);
-
+
+ return client;
+ }
+
+ private ClientRepresentation registerClient() throws ClientRegistrationException {
+ return registerClient(buildClient());
+ }
+
+ private ClientRepresentation registerClient(ClientRepresentation client) throws ClientRegistrationException {
ClientRepresentation createdClient = reg.create(client);
assertEquals(CLIENT_ID, createdClient.getClientId());
@@ -99,6 +107,17 @@ public class ClientRegistrationTest extends AbstractClientRegistrationTest {
}
@Test
+ public void registerClientWithNonAsciiChars() throws ClientRegistrationException {
+ authCreateClients();
+ ClientRepresentation client = buildClient();
+ String name = "Cli\u00EBnt";
+ client.setName(name);
+
+ ClientRepresentation createdClient = registerClient(client);
+ assertEquals(name, createdClient.getName());
+ }
+
+ @Test
public void getClientAsAdmin() throws ClientRegistrationException {
registerClientAsAdmin();
ClientRepresentation rep = reg.get(CLIENT_ID);
@@ -204,6 +223,20 @@ public class ClientRegistrationTest extends AbstractClientRegistrationTest {
}
}
+ @Test
+ public void updateClientWithNonAsciiChars() throws ClientRegistrationException {
+ authCreateClients();
+ registerClient();
+
+ authManageClients();
+ ClientRepresentation client = reg.get(CLIENT_ID);
+ String name = "Cli\u00EBnt";
+ client.setName(name);
+
+ ClientRepresentation updatedClient = reg.update(client);
+ assertEquals(name, updatedClient.getName());
+ }
+
private void deleteClient(ClientRepresentation client) throws ClientRegistrationException {
reg.delete(CLIENT_ID);
try {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
index ae3a4a9..c130cfd 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
@@ -27,6 +27,7 @@ import org.keycloak.exportimport.dir.DirExportProviderFactory;
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
+import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
@@ -35,6 +36,7 @@ import org.keycloak.testsuite.util.UserBuilder;
import java.io.File;
import java.net.URL;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -58,6 +60,8 @@ public class ExportImportTest extends AbstractKeycloakTest {
testRealm1.getUsers().add(makeUser("user1"));
testRealm1.getUsers().add(makeUser("user2"));
testRealm1.getUsers().add(makeUser("user3"));
+
+ setEventsConfig(testRealm1);
testRealms.add(testRealm1);
RealmRepresentation testRealm2 = loadJson(getClass().getResourceAsStream("/model/testrealm.json"), RealmRepresentation.class);
@@ -65,6 +69,22 @@ public class ExportImportTest extends AbstractKeycloakTest {
testRealms.add(testRealm2);
}
+ private void setEventsConfig(RealmRepresentation realm) {
+ realm.setEventsEnabled(true);
+ realm.setAdminEventsEnabled(true);
+ realm.setAdminEventsDetailsEnabled(true);
+ realm.setEventsExpiration(600);
+ realm.setEnabledEventTypes(Arrays.asList("REGISTER", "REGISTER_ERROR", "LOGIN", "LOGIN_ERROR", "LOGOUT_ERROR"));
+ }
+
+ private void checkEventsConfig(RealmEventsConfigRepresentation config) {
+ Assert.assertTrue(config.isEventsEnabled());
+ Assert.assertTrue(config.isAdminEventsEnabled());
+ Assert.assertTrue(config.isAdminEventsDetailsEnabled());
+ Assert.assertEquals((Long) 600L, config.getEventsExpiration());
+ Assert.assertNames(new HashSet(config.getEnabledEventTypes()),"REGISTER", "REGISTER_ERROR", "LOGIN", "LOGIN_ERROR", "LOGOUT_ERROR");
+ }
+
private UserRepresentation makeUser(String userName) {
return UserBuilder.create()
.username(userName)
@@ -222,6 +242,8 @@ public class ExportImportTest extends AbstractKeycloakTest {
String importedSampleClientRoleId = adminClient.realm("test").clients().get(testAppId).roles().get("sample-client-role").toRepresentation().getId();
assertEquals(sampleClientRoleId, importedSampleClientRoleId);
+
+ checkEventsConfig(adminClient.realm("test").getRealmEventsConfig());
}
private void assertAuthenticated(String realmName, String username, String password) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
index 2d6d3af..148d7e1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
@@ -43,6 +43,7 @@ import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.testsuite.ActionURIUtils;
import org.keycloak.testsuite.util.KerberosRule;
/**
@@ -156,10 +157,7 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
Assert.assertTrue(context.contains("Log in to test"));
- Pattern pattern = Pattern.compile("action=\"([^\"]+)\"");
- Matcher m = pattern.matcher(context);
- Assert.assertTrue(m.find());
- String url = m.group(1);
+ String url = ActionURIUtils.getActionURIFromPageSource(context);
// Follow login with HttpClient. Improve if needed
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index 0700677..907d4ab 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -27,6 +27,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.Constants;
+import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@@ -54,6 +55,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -600,9 +602,13 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
driver.manage().deleteAllCookies();
- // Cookies are expired including KC_RESTART. No way to continue login. Error page must be shown
+ // Cookies are expired including KC_RESTART. No way to continue login. Error page must be shown with the "back to application" link
loginPage.login("login@test.com", "password");
errorPage.assertCurrent();
+ String link = errorPage.getBackToApplicationLink();
+
+ ClientRepresentation thirdParty = findClientByClientId(adminClient.realm("test"), "third-party").toRepresentation();
+ Assert.assertNotNull(link, thirdParty.getBaseUrl());
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
index 24a70fd..9d914bd 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
@@ -27,6 +27,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.ActionURIUtils;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
@@ -120,22 +121,16 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
public void openMultipleTabs() {
oauth.openLoginForm();
loginPage.assertCurrent();
- String actionUrl1 = getActionUrl(driver.getPageSource());
+ String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
oauth.openLoginForm();
loginPage.assertCurrent();
- String actionUrl2 = getActionUrl(driver.getPageSource());
+ String actionUrl2 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
Assert.assertEquals(actionUrl1, actionUrl2);
}
-
- private String getActionUrl(String pageSource) {
- return pageSource.split("action=\"")[1].split("\"")[0].replaceAll("&", "&");
- }
-
-
@Test
public void multipleTabsParallelLoginTest() {
oauth.openLoginForm();
@@ -173,7 +168,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
// Simulate to open login form in 2 tabs
oauth.openLoginForm();
loginPage.assertCurrent();
- String actionUrl1 = getActionUrl(driver.getPageSource());
+ String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
// Click "register" in tab2
loginPage.clickRegister();
@@ -204,7 +199,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
// Simulate to open login form in 2 tabs
oauth.openLoginForm();
loginPage.assertCurrent();
- String actionUrl1 = getActionUrl(driver.getPageSource());
+ String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
loginPage.login("invalid", "invalid");
loginPage.assertCurrent();
@@ -228,7 +223,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
// Open tab1
oauth.openLoginForm();
loginPage.assertCurrent();
- String actionUrl1 = getActionUrl(driver.getPageSource());
+ String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
// Authenticate in tab2
loginPage.login("login-test", "password");
@@ -253,8 +248,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
oauth.openLoginForm();
// Manually remove execution from the URL and try to simulate the request just with "code" parameter
- String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&", "&");
- actionUrl = actionUrl.replaceFirst("&execution=.*", "");
+ String actionUrl = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
+ actionUrl = ActionURIUtils.removeQueryParamFromURI(actionUrl, Constants.EXECUTION);
driver.navigate().to(actionUrl);
@@ -272,8 +267,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
updatePasswordPage.assertCurrent();
// Manually remove execution from the URL and try to simulate the request just with "code" parameter
- String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&", "&");
- actionUrl = actionUrl.replaceFirst("&execution=.*", "");
+ String actionUrl = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
+ actionUrl = ActionURIUtils.removeQueryParamFromURI(actionUrl, Constants.EXECUTION);
driver.navigate().to(actionUrl);
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 caaadb9..0784f01 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
@@ -97,7 +97,7 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
fail("Expected error");
} catch (BadRequestException e) {
ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
- assertEquals("Password hashing provider not found", error.getErrorMessage());
+ assertEquals("Invalid config for hashAlgorithm: Password hashing provider not found", error.getErrorMessage());
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 04ee911..16d85ef 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -418,6 +418,51 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
}
}
+ // KEYCLOAK-4016
+ @Test
+ public void resetPasswordExpiredCodeAndAuthSession() throws IOException, MessagingException, InterruptedException {
+ final AtomicInteger originalValue = new AtomicInteger();
+
+ RealmRepresentation realmRep = testRealm().toRepresentation();
+ originalValue.set(realmRep.getActionTokenGeneratedByUserLifespan());
+ realmRep.setActionTokenGeneratedByUserLifespan(60);
+ testRealm().update(realmRep);
+
+ try {
+ initiateResetPasswordFromResetPasswordPage("login-test");
+
+ events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
+ .session((String)null)
+ .user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
+
+ assertEquals(1, greenMail.getReceivedMessages().length);
+
+ MimeMessage message = greenMail.getReceivedMessages()[0];
+
+ String changePasswordUrl = getPasswordResetEmailLink(message);
+
+ setTimeOffset(70);
+
+ log.debug("Going to reset password URI.");
+ driver.navigate().to(oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials"); // This is necessary to delete KC_RESTART cookie that is restricted to /auth/realms/test path
+ log.debug("Removing cookies.");
+ driver.manage().deleteAllCookies();
+ driver.navigate().to(changePasswordUrl.trim());
+
+ errorPage.assertCurrent();
+ Assert.assertEquals("Reset Credential not allowed", errorPage.getError());
+ String backToAppLink = errorPage.getBackToApplicationLink();
+ Assert.assertTrue(backToAppLink.endsWith("/app/auth"));
+
+ events.expectRequiredAction(EventType.EXECUTE_ACTION_TOKEN_ERROR).error("expired_code").client((String) null).user(userId).session((String) null).clearDetails().detail(Details.ACTION, ResetCredentialsActionToken.TOKEN_TYPE).assertEvent();
+ } finally {
+ setTimeOffset(0);
+
+ realmRep.setActionTokenGeneratedByUserLifespan(originalValue.get());
+ testRealm().update(realmRep);
+ }
+ }
+
@Test
public void resetPasswordDisabledUser() throws IOException, MessagingException, InterruptedException {
UserRepresentation user = findUser("login-test");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index ecf540c..6a9aa65 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -56,6 +56,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.ActionURIUtils;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.util.ClientBuilder;
@@ -93,6 +94,8 @@ import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsernameId;
import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
import static org.keycloak.testsuite.util.ProtocolMapperUtil.createRoleNameMapper;
+
+import org.keycloak.util.TokenUtil;
import org.openqa.selenium.By;
/**
@@ -210,7 +213,8 @@ public class AccessTokenTest extends AbstractKeycloakTest {
oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console/nosuch.html");
oauth.openLoginForm();
- String loginPageCode = driver.getPageSource().split("code=")[1].split("&")[0].split("\"")[0];
+ String actionURI = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
+ String loginPageCode = ActionURIUtils.parseQueryParamsFromActionURI(actionURI).get("code");
oauth.fillLoginForm("test-user@localhost", "password");
@@ -441,7 +445,8 @@ public class AccessTokenTest extends AbstractKeycloakTest {
oauth.doLogin("test-user@localhost", "password");
- String code = driver.getPageSource().split("code=")[1].split("&")[0].split("\"")[0];
+ String actionURI = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
+ String code = ActionURIUtils.parseQueryParamsFromActionURI(actionURI).get("code");
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
Assert.assertEquals(400, response.getStatusCode());
@@ -991,7 +996,8 @@ public class AccessTokenTest extends AbstractKeycloakTest {
Form form = new Form();
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)
.param("username", username)
- .param("password", password);
+ .param("password", password)
+ .param(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID);
return grantTarget.request()
.header(HttpHeaders.AUTHORIZATION, header)
.post(Entity.form(form));
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
index 7a01e4e..84144f6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
@@ -36,6 +36,7 @@ import org.keycloak.models.Constants;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.ActionURIUtils;
import java.io.IOException;
import java.net.URLEncoder;
@@ -71,10 +72,7 @@ public class LoginStatusIframeEndpointTest extends AbstractKeycloakTest {
String s = IOUtils.toString(response.getEntity().getContent());
response.close();
- Matcher matcher = Pattern.compile("action=\"([^\"]*)\"").matcher(s);
- matcher.find();
-
- String action = matcher.group(1);
+ String action = ActionURIUtils.getActionURIFromPageSource(s);
HttpPost post = new HttpPost(action);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
index c5304ff..926ff69 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
@@ -42,6 +42,7 @@ import org.keycloak.testsuite.account.AccountTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AccountApplicationsPage;
import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
@@ -77,6 +78,9 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
@Page
protected AppPage appPage;
+ @Page
+ protected ErrorPage errorPage;
+
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
@@ -405,4 +409,23 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
}
+ @Test
+ public void oauthGrantExpiredAuthSession() throws Exception {
+ oauth.clientId(THIRD_PARTY_APP);
+ oauth.doLoginGrant("test-user@localhost", "password");
+
+ grantPage.assertCurrent();
+
+ // Expire cookies
+ driver.manage().deleteAllCookies();
+
+ grantPage.accept();
+
+ // Assert link "back to application" present
+ errorPage.assertCurrent();
+ String backToAppLink = errorPage.getBackToApplicationLink();
+ ClientRepresentation thirdParty = findClientByClientId(adminClient.realm(REALM_NAME), THIRD_PARTY_APP).toRepresentation();
+ Assert.assertEquals(backToAppLink, thirdParty.getBaseUrl());
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
index 0aa91f9..b8dd51f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java
@@ -24,6 +24,7 @@ import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ProtocolMappersResource;
import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.common.util.UriUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.AddressMapper;
import org.keycloak.representations.AccessToken;
@@ -148,7 +149,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
}
{
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
+ OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNotNull(idToken.getAddress());
@@ -197,6 +198,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
assertTrue(accessToken.getRealmAccess().getRoles().contains("realm-user"));
Assert.assertFalse(accessToken.getResourceAccess("test-app").getRoles().contains("customer-user"));
assertTrue(accessToken.getResourceAccess("app").getRoles().contains("hardcoded"));
+
+ oauth.openLogout();
}
// undo mappers
@@ -224,13 +227,15 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
{
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
+ OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
assertNull(idToken.getAddress());
assertNull(idToken.getOtherClaims().get("home_phone"));
assertNull(idToken.getOtherClaims().get("hard"));
assertNull(idToken.getOtherClaims().get("nested"));
assertNull(idToken.getOtherClaims().get("department"));
+
+ oauth.openLogout();
}
@@ -248,7 +253,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper));
// Login user
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
+ OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
// Verify attribute is filled
@@ -257,11 +262,11 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
String realmRoleMappings = (String) roleMappings.get("realm");
String testAppMappings = (String) roleMappings.get("test-app");
assertRolesString(realmRoleMappings,
- "pref.user", // from direct assignment in user definition
- "pref.offline_access" // from direct assignment in user definition
+ "pref.user", // from direct assignment in user definition
+ "pref.offline_access" // from direct assignment in user definition
);
assertRolesString(testAppMappings,
- "customer-user" // from direct assignment in user definition
+ "customer-user" // from direct assignment in user definition
);
// Revert
@@ -282,7 +287,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper));
// Login user
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
+ OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
// Verify attribute is filled
@@ -294,11 +299,11 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
List<String> realmRoleMappings = (List<String>) roleMappings.get("realm");
List<String> testAppMappings = (List<String>) roleMappings.get("test-app");
assertRoles(realmRoleMappings,
- "pref.user", // from direct assignment in user definition
- "pref.offline_access" // from direct assignment in user definition
+ "pref.user", // from direct assignment in user definition
+ "pref.offline_access" // from direct assignment in user definition
);
assertRoles(testAppMappings,
- "customer-user" // from direct assignment in user definition
+ "customer-user" // from direct assignment in user definition
);
// Revert
@@ -316,7 +321,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper));
// Login user
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "rich.roles@redhat.com", "password");
+ OAuthClient.AccessTokenResponse response = browserLogin("password", "rich.roles@redhat.com", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
// Verify attribute is filled
@@ -354,9 +359,16 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
// Login user
ClientManager.realm(adminClient.realm("test")).clientId(clientId).directAccessGrant(true);
oauth.clientId(clientId);
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "rich.roles@redhat.com", "password");
+
+ String oldRedirectUri = oauth.getRedirectUri();
+ oauth.redirectUri(UriUtils.getOrigin(oldRedirectUri) + "/test-app-authz");
+
+ OAuthClient.AccessTokenResponse response = browserLogin("secret", "rich.roles@redhat.com", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
+ // revert redirect_uri
+ oauth.redirectUri(oldRedirectUri);
+
// Verify attribute is filled
Map<String, Object> roleMappings = (Map<String, Object>)idToken.getOtherClaims().get("roles-custom");
Assert.assertThat(roleMappings.keySet(), containsInAnyOrder("realm", clientId));
@@ -387,7 +399,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
// Login user
ClientManager.realm(adminClient.realm("test")).clientId(clientId).directAccessGrant(true);
oauth.clientId(clientId);
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "rich.roles@redhat.com", "password");
+ OAuthClient.AccessTokenResponse response = browserLogin("password", "rich.roles@redhat.com", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
// Verify attribute is filled
@@ -419,7 +431,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
// Login user
ClientManager.realm(adminClient.realm("test")).clientId(clientId).directAccessGrant(true);
oauth.clientId(clientId);
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "rich.roles@redhat.com", "password");
+ OAuthClient.AccessTokenResponse response = browserLogin("password", "rich.roles@redhat.com", "password");
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
// Verify attribute is filled
@@ -468,4 +480,9 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest {
return rep;
}
+ private OAuthClient.AccessTokenResponse browserLogin(String clientSecret, String username, String password) {
+ OAuthClient.AuthorizationEndpointResponse authzEndpointResponse = oauth.doLogin(username, password);
+ return oauth.doAccessTokenRequest(authzEndpointResponse.getCode(), clientSecret);
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
index 572d5d2..0203eb1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
@@ -121,7 +121,8 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
@Test
public void testIssuerMatches() throws Exception {
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
+ OAuthClient.AuthorizationEndpointResponse authzResp = oauth.doLogin("test-user@localhost", "password");
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(authzResp.getCode(), "password");
Assert.assertEquals(200, response.getStatusCode());
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/ScopeParameterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/ScopeParameterTest.java
new file mode 100644
index 0000000..6096ab2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/ScopeParameterTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.oidc;
+
+import java.util.List;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.events.Details;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.EventRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.ActionURIUtils;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.AbstractAdminTest;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.util.ClientManager;
+import org.keycloak.testsuite.util.OAuthClient;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ScopeParameterTest extends AbstractTestRealmKeycloakTest {
+
+ @Rule
+ public AssertEvents events = new AssertEvents(this);
+
+ @Page
+ protected AppPage appPage;
+
+ @Page
+ protected LoginPage loginPage;
+
+ @Page
+ protected AccountUpdateProfilePage profilePage;
+
+ @Page
+ protected OAuthGrantPage grantPage;
+
+ @Page
+ protected ErrorPage errorPage;
+
+
+ @Override
+ public void configureTestRealm(RealmRepresentation testRealm) {
+ }
+
+ @Before
+ public void clientConfiguration() {
+ ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true);
+ /*
+ * Configure the default client ID. Seems like OAuthClient is keeping the state of clientID
+ * For example: If some test case configure oauth.clientId("sample-public-client"), other tests
+ * will faile and the clientID will always be "sample-public-client
+ * @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored()
+ */
+ oauth.clientId("test-app");
+ oauth.maxAge(null);
+ }
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ RealmRepresentation realm = AbstractAdminTest.loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
+ testRealms.add(realm);
+ }
+
+
+ // If scope=openid is missing, IDToken won't be present
+ @Test
+ public void testMissingScopeOpenid() {
+ String loginFormUrl = oauth.getLoginFormUrl();
+ loginFormUrl = ActionURIUtils.removeQueryParamFromURI(loginFormUrl, OAuth2Constants.SCOPE);
+
+ driver.navigate().to(loginFormUrl);
+ oauth.fillLoginForm("test-user@localhost", "password");
+ EventRepresentation loginEvent = events.expectLogin().assertEvent();
+
+ String code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode();
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+
+ // IDToken is not there
+ Assert.assertEquals(200, response.getStatusCode());
+ Assert.assertNull(response.getIdToken());
+ Assert.assertNotNull(response.getRefreshToken());
+
+ AccessToken token = oauth.verifyToken(response.getAccessToken());
+ Assert.assertEquals(token.getSubject(), loginEvent.getUserId());
+
+ // Refresh and assert idToken still not present
+ response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password");
+ Assert.assertEquals(200, response.getStatusCode());
+ Assert.assertNull(response.getIdToken());
+
+ token = oauth.verifyToken(response.getAccessToken());
+ Assert.assertEquals(token.getSubject(), loginEvent.getUserId());
+ }
+
+
+ // If scope=openid is missing, IDToken won't be present
+ @Test
+ public void testMissingScopeOpenidInResourceOwnerPasswordCredentialRequest() throws Exception {
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
+
+ assertEquals(200, response.getStatusCode());
+
+ // idToken not present
+ Assert.assertNull(response.getIdToken());
+
+ Assert.assertNotNull(response.getRefreshToken());
+ AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
+ Assert.assertEquals(accessToken.getPreferredUsername(), "test-user@localhost");
+
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/BasicSamlTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/BasicSamlTest.java
index 0bd0793..78cf93d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/BasicSamlTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/BasicSamlTest.java
@@ -2,19 +2,32 @@ package org.keycloak.testsuite.saml;
import org.junit.Test;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
+import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.util.SamlClient;
+import org.keycloak.testsuite.util.SamlClient.Binding;
+import org.keycloak.testsuite.util.SamlClient.RedirectStrategyWithSwitchableFollowRedirect;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.hamcrest.Matcher;
import org.w3c.dom.Document;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.util.IOUtil.documentToString;
import static org.keycloak.testsuite.util.IOUtil.setDocElementAttributeValue;
+import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
import static org.keycloak.testsuite.util.SamlClient.login;
/**
@@ -35,4 +48,34 @@ public class BasicSamlTest extends AbstractSamlTest {
assertThat(documentToString(document.getSamlDocument()), not(containsString("InResponseTo=\"" + System.getProperty("java.version") + "\"")));
}
+
+ @Test
+ public void testNoPortInDestination() throws Exception {
+ // note that this test relies on settings of the login-protocol.saml.knownProtocols configuration option
+ testWithOverriddenPort(-1, Response.Status.OK, containsString("login"));
+ }
+
+ @Test
+ public void testExplicitPortInDestination() throws Exception {
+ testWithOverriddenPort(Integer.valueOf(System.getProperty("auth.server.http.port")), Response.Status.OK, containsString("login"));
+ }
+
+ @Test
+ public void testWrongPortInDestination() throws Exception {
+ testWithOverriddenPort(123, Response.Status.INTERNAL_SERVER_ERROR, containsString("Invalid Request"));
+ }
+
+ private void testWithOverriddenPort(int port, Response.Status expectedHttpCode, Matcher<String> pageTextMatcher) throws Exception {
+ AuthnRequestType loginRep = SamlClient.createLoginRequestDocument(SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST,
+ RealmsResource.protocolUrl(UriBuilder.fromUri(getAuthServerRoot()).port(port)).build(REALM_NAME, SamlProtocol.LOGIN_PROTOCOL));
+
+ Document doc = SAML2Request.convert(loginRep);
+ HttpUriRequest post = Binding.POST.createSamlUnsignedRequest(getAuthServerSamlEndpoint(REALM_NAME), null, doc);
+
+ try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(new RedirectStrategyWithSwitchableFollowRedirect()).build();
+ CloseableHttpResponse response = client.execute(post)) {
+ assertThat(response, statusCodeIsHC(expectedHttpCode));
+ assertThat(EntityUtils.toString(response.getEntity(), "UTF-8"), pageTextMatcher);
+ }
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SamlClient.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SamlClient.java
index 5d5675f..8c20b26 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SamlClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SamlClient.java
@@ -398,6 +398,14 @@ public class SamlClient {
this.samlEndpoint = samlEndpoint;
}
+ public HttpClientContext getContext() {
+ return context;
+ }
+
+ public URI getSamlEndpoint() {
+ return samlEndpoint;
+ }
+
/**
* Send request for login form and then login using user param. Check whether client requires consent and handle consent page.
*
@@ -415,21 +423,22 @@ public class SamlClient {
Document samlRequest, String relayState, Binding requestBinding, Binding expectedResponseBinding, boolean consentRequired, boolean consent) {
return getSamlResponse(expectedResponseBinding, (client, context, strategy) -> {
HttpUriRequest post = requestBinding.createSamlUnsignedRequest(samlEndpoint, relayState, samlRequest);
- CloseableHttpResponse response = client.execute(post, context);
-
- assertThat(response, statusCodeIsHC(Response.Status.OK));
- String loginPageText = EntityUtils.toString(response.getEntity(), "UTF-8");
- response.close();
+ String loginPageText;
- assertThat(loginPageText, containsString("login"));
+ try (CloseableHttpResponse response = client.execute(post, context)) {
+ assertThat(response, statusCodeIsHC(Response.Status.OK));
+ loginPageText = EntityUtils.toString(response.getEntity(), "UTF-8");
+ assertThat(loginPageText, containsString("login"));
+ }
HttpUriRequest loginRequest = handleLoginPage(user, loginPageText);
if (consentRequired) {
// Client requires consent
- response = client.execute(loginRequest, context);
- String consentPageText = EntityUtils.toString(response.getEntity(), "UTF-8");
- loginRequest = handleConsentPage(consentPageText, consent);
+ try (CloseableHttpResponse response = client.execute(loginRequest, context)) {
+ String consentPageText = EntityUtils.toString(response.getEntity(), "UTF-8");
+ loginRequest = handleConsentPage(consentPageText, consent);
+ }
}
strategy.setRedirectable(false);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java
index 0411ce9..c11168e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java
@@ -50,9 +50,16 @@ public class WelcomePageTest extends AbstractKeycloakTest {
@Page
protected OIDCLogin loginPage;
+ /*
+ * Assume adding user is skipped.
+ *
+ * Assume we are not testing migration. In migration scenario there is admin user
+ * migrated from previous version.
+ */
@BeforeClass
public static void beforeWelcomePageTest() {
Assume.assumeTrue(Boolean.parseBoolean(System.getProperty("skip.add.user.json")));
+ Assume.assumeFalse(Boolean.parseBoolean(System.getProperty("skip.welcome.page.test")));
}
@Override
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keycloak-saml.xml
new file mode 100644
index 0000000..39df7d9
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,65 @@
+<!--
+ ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+ ~ and other contributors as indicated by the @author tags.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
+ <SP entityID="http://localhost:8081/sales-post-enc-sign-assertions-only/"
+ sslPolicy="EXTERNAL"
+ nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ logoutPage="/logout.jsp"
+ forceAuthentication="false">
+ <Keys>
+ <Key signing="true" encryption="true">
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+ <PrivateKey alias="http://localhost:8080/sales-post-enc/" password="test123"/>
+ <Certificate alias="http://localhost:8080/sales-post-enc/"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ <PrincipalNameMapping policy="FROM_NAME_ID"/>
+ <RoleIdentifiers>
+ <Attribute name="Role"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp">
+ <SingleSignOnService signRequest="true"
+ validateResponseSignature="false"
+ validateAssertionSignature="true"
+ requestBinding="POST"
+ bindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+ />
+
+ <SingleLogoutService
+ validateRequestSignature="true"
+ validateResponseSignature="false"
+ signRequest="true"
+ signResponse="true"
+ requestBinding="POST"
+ responseBinding="POST"
+ postBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+ redirectBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+ />
+ <Keys>
+ <Key signing="true" >
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+ <Certificate alias="demo"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ </IDP>
+ </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keystore.jks
new file mode 100644
index 0000000..822162c
Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
index 25e1f21..e190129 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
@@ -332,6 +332,25 @@
}
},
{
+ "clientId": "http://localhost:8081/sales-post-enc-sign-assertions-only/",
+ "enabled": true,
+ "protocol": "saml",
+ "fullScopeAllowed": true,
+ "baseUrl": "http://localhost:8080/sales-post-enc-sign-assertions-only",
+ "redirectUris": [
+ ],
+ "attributes": {
+ "saml.server.signature": "false",
+ "saml.assertion.signature": "true",
+ "saml.signature.algorithm": "RSA_SHA512",
+ "saml.client.signature": "true",
+ "saml.encrypt": "true",
+ "saml.authnstatement": "true",
+ "saml.signing.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g==",
+ "saml.encryption.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g=="
+ }
+ },
+ {
"clientId": "http://localhost:8081/employee-sig/",
"enabled": true,
"protocol": "saml",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/export/partialexport-testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/export/partialexport-testrealm.json
new file mode 100644
index 0000000..933a170
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/export/partialexport-testrealm.json
@@ -0,0 +1,989 @@
+{
+ "id": "partial-export-test",
+ "realm": "partial-export-test",
+
+ "roles": {
+ "realm": [
+ {
+ "name": "sample-realm-role",
+ "description": "Sample realm role",
+ "scopeParamRequired": false,
+ "composite": false,
+ "clientRole": false,
+ "containerId": "test"
+ },
+ {
+ "name": "realm-composite-role",
+ "description": "Realm composite role containing client role",
+ "scopeParamRequired": false,
+ "composite": true,
+ "composites": {
+ "realm": [
+ "sample-realm-role"
+ ],
+ "client": {
+ "test-app": [
+ "sample-client-role"
+ ],
+ "account": [
+ "view-profile"
+ ]
+ }
+ },
+ "clientRole": false,
+ "containerId": "test"
+ },
+ {
+ "name": "customer-user-premium",
+ "description": "Have User Premium privileges",
+ "scopeParamRequired": false,
+ "composite": false,
+ "clientRole": false,
+ "containerId": "test"
+ },
+ {
+ "name": "admin",
+ "description": "Have Administrator privileges",
+ "scopeParamRequired": false,
+ "composite": false,
+ "clientRole": false,
+ "containerId": "test"
+ },
+ {
+ "name": "user",
+ "description": "Have User privileges",
+ "scopeParamRequired": false,
+ "composite": false,
+ "clientRole": false,
+ "containerId": "test"
+ }
+ ],
+ "client": {
+ "test-app": [
+ {
+ "name": "customer-admin",
+ "description": "Have Customer Admin privileges",
+ "scopeParamRequired": false,
+ "composite": false,
+ "clientRole": true,
+ "containerId": "c1a37c9e-6ba4-4d77-988d-ab11462d5668"
+ },
+ {
+ "name": "sample-client-role",
+ "description": "Sample client role",
+ "scopeParamRequired": false,
+ "composite": false,
+ "clientRole": true,
+ "containerId": "c1a37c9e-6ba4-4d77-988d-ab11462d5668"
+ },
+ {
+ "name": "customer-admin-composite-role",
+ "description": "Have Customer Admin privileges via composite role",
+ "scopeParamRequired": false,
+ "composite": true,
+ "composites": {
+ "realm": [
+ "customer-user-premium"
+ ],
+ "client": {
+ "test-app": [
+ "customer-admin"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "c1a37c9e-6ba4-4d77-988d-ab11462d5668"
+ },
+ {
+ "name": "customer-user",
+ "description": "Have Customer User privileges",
+ "scopeParamRequired": false,
+ "composite": false,
+ "clientRole": true,
+ "containerId": "c1a37c9e-6ba4-4d77-988d-ab11462d5668"
+ }
+ ],
+ "test-app-scope": [
+ {
+ "name": "test-app-disallowed-by-scope",
+ "description": "Role disallowed by scope in test-app-scope",
+ "scopeParamRequired": false,
+ "composite": false,
+ "clientRole": true,
+ "containerId": "f3ff0b0d-e922-4874-a34c-cdfa1b3305fe"
+ },
+ {
+ "name": "test-app-allowed-by-scope",
+ "description": "Role allowed by scope in test-app-scope",
+ "scopeParamRequired": false,
+ "composite": false,
+ "clientRole": true,
+ "containerId": "f3ff0b0d-e922-4874-a34c-cdfa1b3305fe"
+ }
+ ]
+ }
+ },
+ "groups": [
+ {
+ "name": "roleRichGroup",
+ "path": "/roleRichGroup",
+ "attributes": {
+ "topAttribute": [
+ "true"
+ ]
+ },
+ "realmRoles": [
+ "realm-composite-role",
+ "user"
+ ],
+ "clientRoles": {
+ "account": [
+ "manage-account"
+ ]
+ },
+ "subGroups": [
+ {
+ "name": "level2group",
+ "path": "/roleRichGroup/level2group",
+ "attributes": {
+ "level2Attribute": [
+ "true"
+ ]
+ },
+ "realmRoles": [
+ "admin"
+ ],
+ "clientRoles": {
+ "test-app": [
+ "customer-admin-composite-role",
+ "customer-user"
+ ]
+ },
+ "subGroups": []
+ }
+ ]
+ },
+ {
+ "name": "topGroup",
+ "path": "/topGroup",
+ "attributes": {
+ "topAttribute": [
+ "true"
+ ]
+ },
+ "realmRoles": [
+ "user"
+ ],
+ "clientRoles": {},
+ "subGroups": [
+ {
+ "name": "level2group",
+ "path": "/topGroup/level2group",
+ "attributes": {
+ "level2Attribute": [
+ "true"
+ ]
+ },
+ "realmRoles": [
+ "admin"
+ ],
+ "clientRoles": {
+ "test-app": [
+ "customer-user"
+ ]
+ },
+ "subGroups": []
+ }
+ ]
+ }
+ ],
+ "defaultRoles": [
+ "user",
+ "offline_access",
+ "uma_authorization"
+ ],
+ "smtpServer": {
+ "from": "auto@keycloak.org",
+ "host": "localhost",
+ "port": "3025",
+ "user": "user",
+ "password": "secret"
+ },
+ "scopeMappings": [
+ {
+ "client": "test-app",
+ "roles": [
+ "user"
+ ]
+ },
+ {
+ "client": "test-app-scope",
+ "roles": [
+ "admin",
+ "user"
+ ]
+ },
+ {
+ "client": "third-party",
+ "roles": [
+ "user"
+ ]
+ }
+ ],
+ "clientScopeMappings": {
+ "realm-management": [
+ {
+ "client": "admin-cli",
+ "roles": [
+ "realm-admin"
+ ]
+ },
+ {
+ "client": "security-admin-console",
+ "roles": [
+ "realm-admin"
+ ]
+ }
+ ],
+ "test-app": [
+ {
+ "client": "test-app-scope",
+ "roles": [
+ "customer-admin-composite-role"
+ ]
+ },
+ {
+ "client": "third-party",
+ "roles": [
+ "customer-user"
+ ]
+ }
+ ],
+ "test-app-scope": [
+ {
+ "client": "test-app-scope",
+ "roles": [
+ "test-app-allowed-by-scope"
+ ]
+ }
+ ]
+ },
+ "clients": [
+ {
+ "clientId": "test-app",
+ "adminUrl": "http://localhost:8180/auth/realms/master/app/admin",
+ "baseUrl": "http://localhost:8180/auth/realms/master/app/auth",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "password",
+ "redirectUris": [
+ "http://localhost:8180/auth/realms/master/app/auth/*"
+ ],
+ "webOrigins": [
+ "http://localhost:8180"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "attributes": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "protocolMappers": [
+ {
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ },
+ {
+ "name": "full name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-full-name-mapper",
+ "consentRequired": true,
+ "consentText": "${fullName}",
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true"
+ }
+ },
+ {
+ "name": "email",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${email}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "email",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "username",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${username}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "preferred_username",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "given name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${givenName}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "firstName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "given_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "family name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${familyName}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "lastName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "family_name",
+ "jsonType.label": "String"
+ }
+ }
+ ],
+ "useTemplateConfig": false,
+ "useTemplateScope": false,
+ "useTemplateMappers": false
+ },
+ {
+ "clientId": "test-app-scope",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "password",
+ "redirectUris": [
+ "http://localhost:8180/auth/realms/master/app/*"
+ ],
+ "webOrigins": [
+ "http://localhost:8180"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "attributes": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": -1,
+ "protocolMappers": [
+ {
+ "name": "email",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${email}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "email",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "full name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-full-name-mapper",
+ "consentRequired": true,
+ "consentText": "${fullName}",
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true"
+ }
+ },
+ {
+ "name": "username",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${username}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "preferred_username",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "family name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${familyName}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "lastName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "family_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ },
+ {
+ "name": "given name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${givenName}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "firstName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "given_name",
+ "jsonType.label": "String"
+ }
+ }
+ ],
+ "useTemplateConfig": false,
+ "useTemplateScope": false,
+ "useTemplateMappers": false
+ },
+ {
+ "clientId": "third-party",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "password",
+ "redirectUris": [
+ "http://localhost:8180/auth/realms/master/app/*"
+ ],
+ "webOrigins": [
+ "http://localhost:8180"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": true,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "attributes": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": -1,
+ "protocolMappers": [
+ {
+ "name": "family name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${familyName}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "lastName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "family_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "email",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${email}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "email",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "username",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${username}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "preferred_username",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "given name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": true,
+ "consentText": "${givenName}",
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "firstName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "given_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ },
+ {
+ "name": "full name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-full-name-mapper",
+ "consentRequired": true,
+ "consentText": "${fullName}",
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true"
+ }
+ }
+ ],
+ "useTemplateConfig": false,
+ "useTemplateScope": false,
+ "useTemplateMappers": false
+ }],
+ "components": {
+ "org.keycloak.keys.KeyProvider": [
+ {
+ "name": "rsa",
+ "providerId": "rsa",
+ "subComponents": {},
+ "config": {
+ "privateKey": [
+ "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y="
+ ],
+ "certificate": [
+ "MIIBkTCB+wIGAVufbLMuMA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMMBHRlc3QwHhcNMTcwNDI0MTAwNDEyWhcNMjcwNDI0MTAwNTUyWjAPMQ0wCwYDVQQDDAR0ZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAKKj6Ygftq7iSfvi8G6IoJ4RbknpA0+g+s1fYgmpdHdBEfAfbODmWrNR8GLWQDU0ccnHT0oQDc66ShfluMZ0KAVcfxNJUFP2OYdrGNRJNZbGT9WMcD8LUF8mlACa8uKVfhMU4LssOdEBnW2RpM4xEe1DYPRC+AWoFODb0wsYDwll"
+ ],
+ "priority": [
+ "100"
+ ]
+ }
+ }
+ ],
+ "org.keycloak.storage.UserStorageProvider": [
+ {
+ "name": "ldap-apacheds",
+ "providerId": "ldap",
+ "subComponents": {
+ "org.keycloak.storage.ldap.mappers.LDAPStorageMapper": [
+ {
+ "name": "username",
+ "providerId": "user-attribute-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "ldap.attribute": [
+ "uid"
+ ],
+ "is.mandatory.in.ldap": [
+ "true"
+ ],
+ "read.only": [
+ "false"
+ ],
+ "always.read.value.from.ldap": [
+ "false"
+ ],
+ "user.model.attribute": [
+ "username"
+ ]
+ }
+ },
+ {
+ "name": "first name",
+ "providerId": "user-attribute-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "ldap.attribute": [
+ "cn"
+ ],
+ "is.mandatory.in.ldap": [
+ "true"
+ ],
+ "read.only": [
+ "false"
+ ],
+ "always.read.value.from.ldap": [
+ "false"
+ ],
+ "user.model.attribute": [
+ "firstName"
+ ]
+ }
+ },
+ {
+ "name": "last name",
+ "providerId": "user-attribute-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "ldap.attribute": [
+ "sn"
+ ],
+ "is.mandatory.in.ldap": [
+ "true"
+ ],
+ "read.only": [
+ "false"
+ ],
+ "always.read.value.from.ldap": [
+ "false"
+ ],
+ "user.model.attribute": [
+ "lastName"
+ ]
+ }
+ },
+ {
+ "name": "email",
+ "providerId": "user-attribute-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "ldap.attribute": [
+ "mail"
+ ],
+ "is.mandatory.in.ldap": [
+ "false"
+ ],
+ "read.only": [
+ "false"
+ ],
+ "always.read.value.from.ldap": [
+ "false"
+ ],
+ "user.model.attribute": [
+ "email"
+ ]
+ }
+ },
+ {
+ "name": "creation date",
+ "providerId": "user-attribute-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "ldap.attribute": [
+ "createTimestamp"
+ ],
+ "is.mandatory.in.ldap": [
+ "false"
+ ],
+ "read.only": [
+ "true"
+ ],
+ "always.read.value.from.ldap": [
+ "false"
+ ],
+ "user.model.attribute": [
+ "createTimestamp"
+ ]
+ }
+ },
+ {
+ "name": "modify date",
+ "providerId": "user-attribute-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "ldap.attribute": [
+ "modifyTimestamp"
+ ],
+ "is.mandatory.in.ldap": [
+ "false"
+ ],
+ "read.only": [
+ "true"
+ ],
+ "always.read.value.from.ldap": [
+ "false"
+ ],
+ "user.model.attribute": [
+ "modifyTimestamp"
+ ]
+ }
+ },
+ {
+ "name": "postal code",
+ "providerId": "user-attribute-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "ldap.attribute": [
+ "postalCode"
+ ],
+ "is.mandatory.in.ldap": [
+ "false"
+ ],
+ "read.only": [
+ "false"
+ ],
+ "always.read.value.from.ldap": [
+ "false"
+ ],
+ "user.model.attribute": [
+ "postal_code"
+ ]
+ }
+ },
+ {
+ "name": "street",
+ "providerId": "user-attribute-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "ldap.attribute": [
+ "street"
+ ],
+ "is.mandatory.in.ldap": [
+ "false"
+ ],
+ "read.only": [
+ "false"
+ ],
+ "always.read.value.from.ldap": [
+ "false"
+ ],
+ "user.model.attribute": [
+ "street"
+ ]
+ }
+ },
+ {
+ "name": "picture",
+ "providerId": "user-attribute-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "ldap.attribute": [
+ "jpegPhoto"
+ ],
+ "is.mandatory.in.ldap": [
+ "false"
+ ],
+ "is.binary.attribute": [
+ "true"
+ ],
+ "read.only": [
+ "false"
+ ],
+ "always.read.value.from.ldap": [
+ "true"
+ ],
+ "user.model.attribute": [
+ "picture"
+ ]
+ }
+ },
+ {
+ "name": "realm roles",
+ "providerId": "role-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "mode": [
+ "LDAP_ONLY"
+ ],
+ "roles.dn": [
+ "ou=RealmRoles,dc=keycloak,dc=org"
+ ],
+ "membership.ldap.attribute": [
+ "member"
+ ],
+ "role.name.ldap.attribute": [
+ "cn"
+ ],
+ "use.realm.roles.mapping": [
+ "true"
+ ],
+ "role.object.classes": [
+ "groupOfNames"
+ ]
+ }
+ },
+ {
+ "name": "finance roles",
+ "providerId": "role-ldap-mapper",
+ "subComponents": {
+
+ },
+ "config": {
+ "mode": [
+ "LDAP_ONLY"
+ ],
+ "roles.dn": [
+ "ou=FinanceRoles,dc=keycloak,dc=org"
+ ],
+ "membership.ldap.attribute": [
+ "member"
+ ],
+ "role.name.ldap.attribute": [
+ "cn"
+ ],
+ "use.realm.roles.mapping": [
+ "false"
+ ],
+ "role.object.classes": [
+ "groupOfNames"
+ ],
+ "client.id": [
+ "finance"
+ ]
+ }
+ }
+ ]
+ },
+ "config": {
+ "fullSyncPeriod": [
+ "-1"
+ ],
+ "pagination": [
+ "true"
+ ],
+ "debug": [
+ "false"
+ ],
+ "searchScope": [
+ "1"
+ ],
+ "connectionPooling": [
+ "true"
+ ],
+ "usersDn": [
+ "ou=People,dc=keycloak,dc=org"
+ ],
+ "priority": [
+ "1"
+ ],
+ "userObjectClasses": [
+ "inetOrgPerson, organizationalPerson"
+ ],
+ "changedSyncPeriod": [
+ "-1"
+ ],
+ "usernameLDAPAttribute": [
+ "uid"
+ ],
+ "bindDn": [
+ "uid=admin,ou=system"
+ ],
+ "bindCredential": [
+ "secret"
+ ],
+ "rdnLDAPAttribute": [
+ "uid"
+ ],
+ "lastSync": [
+ "0"
+ ],
+ "vendor": [
+ "other"
+ ],
+ "editMode": [
+ "WRITABLE"
+ ],
+ "uuidLDAPAttribute": [
+ "entryUUID"
+ ],
+ "connectionUrl": [
+ "ldap://localhost:10389"
+ ],
+ "syncRegistrations": [
+ "true"
+ ],
+ "authType": [
+ "simple"
+ ]
+ }
+ }
+ ]
+ },
+ "identityProviders" : [
+ {
+ "providerId" : "google",
+ "alias" : "google1",
+ "enabled": true,
+ "config": {
+ "clientId": "googleId",
+ "clientSecret": "googleSecret"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
index d038877..9d801e5 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
@@ -133,5 +133,14 @@
"enabled": true
}
+ },
+
+ "login-protocol": {
+ "saml": {
+ "knownProtocols": [
+ "http=${auth.server.http.port}",
+ "https=${auth.server.https.port}"
+ ]
+ }
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
index edb0f61..f4b118e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
@@ -162,6 +162,7 @@
"enabled": true,
"consentRequired": true,
+ "baseUrl": "http://localhost:8180/auth/realms/master/app/auth",
"redirectUris": [
"http://localhost:8180/auth/realms/master/app/*"
],
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 4fd00a4..1dd53fa 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -679,6 +679,11 @@ skip=Skip
overwrite=Overwrite
if-resource-exists.tooltip=Specify what should be done if you try to import a resource that already exists.
+partial-export=Partial Export
+partial-export.tooltip=Partial export allows you to export realm configuration, and other associated resources into a json file.
+export-groups-and-roles=Export groups and roles
+export-clients=Export clients
+
action=Action
role-selector=Role Selector
realm-roles.tooltip=Realm roles that can be selected.
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js
index 718d5fe..2864559 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -550,6 +550,15 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'RealmImportCtrl'
})
+ .when('/realms/:realm/partial-export', {
+ templateUrl : resourceUrl + '/partials/partial-export.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ }
+ },
+ controller : 'RealmExportCtrl'
+ })
.when('/create/user/:realm', {
templateUrl : resourceUrl + '/partials/user-detail.html',
resolve : {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js
index 7b61a3c..455891d 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js
@@ -59,6 +59,23 @@ module.config(['$routeProvider', function ($routeProvider) {
}
},
controller: 'ResourceServerDetailCtrl'
+ }).when('/realms/:realm/clients/:client/authz/resource-server/export-settings', {
+ templateUrl: resourceUrl + '/partials/authz/resource-server-export-settings.html',
+ resolve: {
+ realm: function (RealmLoader) {
+ return RealmLoader();
+ },
+ client : function(ClientLoader) {
+ return ClientLoader();
+ },
+ clients: function (ClientListLoader) {
+ return ClientListLoader();
+ },
+ serverInfo: function (ServerInfoLoader) {
+ return ServerInfoLoader();
+ }
+ },
+ controller: 'ResourceServerDetailCtrl'
}).when('/realms/:realm/clients/:client/authz/resource-server/evaluate', {
templateUrl: resourceUrl + '/partials/authz/policy/resource-server-policy-evaluate.html',
resolve: {
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 32d07a8..5eb0ba2 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
@@ -2353,6 +2353,11 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio
$scope.authzRequest.userId = user.id;
}
+
+ $scope.reset = function() {
+ $scope.authzRequest = angular.copy(authzRequest);
+ $scope.changed = false;
+ }
});
getManageClientId = function(realm) {
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 d5ed3d0..e3d3070 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
@@ -2712,3 +2712,40 @@ module.controller('RealmImportCtrl', function($scope, realm, $route,
}
});
+
+module.controller('RealmExportCtrl', function($scope, realm, $http,
+ $httpParamSerializer, Notifications, Dialog) {
+ $scope.realm = realm;
+ $scope.exportGroupsAndRoles = false;
+ $scope.exportClients = false;
+
+ $scope.export = function() {
+ if ($scope.exportGroupsAndRoles || $scope.exportClients) {
+ Dialog.confirm('Export', 'This operation may make server unresponsive for a while.\n\nAre you sure you want to proceed?', download);
+ } else {
+ download();
+ }
+ }
+
+ function download() {
+ var exportUrl = authUrl + '/admin/realms/' + realm.realm + '/partial-export';
+ var params = {};
+ if ($scope.exportGroupsAndRoles) {
+ params['exportGroupsAndRoles'] = true;
+ }
+ if ($scope.exportClients) {
+ params['exportClients'] = true;
+ }
+ if (Object.keys(params).length > 0) {
+ exportUrl += '?' + $httpParamSerializer(params);
+ }
+ $http.post(exportUrl)
+ .success(function(data, status, headers) {
+ var download = angular.fromJson(data);
+ download = angular.toJson(download, true);
+ saveAs(new Blob([download], { type: 'application/json' }), 'realm-export.json');
+ }).error(function() {
+ Notifications.error("Sorry, something went wrong.");
+ });
+ }
+});
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html
index 221a902..6a02a0a 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html
@@ -81,7 +81,7 @@
<div class="form-group" data-ng-show="access.manageAuthorization">
<div class="col-md-10 col-md-offset-2">
- <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
+ <button kc-save data-ng-disabled="!changed || (selectedPolicies == null || selectedPolicies.length == 0)">{{:: 'save' | translate}}</button>
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html
index cce7e24..79cec9a 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html
@@ -85,7 +85,7 @@
</fieldset>
<div class="form-group" data-ng-show="access.manageAuthorization">
<div class="col-md-10 col-md-offset-2">
- <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
+ <button kc-save data-ng-disabled="!changed || ((selectedPolicies == null || selectedPolicies.length == 0) || (selectedScopes == null || selectedScopes.length == 0))">{{:: 'save' | translate}}</button>
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html
index 2101643..2ee735b 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html
@@ -62,12 +62,12 @@
<th>{{:: 'name' | translate}}</th>
<th>{{:: 'description' | translate}}</th>
<th>{{:: 'type' | translate}}</th>
- <th>{{:: 'actions' | translate}}</th>
+ <th colspan="3">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tfoot data-ng-show="policies && (policies.length >= query.max || query.first > 0)">
<tr>
- <td colspan="7">
+ <td colspan="8">
<div class="table-nav">
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">{{:: 'first-page' | translate}}</button>
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">{{:: 'previous-page' | translate}}</button>
@@ -87,9 +87,12 @@
<td ng-if="policy.details.loaded" class="kc-action-cell" data-ng-click="showDetails(policy);">
{{:: 'authz-hide-details' | translate}}
</td>
+ <td class="kc-action-cell" ng-click="delete(policy);">
+ {{:: 'delete' | translate}}
+ </td>
</tr>
<tr ng-if="policy.details && policy.details.loaded" ng-repeat-end="">
- <td colspan="4">
+ <td colspan="5">
<div id="details">
<table class="table kc-authz-table-expanded table-striped">
<thead>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html
index 4af440b..11d0827 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html
@@ -71,7 +71,7 @@
<div class="form-group" data-ng-show="access.manageAuthorization">
<div class="col-md-10 col-md-offset-2">
- <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
+ <button kc-save data-ng-disabled="!changed || (selectedPolicies == null || selectedPolicies.length == 0)">{{:: 'save' | translate}}</button>
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html
index dbaedd3..aedbdea 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html
@@ -11,7 +11,7 @@
<div data-ng-show="showResult">
<br>
- <a href="" data-ng-click="showRequestTab()">{{:: 'authz-evaluation-new' | translate}}</a>
+ <a href="" data-ng-click="showRequestTab()">{{:: 'back' | translate}}</a>
|
<a href="" data-ng-click="reevaluate()">{{:: 'authz-evaluation-re-evaluate' | translate}}</a>
|
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
index 8e9061c..cd8007f 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
@@ -45,7 +45,7 @@
href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a></strong>
decision was <span style="color: green" data-ng-show="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
<span style="color: red" data-ng-hide="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
- by <strong>{{policyResult.policy.decisionStrategy}}</strong> decision. {{policyResult.scopes.length > 0 ? 'Denied Scopes:' : ''}} <span data-ng-repeat="scope in policyResult.scopes"><strong style="color: red">{{scope.name}}{{$last ? '' : ', '}}</strong></span>{{policyResult.scopes.length > 0 ? '.' : ''}}
+ by <strong>{{policyResult.policy.decisionStrategy}}</strong> decision. {{policyResult.policy.scopes.length > 0 ? (policyResult.status == 'DENY' ? 'Denied Scopes:' : 'Granted Scopes:') : ''}} <span data-ng-repeat="scope in policyResult.policy.scopes"><strong style="color: {{(policyResult.status == 'DENY' ? 'red' : 'green')}}">{{scope}}{{$last ? '' : ', '}}</strong></span>{{policyResult.policy.scopes.length > 0 ? '.' : ''}}
<ul>
<li data-ng-repeat="subPolicy in policyResult.associatedPolicies">
<strong><a
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html
index f4e0e89..8fe2117 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html
@@ -63,12 +63,12 @@
<th>{{:: 'name' | translate}}</th>
<th>{{:: 'description' | translate}}</th>
<th>{{:: 'type' | translate}}</th>
- <th>{{:: 'actions' | translate}}</th>
+ <th colspan="3">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tfoot data-ng-show="policies && (policies.length >= query.max || query.first > 0)">
<tr>
- <td colspan="7">
+ <td colspan="8">
<div class="table-nav">
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">{{:: 'first-page' | translate}}</button>
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">{{:: 'previous-page' | translate}}</button>
@@ -88,9 +88,12 @@
<td ng-if="policy.details.loaded" class="kc-action-cell" data-ng-click="showDetails(policy);">
{{:: 'authz-hide-details' | translate}}
</td>
+ <td class="kc-action-cell" ng-click="delete(policy);">
+ {{:: 'delete' | translate}}
+ </td>
</tr>
<tr ng-if="policy.details && policy.details.loaded" ng-repeat-end="">
- <td colspan="4">
+ <td colspan="5">
<div id="details">
<table class="table kc-authz-table-expanded table-striped">
<thead>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-detail.html
index 3c28f6b..092280f 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-detail.html
@@ -57,29 +57,6 @@
</div>
</div>
</fieldset>
-
- <fieldset class="border-top" data-ng-show="server.id">
- <legend><span class="text">{{:: 'authz-export-settings' | translate}}</span>
- <kc-tooltip>{{:: 'authz-export-settings.tooltip' | translate}}</kc-tooltip>
- </legend>
- <div class="form-group">
- <label class="col-md-2 control-label" for="server.allowRemoteResourceManagement">{{:: 'authz-export-settings' | translate}}</label>
- <div class="col-md-6">
- <button data-ng-click="export()" class="btn btn-primary" data-ng-hide="settings">{{:: 'export' | translate}}</button>
- <button data-ng-click="downloadSettings()" class="btn btn-primary" data-ng-show="settings">{{:: 'download' | translate}}</button>
- <button data-ng-click="cancelExport()" class="btn btn-primary" data-ng-show="settings">{{:: 'cancel' | translate}}</button>
- </div>
- <kc-tooltip>{{:: 'authz-export-settings.tooltip' | translate}}</kc-tooltip>
- </div>
- <fieldset class="margin-top">
- <div class="form-group" ng-show="settings">
- <div class="col-sm-12">
- <a class="btn btn-primary btn-lg" data-ng-click="download()" type="submit" ng-show="installation">{{:: 'download' | translate}}</a>
- <textarea class="form-control" rows="20" kc-select-action="click">{{settings}}</textarea>
- </div>
- </div>
- </fieldset>
- </fieldset>
</form>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-export-settings.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-export-settings.html
new file mode 100644
index 0000000..86505db
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-export-settings.html
@@ -0,0 +1,35 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+ <ol class="breadcrumb">
+ <li><a href="#/realms/{{realm.realm}}/clients">{{:: 'clients' | translate}}</a></li>
+ <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></li>
+ <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'authz-authorization' | translate}}</a></li>
+ <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'export-settings' | translate}}</a></li>
+ </ol>
+
+ <kc-tabs-resource-server></kc-tabs-resource-server>
+
+ <form class="form-horizontal" name="exportForm" novalidate>
+ <fieldset>
+ <div class="form-group">
+ <label class="col-md-2 control-label">{{:: 'authz-export-settings' | translate}}</label>
+ <div class="col-md-6">
+ <button data-ng-click="export()" class="btn btn-primary" data-ng-hide="settings">{{:: 'export' | translate}}</button>
+ <button data-ng-click="downloadSettings()" class="btn btn-primary" data-ng-show="settings">{{:: 'download' | translate}}</button>
+ <button data-ng-click="cancelExport()" class="btn btn-primary" data-ng-show="settings">{{:: 'cancel' | translate}}</button>
+ </div>
+ <kc-tooltip>{{:: 'authz-export-settings.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <fieldset class="margin-top">
+ <div class="form-group" ng-show="settings">
+ <div class="col-sm-12">
+ <a class="btn btn-primary btn-lg" data-ng-click="download()" type="submit" ng-show="installation">{{:: 'download' | translate}}</a>
+ <textarea class="form-control" rows="20" kc-select-action="click">{{settings}}</textarea>
+ </div>
+ </div>
+ </fieldset>
+ </fieldset>
+ </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html
index 3080ce1..924edbc 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html
@@ -64,7 +64,7 @@
<th>{{:: 'type' | translate}}</th>
<th>{{:: 'authz-uri' | translate}}</th>
<th>{{:: 'authz-owner' | translate}}</th>
- <th colspan="2">{{:: 'actions' | translate}}</th>
+ <th colspan="3">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tfoot data-ng-show="resources && (resources.length >= query.max || query.first > 0)">
@@ -99,9 +99,12 @@
<td class="kc-action-cell" ng-click="createPolicy(resource);">
{{:: 'authz-create-permission' | translate}}
</td>
+ <td class="kc-action-cell" ng-click="delete(resource);">
+ {{:: 'delete' | translate}}
+ </td>
</tr>
<tr ng-if="resource.details && resource.details.loaded" ng-repeat-end="">
- <td colspan="6">
+ <td colspan="7">
<div id="details">
<table class="table kc-authz-table-expanded table-striped">
<thead>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html
index 8153935..519c9f5 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html
@@ -36,7 +36,7 @@
</tr>
<tr data-ng-hide="scopes.length == 0">
<th>{{:: 'name' | translate}}</th>
- <th colspan="2">{{:: 'actions' | translate}}</th>
+ <th colspan="3">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tfoot data-ng-show="scopes && (scopes.length >= query.max || query.first > 0)">
@@ -52,7 +52,7 @@
</tfoot>
<tbody>
<tr ng-repeat-start="scope in scopes | filter:search | orderBy:'name'">
- <td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope/{{scope.id}}">{{scope.name}}</a></td>
+ <td width="70%"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope/{{scope.id}}">{{scope.name}}</a></td>
<td ng-if="!scope.details.loaded" class="kc-action-cell" data-ng-click="showDetails(scope);">
{{:: 'authz-show-details' | translate}}
</td>
@@ -62,9 +62,12 @@
<td class="kc-action-cell" ng-click="createPolicy(scope);">
{{:: 'authz-create-permission' | translate}}
</td>
+ <td class="kc-action-cell" ng-click="delete(scope);">
+ {{:: 'delete' | translate}}
+ </td>
</tr>
<tr ng-if="scope.details && scope.details.loaded" ng-repeat-end="">
- <td colspan="3">
+ <td colspan="4">
<div id="details">
<table class="table kc-authz-table-expanded table-striped">
<thead>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/partial-export.html b/themes/src/main/resources/theme/base/admin/resources/partials/partial-export.html
new file mode 100644
index 0000000..1ceed8c
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/partial-export.html
@@ -0,0 +1,34 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+ <h1>
+ <span>{{:: 'partial-export' | translate}}</span>
+ <kc-tooltip>{{:: 'partial-export.tooltip' | translate}}</kc-tooltip>
+ </h1>
+
+ <form class="form-horizontal" name="partialExportForm" novalidate>
+ <fieldset class="border-top">
+
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="exportGroupsAndRoles">{{:: 'export-groups-and-roles' | translate}}</label>
+ <div class="col-sm-6">
+ <input ng-model="exportGroupsAndRoles" name="exportGroupsAndRoles" id="exportGroupsAndRoles" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="exportClients">{{:: 'export-clients' | translate}}</label>
+ <div class="col-sm-6">
+ <input ng-model="exportClients" name="exportClients" id="exportClients" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
+ </div>
+ </div>
+
+ <div class="col-sm-12">
+ <button class="btn btn-primary" data-ng-click="export()" type="submit">{{:: 'export' | translate}}</button>
+ </div>
+
+ </fieldset>
+
+ </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3.html
index a4630ac..054b98a 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3.html
@@ -1 +1,154 @@
-<div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-social.html'"></div>
\ No newline at end of file
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <ol class="breadcrumb">
+ <li><a href="#/realms/{{realm.realm}}/identity-provider-settings">{{:: 'identity-providers' | translate}}</a></li>
+ <li data-ng-hide="newIdentityProvider">{{provider.name}}</li>
+ <li data-ng-show="newIdentityProvider">{{:: 'add-identity-provider' | translate}}</li>
+ </ol>
+
+ <kc-tabs-identity-provider></kc-tabs-identity-provider>
+
+ <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageIdentityProviders">
+ <input type="text" readonly value="this is not a login form" style="display: none;">
+ <input type="password" readonly value="this is not a login form" style="display: none;">
+
+
+
+ <fieldset>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="redirectUri">{{:: 'redirect-uri' | translate}}</label>
+ <div class="col-sm-6">
+ <input class="form-control" id="redirectUri" type="text" value="{{callbackUrl}}{{identityProvider.alias}}/endpoint" readonly kc-select-action="click">
+ </div>
+ <kc-tooltip>{{:: 'redirect-uri.tooltip' | translate}}</kc-tooltip>
+ </div>
+ </fieldset>
+ <fieldset>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="identifier"><span class="required">*</span> {{:: 'alias' | translate}}</label>
+ <div class="col-md-6">
+ <input class="form-control" id="identifier" type="text" ng-model="identityProvider.alias" data-ng-readonly="!newIdentityProvider" required>
+ </div>
+ <kc-tooltip>{{:: 'identity-provider.alias.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="displayName"> {{:: 'display-name' | translate}}</label>
+ <div class="col-md-6">
+ <input class="form-control" id="displayName" type="text" ng-model="identityProvider.displayName">
+ </div>
+ <kc-tooltip>{{:: 'identity-provider.display-name.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="clientId"><span class="required">*</span> {{:: 'client-id' | translate}}</label>
+ <div class="col-md-6">
+ <input class="form-control" id="clientId" type="text" ng-model="identityProvider.config.clientId" required>
+ </div>
+ <kc-tooltip>{{:: 'social.client-id.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="clientSecret"><span class="required">*</span> {{:: 'client-secret' | translate}}</label>
+ <div class="col-md-6">
+ <input class="form-control" id="clientSecret" type="password" ng-model="identityProvider.config.clientSecret" required>
+ </div>
+ <kc-tooltip>{{:: 'social.client-secret.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-' + identityProvider.providerId + '-ext.html'"></div>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="defaultScope">{{:: 'default-scopes' | translate}} </label>
+ <div class="col-md-6">
+ <input class="form-control" id="defaultScope" type="text" ng-model="identityProvider.config.defaultScope">
+ </div>
+ <kc-tooltip>{{:: 'social.default-scopes.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="enabled">{{:: 'store-tokens' | translate}}</label>
+ <div class="col-md-6">
+ <input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'identity-provider.store-tokens.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="storedTokensReadable">{{:: 'stored-tokens-readable' | translate}}</label>
+ <div class="col-md-6">
+ <input ng-model="identityProvider.addReadTokenRoleOnCreate" id="storedTokensReadable" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="enabled">{{:: 'enabled' | translate}}</label>
+ <div class="col-md-6">
+ <input ng-model="identityProvider.enabled" id="enabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'identity-provider.enabled.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="disableUserInfo">{{:: 'disableUserInfo' | translate}}</label>
+ <div class="col-md-6">
+ <input ng-model="identityProvider.config.disableUserInfo" id="disableUserInfo" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'identity-provider.disableUserInfo.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
+ <div class="col-md-6">
+ <input ng-model="identityProvider.trustEmail" name="identityProvider.trustEmail" id="trustEmail" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'trust-email.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="linkOnly">{{:: 'link-only' | translate}}</label>
+ <div class="col-md-6">
+ <input ng-model="identityProvider.linkOnly" name="identityProvider.trustEmail" id="linkOnly" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'linkOnly.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="hideOnLoginPage">{{:: 'hide-on-login-page' | translate}}</label>
+ <div class="col-md-6">
+ <input ng-model="identityProvider.config.hideOnLoginPage" name="identityProvider.config.hideOnLoginPage" id="hideOnLoginPage" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'hide-on-login-page.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="guiOrder">{{:: 'gui-order' | translate}}</label>
+ <div class="col-md-6">
+ <input class="form-control" id="guiOrder" type="text" ng-model="identityProvider.config.guiOrder">
+ </div>
+ <kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
+ <div class="col-md-6">
+ <div>
+ <select class="form-control" id="firstBrokerLoginFlowAlias"
+ ng-model="identityProvider.firstBrokerLoginFlowAlias"
+ ng-options="flow.alias as flow.alias for flow in authFlows"
+ required>
+ </select>
+ </div>
+ </div>
+ <kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="postBrokerLoginFlowAlias">{{:: 'post-broker-login-flow' | translate}}</label>
+ <div class="col-md-6">
+ <div>
+ <select class="form-control" id="postBrokerLoginFlowAlias"
+ ng-model="identityProvider.postBrokerLoginFlowAlias"
+ ng-options="flow.alias as flow.alias for flow in postBrokerAuthFlows">
+ </select>
+ </div>
+ </div>
+ <kc-tooltip>{{:: 'post-broker-login-flow.tooltip' | translate}}</kc-tooltip>
+ </div>
+ </fieldset>
+
+ <div class="form-group">
+ <div class="col-md-10 col-md-offset-2">
+ <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
+ <button kc-cancel data-ng-click="cancel()" data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
+ </div>
+ </div>
+ </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html b/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html
index 0491364..bd20270 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/authz/kc-tabs-resource-server.html
@@ -9,5 +9,6 @@
<li ng-class="{active: path[6] == 'policy'}" data-ng-hide="create"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy">{{:: 'authz-policies' | translate}}</a></li>
<li ng-class="{active: path[6] == 'permission'}" data-ng-hide="create"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission">{{:: 'authz-permissions' | translate}}</a></li>
<li ng-class="{active: path[6] == 'evaluate'}" data-ng-hide="create"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/evaluate">{{:: 'authz-evaluate' | translate}}</a></li>
+ <li ng-class="{active: path[6] == 'export-settings'}" data-ng-hide="create"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/export-settings">{{:: 'authz-export-settings' | translate}}</a></li>
</ul>
</div>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
index 898e1a0..25d22d7 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
@@ -56,6 +56,7 @@
|| path[2] == 'events-settings'
|| path[2] == 'admin-events') && 'active'"><a href="#/realms/{{realm.realm}}/events"><i class="fa fa-calendar"></i> {{:: 'events' | translate}}</a></li>
<li data-ng-show="access.manageRealm" ng-class="(path[2] =='partial-import') && 'active'"><a href="#/realms/{{realm.realm}}/partial-import"><span class="pficon pficon-import"></span> {{:: 'import' | translate}}</a></li>
+ <li data-ng-show="access.manageRealm" ng-class="(path[2] =='partial-export') && 'active'"><a href="#/realms/{{realm.realm}}/partial-export"><span class="pficon pficon-export"></span> {{:: 'export' | translate}}</a></li>
</ul>
</div>
</div>
\ No newline at end of file
diff --git a/themes/src/main/resources-community/theme/base/email/messages/messages_de.properties b/themes/src/main/resources-community/theme/base/email/messages/messages_de.properties
index 10599d7..f8c7145 100755
--- a/themes/src/main/resources-community/theme/base/email/messages/messages_de.properties
+++ b/themes/src/main/resources-community/theme/base/email/messages/messages_de.properties
@@ -1,7 +1,7 @@
emailVerificationSubject=E-Mail verifizieren
passwordResetSubject=Passwort zur\u00FCcksetzen
-emailVerificationBody=Jemand hat ein {2} Konto mit dieser E-Mail Adresse erstellt. Fall Sie das waren, dann klicken Sie auf den Link, um die E-Mail Adresse zu verifizieren.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie dieses Konto nicht erstellt haben, dann k\u00F6nnen sie diese Nachricht ignorieren.
-emailVerificationBodyHtml=<p>Jemand hat ein {2} Konto mit dieser E-Mail Adresse erstellt. Fall das Sie waren, klicken Sie auf den Link, um die E-Mail Adresse zu verifizieren.</p><p><a href="{0}">{0}</a></p><p>Dieser Link wird in {1} Minuten ablaufen.</p><p>Falls Sie dieses Konto nicht erstellt haben, dann k\u00F6nnen sie diese Nachricht ignorieren.</p>
+emailVerificationBody=Jemand hat ein {2} Konto mit dieser E-Mail Adresse erstellt. Falls Sie das waren, dann klicken Sie auf den Link, um die E-Mail Adresse zu verifizieren.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie dieses Konto nicht erstellt haben, dann k\u00F6nnen sie diese Nachricht ignorieren.
+emailVerificationBodyHtml=<p>Jemand hat ein {2} Konto mit dieser E-Mail Adresse erstellt. Falls das Sie waren, klicken Sie auf den Link, um die E-Mail Adresse zu verifizieren.</p><p><a href="{0}">{0}</a></p><p>Dieser Link wird in {1} Minuten ablaufen.</p><p>Falls Sie dieses Konto nicht erstellt haben, dann k\u00F6nnen sie diese Nachricht ignorieren.</p>
eventLoginErrorSubject=Fehlgeschlagene Anmeldung
eventLoginErrorBody=Jemand hat um {0} von {1} versucht, sich mit ihrem Konto anzumelden. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.
eventLoginErrorBodyHtml=<p>Jemand hat um {0} von {1} versucht, sich mit ihrem Konto anzumelden. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.</p>
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml
index a76162b..839ecdf 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml
@@ -37,7 +37,7 @@
<local-cache name="loginFailures"/>
<local-cache name="work"/>
<local-cache name="authorization">
- <eviction max-entries="100" strategy="LRU"/>
+ <eviction max-entries="10000" strategy="LRU"/>
</local-cache>
<local-cache name="keys">
<eviction max-entries="1000" strategy="LRU"/>