keycloak-uncached
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java 10(+5 -5)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java 1(+1 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java 1(+1 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java 1(+1 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java 42(+39 -3)
adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java 3(+2 -1)
adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml 4(+2 -2)
authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java 4(+2 -2)
authz/client/src/main/java/org/keycloak/authorization/client/representation/AuthorizationRequestMetadata.java 48(+48 -0)
authz/client/src/main/java/org/keycloak/authorization/client/representation/EntitlementRequest.java 58(+54 -4)
authz/client/src/main/java/org/keycloak/authorization/client/representation/PermissionRequest.java 19(+19 -0)
authz/client/src/main/java/org/keycloak/authorization/client/resource/EntitlementResource.java 9(+5 -4)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java 19(+18 -1)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java 24(+24 -0)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java 19(+19 -0)
examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts 66(+0 -66)
examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.spec.ts 47(+0 -47)
examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts 42(+0 -42)
examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.service.ts 60(+0 -60)
examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.prod.ts 5(+0 -5)
examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.ts 10(+0 -10)
examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.war.ts 5(+0 -5)
examples/demo-template/pom.xml 1(+0 -1)
examples/demo-template/README.md 27(+3 -24)
misc/CrossDataCenter.md 4(+3 -1)
model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java 45(+31 -14)
model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java 1(+1 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java 8(+6 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java 8(+6 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java 18(+6 -12)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java 8(+4 -4)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java 18(+15 -3)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java 35(+29 -6)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AddInvalidatedActionTokenEvent.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RemoveActionTokensSpecificEvent.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java 5(+5 -0)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java 13(+9 -4)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java 54(+41 -13)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java 2(+1 -1)
server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java 2(+1 -1)
server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java 27(+25 -2)
server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java 4(+4 -0)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java 3(+2 -1)
server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java 6(+5 -1)
server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java 8(+7 -1)
server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java 4(+3 -1)
server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java 4(+3 -1)
server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java 4(+3 -1)
server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java 2(+1 -1)
services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java 4(+2 -2)
services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticatorFactory.java 2(+1 -1)
services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java 2(+1 -1)
services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java 64(+33 -31)
services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequest.java 9(+5 -4)
services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequestMetadata.java 51(+51 -0)
services/src/main/java/org/keycloak/authorization/entitlement/representation/EntitlementRequest.java 58(+56 -2)
services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java 14(+8 -6)
services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java 2(+1 -1)
services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java 26(+16 -10)
services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java 33(+33 -0)
services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java 2(+1 -1)
services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java 4(+0 -4)
services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java 17(+11 -6)
services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java 163(+90 -73)
services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java 64(+36 -28)
testsuite/integration-arquillian/servers/app-server/jboss/wildfly10/src/saml-adapter-supported 1(+1 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/arquillian/LoadBalancerController.java 33(+33 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java 7(+7 -0)
testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java 28(+17 -11)
testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertowConfiguration.java 28(+28 -0)
testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java 79(+48 -31)
testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerConfiguration.java 18(+18 -0)
testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerContainer.java 23(+22 -1)
testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SetSystemProperty.java 13(+10 -3)
testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-remote-store.xsl 42(+42 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAutodetectServlet.java 39(+39 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/LoadBalancer.java 32(+32 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java 122(+80 -42)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java 10(+10 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/RegistryCreator.java 2(+2 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java 4(+3 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/LoadBalancerControllerProvider.java 67(+67 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java 79(+67 -12)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java 5(+5 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SetSystemProperty.java 60(+60 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java 1(+1 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java 56(+56 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java 4(+3 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ExportAuthorizationSettingsTest.java 187(+187 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java 43(+43 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java 285(+285 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java 20(+18 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java 81(+81 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java 161(+161 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java 152(+152 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java 4(+3 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java 32(+23 -9)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java 9(+8 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/policy/PasswordPolicyTest.java 300(+189 -111)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-autodetect/WEB-INF/keycloak-saml.xml 44(+44 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json 10(+9 -1)
testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js 6(+3 -3)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/cluster/Wildfly10SAMLAdapterClusterTest.java 97(+97 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10DefaultAuthzConfigAdapterTest.java 30(+30 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10PermissiveModeAdapterTest.java 31(+31 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10PhotozExampleAdapterTest.java 30(+30 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10ServletAuthzAdapterTest.java 31(+31 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10ServletPolicyEnforcerAdapterTest.java 28(+10 -18)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/cors/Wildfly10CorsExampleAdapterTest.java 12(+12 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10BasicAuthExampleAdapterTest.java 12(+12 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10DemoExampleAdapterTest.java 12(+12 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10JSConsoleExampleAdapterTest.java 13(+13 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10SAMLExampleAdapterTest.java 11(+11 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10ClientInitiatedAccountLinkTest.java 29(+29 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OfflineServletsAdapterTest.java 11(+11 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCAdapterTest.java 13(+13 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCFilterAdapterTest.java 12(+12 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCPublicKeyRotationAdapterTest.java 28(+28 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCSessionAdapterTest.java 13(+13 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10SAMLAdapterTest.java 12(+12 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10SAMLFilterAdapterTest.java 11(+11 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/resources/adapter-test/keycloak-saml/employee-distributable/WEB-INF/web.xml 62(+62 -0)
testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/resource/ResourcesTable.java 20(+12 -8)
testsuite/integration-arquillian/tests/pom.xml 182(+180 -2)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index 61f46f1..f3127be 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -78,13 +78,13 @@ public abstract class AbstractPolicyEnforcer {
if (pathConfig == null) {
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
- return createAuthorizationContext(accessToken);
+ return createAuthorizationContext(accessToken, null);
}
LOGGER.debugf("Could not find a configuration for path [%s]", path);
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
- return createAuthorizationContext(accessToken);
+ return createAuthorizationContext(accessToken, null);
}
handleAccessDenied(httpFacade);
@@ -100,7 +100,7 @@ public abstract class AbstractPolicyEnforcer {
if (isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
try {
- return createAuthorizationContext(accessToken);
+ return createAuthorizationContext(accessToken, pathConfig);
} catch (Exception e) {
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
}
@@ -252,8 +252,8 @@ public abstract class AbstractPolicyEnforcer {
return requiredScopes;
}
- private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
- return new ClientAuthorizationContext(accessToken, this.paths, authzClient);
+ private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
+ return new ClientAuthorizationContext(accessToken, pathConfig, this.paths, authzClient);
}
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
index 256bfa2..ecde04a 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
@@ -33,6 +33,7 @@ public class ConfigXmlConstants {
public static final String SIGNATURE_ALGORITHM_ATTR = "signatureAlgorithm";
public static final String SIGNATURE_CANONICALIZATION_METHOD_ATTR = "signatureCanonicalizationMethod";
public static final String LOGOUT_PAGE_ATTR = "logoutPage";
+ public static final String AUTODETECT_BEARER_ONLY_ATTR = "autodetectBearerOnly";
public static final String KEYS_ELEMENT = "Keys";
public static final String KEY_ELEMENT = "Key";
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
index b5f8aba..92254a2 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
@@ -68,6 +68,7 @@ public class DeploymentBuilder {
deployment.setNameIDPolicyFormat(sp.getNameIDPolicyFormat());
deployment.setLogoutPage(sp.getLogoutPage());
deployment.setSignatureCanonicalizationMethod(sp.getIdp().getSignatureCanonicalizationMethod());
+ deployment.setAutodetectBearerOnly(sp.isAutodetectBearerOnly());
deployment.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
if (sp.getIdp().getSignatureAlgorithm() != null) {
deployment.setSignatureAlgorithm(SignatureAlgorithm.valueOf(sp.getIdp().getSignatureAlgorithm()));
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
index be6d682..57d5f58 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
@@ -89,6 +89,7 @@ public class SPXmlParser extends AbstractParser {
sp.setNameIDPolicyFormat(getAttributeValue(startElement, ConfigXmlConstants.NAME_ID_POLICY_FORMAT_ATTR));
sp.setForceAuthentication(getBooleanAttributeValue(startElement, ConfigXmlConstants.FORCE_AUTHENTICATION_ATTR));
sp.setIsPassive(getBooleanAttributeValue(startElement, ConfigXmlConstants.IS_PASSIVE_ATTR));
+ sp.setAutodetectBearerOnly(getBooleanAttributeValue(startElement, ConfigXmlConstants.AUTODETECT_BEARER_ONLY_ATTR));
sp.setTurnOffChangeSessionIdOnLogin(getBooleanAttributeValue(startElement, ConfigXmlConstants.TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN_ATTR));
while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
index 475a0f4..071b170 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
@@ -58,6 +58,7 @@ public class SP implements Serializable {
private PrincipalNameMapping principalNameMapping;
private Set<String> roleAttributes;
private IDP idp;
+ private boolean autodetectBearerOnly;
public String getEntityID() {
return entityID;
@@ -147,4 +148,11 @@ public class SP implements Serializable {
this.logoutPage = logoutPage;
}
+ public boolean isAutodetectBearerOnly() {
+ return autodetectBearerOnly;
+ }
+
+ public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
+ this.autodetectBearerOnly = autodetectBearerOnly;
+ }
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
index d92884f..9a6e288 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
@@ -294,6 +294,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
private String logoutPage;
private SignatureAlgorithm signatureAlgorithm;
private String signatureCanonicalizationMethod;
+ private boolean autodetectBearerOnly;
@Override
public boolean turnOffChangeSessionIdOnLogin() {
@@ -439,4 +440,13 @@ public class DefaultSamlDeployment implements SamlDeployment {
public void setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
this.signatureAlgorithm = signatureAlgorithm;
}
+
+ @Override
+ public boolean isAutodetectBearerOnly() {
+ return autodetectBearerOnly;
+ }
+
+ public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
+ this.autodetectBearerOnly = autodetectBearerOnly;
+ }
}
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 5721b03..1613593 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
@@ -568,9 +568,15 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return new AbstractInitiateLogin(deployment, sessionStore) {
@Override
protected void sendAuthnRequest(HttpFacade httpFacade, SAML2AuthnRequestBuilder authnRequestBuilder, BaseSAML2BindingBuilder binding) throws ProcessingException, ConfigurationException, IOException {
- Document document = authnRequestBuilder.toDocument();
- SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding();
- SamlUtil.sendSaml(true, httpFacade, deployment.getIDP().getSingleSignOnService().getRequestBindingUrl(), binding, document, samlBinding);
+ if (isAutodetectedBearerOnly(httpFacade.getRequest())) {
+ httpFacade.getResponse().setStatus(401);
+ httpFacade.getResponse().end();
+ }
+ else {
+ Document document = authnRequestBuilder.toDocument();
+ SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding();
+ SamlUtil.sendSaml(true, httpFacade, deployment.getIDP().getSingleSignOnService().getRequestBindingUrl(), binding, document, samlBinding);
+ }
}
};
}
@@ -693,4 +699,34 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return signature.verify(decodedSignature);
}
+
+ protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
+ if (!deployment.isAutodetectBearerOnly()) return false;
+
+ String headerValue = facade.getRequest().getHeader(GeneralConstants.HTTP_HEADER_X_REQUESTED_WITH);
+ if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
+ return true;
+ }
+
+ headerValue = facade.getRequest().getHeader("Faces-Request");
+ if (headerValue != null && headerValue.startsWith("partial/")) {
+ return true;
+ }
+
+ headerValue = facade.getRequest().getHeader("SOAPAction");
+ if (headerValue != null) {
+ return true;
+ }
+
+ List<String> accepts = facade.getRequest().getHeaders("Accept");
+ if (accepts == null) accepts = Collections.emptyList();
+
+ for (String accept : accepts) {
+ if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
index a9df7e9..0b7ac40 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -168,6 +168,6 @@ public interface SamlDeployment {
}
PrincipalNamePolicy getPrincipalNamePolicy();
String getPrincipalAttributeName();
-
+ boolean isAutodetectBearerOnly();
}
diff --git a/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_9.xsd b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_9.xsd
new file mode 100644
index 0000000..cf9c05c
--- /dev/null
+++ b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_9.xsd
@@ -0,0 +1,461 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+ ~ and other contributors as indicated by the @author tags.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<xs:schema version="1.0"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns="urn:keycloak:saml:adapter"
+ targetNamespace="urn:keycloak:saml:adapter"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xs:element name="keycloak-saml-adapter" type="adapter-type"/>
+ <xs:complexType name="adapter-type">
+ <xs:annotation>
+ <xs:documentation>Keycloak SAML Adapter configuration file.</xs:documentation>
+ </xs:annotation>
+ <xs:all>
+ <xs:element name="SP" maxOccurs="1" minOccurs="0" type="sp-type">
+ <xs:annotation>
+ <xs:documentation>Describes SAML service provider configuration.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:all>
+ </xs:complexType>
+
+ <xs:complexType name="sp-type">
+ <xs:all>
+ <xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>
+ List of service provider encryption and validation keys.
+
+ If the IDP requires that the client application (SP) sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. For client signed documents you must define both the private and public key or certificate that will be used to sign documents. For encryption, you only have to define the private key that will be used to decrypt.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="PrincipalNameMapping" type="principal-name-mapping-type" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>When creating a Java Principal object that you obtain from methods like HttpServletRequest.getUserPrincipal(), you can define what name that is returned by the Principal.getName() method.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="RoleIdentifiers" type="role-identifiers-type" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>Defines what SAML attributes within the assertion received from the user should be used as role identifiers within the Java EE Security Context for the user.
+ By default Role attribute values are converted to Java EE roles. Some IDPs send roles via a member or memberOf attribute assertion. You can define one or more Attribute elements to specify which SAML attributes must be converted into roles.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="IDP" type="idp-type" minOccurs="1" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>Describes configuration of SAML identity provider for this service provider.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:all>
+ <xs:attribute name="entityID" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>This is the identifier for this client. The IDP needs this value to determine who the client is that is communicating with it.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="sslPolicy" type="ssl-policy-type" use="optional">
+ <xs:annotation>
+ <xs:documentation>SSL policy the adapter will enforce.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. It must be a standard SAML format identifier, i.e. urn:oasis:names:tc:SAML:2.0:nameid-format:transient. By default, no special format is requested.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="logoutPage" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>URL of the logout page.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="forceAuthentication" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. Default value is false.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="isPassive" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to true if you want this. Do not use together with forceAuthentication as they are opposite. Default value is false.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="turnOffChangeSessionIdOnLogin" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true to disable this. It is recommended you do not turn it off. Default value is false.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="autodetectBearerOnly" type="xs:boolean" use="optional" default="false">
+ <xs:annotation>
+ <xs:documentation>This should be set to true if your application serves both a web application and web services (e.g. SOAP or REST). It allows you to redirect unauthenticated users of the web application to the Keycloak login page, but send an HTTP 401 status code to unauthenticated SOAP or REST clients instead as they would not understand a redirect to the login page. Keycloak auto-detects SOAP or REST clients based on typical headers like X-Requested-With, SOAPAction or Accept. The default value is false.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="keys-type">
+ <xs:sequence>
+ <xs:element name="Key" type="key-type" minOccurs="1" maxOccurs="unbounded">
+ <xs:annotation>
+ <xs:documentation>Describes a single key used for signing or encryption.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="key-type">
+ <xs:all>
+ <xs:element name="KeyStore" maxOccurs="1" minOccurs="0" type="key-store-type">
+ <xs:annotation>
+ <xs:documentation>Java keystore to load keys and certificates from.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="PrivateKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>Private key (PEM format)</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="PublicKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>Public key (PEM format)</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="CertificatePem" type="xs:string" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>Certificate key (PEM format)</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:all>
+ <xs:attribute name="signing" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Flag defining whether the key should be used for signing.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="encryption" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Flag defining whether the key should be used for encryption</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="key-store-type">
+ <xs:all>
+ <xs:element name="PrivateKey" maxOccurs="1" minOccurs="0" type="private-key-type">
+ <xs:annotation>
+ <xs:documentation>Private key declaration</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="Certificate" type="certificate-type" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>Certificate declaration</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:all>
+ <xs:attribute name="file" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>File path to the key store.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="resource" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>WAR resource path to the key store. This is a path used in method call to ServletContext.getResourceAsStream().</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="password" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The password of the key store.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="private-key-type">
+ <xs:attribute name="alias" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="password" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Keystores require an additional password to access private keys. In the PrivateKey element you must define this password within a password attribute.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="certificate-type">
+ <xs:attribute name="alias" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="principal-name-mapping-type">
+ <xs:attribute name="policy" type="principal-name-mapping-policy-type" use="required">
+ <xs:annotation>
+ <xs:documentation>Policy used to populate value of Java Principal object obtained from methods like HttpServletRequest.getUserPrincipal().</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="attribute" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Name of the SAML assertion attribute to use within.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:simpleType name="principal-name-mapping-policy-type">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="FROM_NAME_ID">
+ <xs:annotation>
+ <xs:documentation>This policy just uses whatever the SAML subject value is. This is the default setting</xs:documentation>
+ </xs:annotation>
+ </xs:enumeration>
+ <xs:enumeration value="FROM_ATTRIBUTE">
+ <xs:annotation>
+ <xs:documentation>This will pull the value from one of the attributes declared in the SAML assertion received from the server. You'll need to specify the name of the SAML assertion attribute to use within the attribute XML attribute.</xs:documentation>
+ </xs:annotation>
+ </xs:enumeration>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType name="ssl-policy-type">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="ALL">
+ <xs:annotation>
+ <xs:documentation>All requests must come in via HTTPS.</xs:documentation>
+ </xs:annotation>
+ </xs:enumeration>
+ <xs:enumeration value="EXTERNAL">
+ <xs:annotation>
+ <xs:documentation>Only non-private IP addresses must come over the wire via HTTPS.</xs:documentation>
+ </xs:annotation>
+ </xs:enumeration>
+ <xs:enumeration value="NONE">
+ <xs:annotation>
+ <xs:documentation>no requests are required to come over via HTTPS.</xs:documentation>
+ </xs:annotation>
+ </xs:enumeration>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType name="signature-algorithm-type">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="RSA_SHA1"/>
+ <xs:enumeration value="RSA_SHA256"/>
+ <xs:enumeration value="RSA_SHA512"/>
+ <xs:enumeration value="DSA_SHA1"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType name="binding-type">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="POST"/>
+ <xs:enumeration value="REDIRECT"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:complexType name="role-identifiers-type">
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="Attribute" maxOccurs="unbounded" minOccurs="0" type="attribute-type">
+ <xs:annotation>
+ <xs:documentation>Specifies SAML attribute to be converted into roles.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:choice>
+ </xs:complexType>
+ <xs:complexType name="attribute-type">
+ <xs:attribute name="name" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Specifies name of the SAML attribute to be converted into roles.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="idp-type">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="SingleSignOnService" maxOccurs="1" minOccurs="1" type="sign-on-type">
+ <xs:annotation>
+ <xs:documentation>Configuration of the login SAML endpoint of the IDP.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="SingleLogoutService" type="logout-type" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>Configuration of the logout SAML endpoint of the IDP</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="HttpClient" type="http-client-type" minOccurs="0" maxOccurs="1">
+ <xs:annotation>
+ <xs:documentation>Configuration of HTTP client used for automatic obtaining of certificates containing public keys for IDP signature verification via SAML descriptor of the IDP.</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:sequence>
+ <xs:attribute name="entityID" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>issuer ID of the IDP.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signaturesRequired" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>If set to true, the client adapter will sign every document it sends to the IDP. Also, the client will expect that the IDP will be signing any documents sent to it. This switch sets the default for all request and response types.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signatureAlgorithm" type="signature-algorithm-type" use="optional">
+ <xs:annotation>
+ <xs:documentation>Signature algorithm that the IDP expects signed documents to use. Defaults to RSA_SHA256</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>This is the signature canonicalization method that the IDP expects signed documents to use. The default value is https://www.w3.org/2001/10/xml-exc-c14n# and should be good for most IDPs.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="encryption" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation></xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="sign-on-type">
+ <xs:attribute name="signRequest" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="validateAssertionSignature" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Should the client expect the IDP to sign the individual assertions sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="requestBinding" type="binding-type" use="optional">
+ <xs:annotation>
+ <xs:documentation>SAML binding type used for communicating with the IDP. The default value is POST, but you can set it to REDIRECT as well.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="responseBinding" type="binding-type" use="optional">
+ <xs:annotation>
+ <xs:documentation>SAML allows the client to request what binding type it wants authn responses to use. This value maps to ProtocolBinding attribute in SAML AuthnRequest. The default is that the client will not request a specific binding type for responses.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="bindingUrl" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>This is the URL for the IDP login service that the client will send requests to.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="assertionConsumerServiceUrl" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>URL of the assertion consumer service (ACS) where the IDP login service should send responses to. By default it is unset, relying on the IdP settings. When set, it must end in "/saml". This property is typically accompanied by the responseBinding attribute.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="logout-type">
+ <xs:attribute name="signRequest" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signResponse" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Should the client sign logout responses it sends to the IDP requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Should the client expect signed logout request documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Should the client expect signed logout response documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="requestBinding" type="binding-type" use="optional">
+ <xs:annotation>
+ <xs:documentation>This is the SAML binding type used for communicating SAML requests to the IDP. The default value is POST.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="responseBinding" type="binding-type" use="optional">
+ <xs:annotation>
+ <xs:documentation>This is the SAML binding type used for communicating SAML responses to the IDP. The default value is POST.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="postBindingUrl" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>This is the URL for the IDP's logout service when using the POST binding. This setting is REQUIRED if using the POST binding.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="redirectBindingUrl" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>This is the URL for the IDP's logout service when using the REDIRECT binding. This setting is REQUIRED if using the REDIRECT binding.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="http-client-type">
+ <xs:attribute name="allowAnyHostname" type="xs:boolean" use="optional" default="false">
+ <xs:annotation>
+ <xs:documentation>If the the IDP server requires HTTPS and this config option is set to true the IDP's certificate
+ is validated via the truststore, but host name validation is not done. This setting should only be used during
+ development and never in production as it will partly disable verification of SSL certificates.
+ This seting may be useful in test environments. The default value is false.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="clientKeystore" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>This is the file path to a keystore file. This keystore contains client certificate
+ for two-way SSL when the adapter makes HTTPS requests to the IDP server.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="clientKeystorePassword" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Password for the client keystore and for the client's key.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="connectionPoolSize" type="xs:int" use="optional" default="10">
+ <xs:annotation>
+ <xs:documentation>Defines number of pooled connections.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="disableTrustManager" type="xs:boolean" use="optional" default="false">
+ <xs:annotation>
+ <xs:documentation>If the the IDP server requires HTTPS and this config option is set to true you do not have to specify a truststore.
+ This setting should only be used during development and never in production as it will disable verification of SSL certificates.
+ The default value is false.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="proxyUrl" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>URL to HTTP proxy to use for HTTP connections.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="truststore" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
+ then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
+ HTTPS communications to the IDP server. Client making HTTPS requests need
+ a way to verify the host of the server they are talking to. This is what the trustore does.
+ The keystore contains one or more trusted host certificates or certificate authorities.
+ You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="truststorePassword" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Password for the truststore keystore.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+</xs:schema>
diff --git a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
index b64f181..88a96cb 100755
--- a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
+++ b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
@@ -37,7 +37,7 @@ import org.keycloak.saml.common.exceptions.ParsingException;
*/
public class KeycloakSamlAdapterXMLParserTest {
- private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_7.xsd";
+ private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_9.xsd";
@Rule
public ExpectedException expectedException = ExpectedException.none();
@@ -91,6 +91,7 @@ public class KeycloakSamlAdapterXMLParserTest {
assertEquals("format", sp.getNameIDPolicyFormat());
assertTrue(sp.isForceAuthentication());
assertTrue(sp.isIsPassive());
+ assertFalse(sp.isAutodetectBearerOnly());
assertEquals(2, sp.getKeys().size());
Key signing = sp.getKeys().get(0);
assertTrue(signing.isSigning());
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml
index 193525a..31f62e5 100755
--- a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml
@@ -17,7 +17,7 @@
<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">
+ xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_9.xsd">
<SP entityID="sp"
sslPolicy="EXTERNAL"
nameIDPolicyFormat="format"
@@ -73,4 +73,4 @@
</Keys>
</IDP>
</SP>
-</keycloak-saml-adapter>
\ No newline at end of file
+</keycloak-saml-adapter>
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java b/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java
index 73bcd9f..a46e511 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/ClientAuthorizationContext.java
@@ -30,8 +30,8 @@ public class ClientAuthorizationContext extends AuthorizationContext {
private final AuthzClient client;
- public ClientAuthorizationContext(AccessToken authzToken, Map<String, PolicyEnforcerConfig.PathConfig> paths, AuthzClient client) {
- super(authzToken, paths);
+ public ClientAuthorizationContext(AccessToken authzToken, PolicyEnforcerConfig.PathConfig current, Map<String, PolicyEnforcerConfig.PathConfig> paths, AuthzClient client) {
+ super(authzToken, current, paths);
this.client = client;
}
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/representation/AuthorizationRequestMetadata.java b/authz/client/src/main/java/org/keycloak/authorization/client/representation/AuthorizationRequestMetadata.java
new file mode 100644
index 0000000..ca45017
--- /dev/null
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/representation/AuthorizationRequestMetadata.java
@@ -0,0 +1,48 @@
+/*
+ * 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.authorization.client.representation;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class AuthorizationRequestMetadata {
+
+ public static final String INCLUDE_RESOURCE_NAME = "include_resource_name";
+
+ @JsonProperty(INCLUDE_RESOURCE_NAME)
+ private boolean includeResourceName = true;
+
+ private int limit;
+
+ public boolean isIncludeResourceName() {
+ return includeResourceName;
+ }
+
+ public void setIncludeResourceName(boolean includeResourceName) {
+ this.includeResourceName = includeResourceName;
+ }
+
+ public void setLimit(int limit) {
+ this.limit = limit;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+}
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/representation/EntitlementRequest.java b/authz/client/src/main/java/org/keycloak/authorization/client/representation/EntitlementRequest.java
index daec233..b3efa85 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/representation/EntitlementRequest.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/representation/EntitlementRequest.java
@@ -4,31 +4,81 @@ import java.util.ArrayList;
import java.util.List;
/**
+ * <p>An {@code {@link EntitlementRequest} represents a request sent to the server containing the permissions being requested.
+ *
+ * <p>Along with an entitlement request additional {@link AuthorizationRequestMetadata} information can be passed in order to define what clients expect from
+ * the server when evaluating the requested permissions and when returning with a response.
+ *
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class EntitlementRequest {
private String rpt;
+ private AuthorizationRequestMetadata metadata;
private List<PermissionRequest> permissions = new ArrayList<>();
+ /**
+ * Returns the permissions being requested.
+ *
+ * @return the permissions being requested (not {@code null})
+ */
public List<PermissionRequest> getPermissions() {
return permissions;
}
+ /**
+ * Set the permissions being requested
+ *
+ * @param permissions the permissions being requests (not {@code null})
+ */
+ public void setPermissions(List<PermissionRequest> permissions) {
+ this.permissions = permissions;
+ }
+
+ /**
+ * Adds the given {@link PermissionRequest} to the list of requested permissions.
+ *
+ * @param request the permission to request (not {@code null})
+ */
+ public void addPermission(PermissionRequest request) {
+ getPermissions().add(request);
+ }
+
+ /**
+ * Returns a {@code String} representing a previously issued RPT which permissions will be included the response in addition to the new ones being requested.
+ *
+ * @return a previously issued RPT (may be {@code null})
+ */
public String getRpt() {
return rpt;
}
+ /**
+ * A {@code String} representing a previously issued RPT which permissions will be included the response in addition to the new ones being requested.
+ *
+ * @param rpt a previously issued RPT. If {@code null}, only the requested permissions are evaluated
+ */
public void setRpt(String rpt) {
this.rpt = rpt;
}
- public void setPermissions(List<PermissionRequest> permissions) {
- this.permissions = permissions;
+ /**
+ * Return the {@link Metadata} associated with this request.
+ *
+ * @return
+ */
+ public AuthorizationRequestMetadata getMetadata() {
+ return metadata;
}
- public void addPermission(PermissionRequest request) {
- getPermissions().add(request);
+ /**
+ * The {@link Metadata} associated with this request. The metadata defines specific information that should be considered
+ * by the server when evaluating and returning permissions.
+ *
+ * @param metadata the {@link Metadata} associated with this request (may be {@code null})
+ */
+ public void setMetadata(AuthorizationRequestMetadata metadata) {
+ this.metadata = metadata;
}
}
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/representation/PermissionRequest.java b/authz/client/src/main/java/org/keycloak/authorization/client/representation/PermissionRequest.java
index 39518fc..38d5471 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/representation/PermissionRequest.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/representation/PermissionRequest.java
@@ -34,6 +34,25 @@ public class PermissionRequest {
private Set<String> scopes;
+ public PermissionRequest() {
+
+ }
+
+ public PermissionRequest(String resourceSetId, String resourceSetName, Set<String> scopes) {
+ this.resourceSetId = resourceSetId;
+ this.resourceSetName = resourceSetName;
+ this.scopes = scopes;
+ }
+
+ public PermissionRequest(String resourceSetName) {
+ this.resourceSetName = resourceSetName;
+ }
+
+ public PermissionRequest(String resourceSetName, Set<String> scopes) {
+ this.resourceSetName = resourceSetName;
+ this.scopes = scopes;
+ }
+
public String getResourceSetId() {
return this.resourceSetId;
}
diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/resource/EntitlementResource.java b/authz/client/src/main/java/org/keycloak/authorization/client/resource/EntitlementResource.java
index 55c0abd..8c12abf 100644
--- a/authz/client/src/main/java/org/keycloak/authorization/client/resource/EntitlementResource.java
+++ b/authz/client/src/main/java/org/keycloak/authorization/client/resource/EntitlementResource.java
@@ -1,9 +1,11 @@
package org.keycloak.authorization.client.resource;
import org.keycloak.authorization.client.AuthorizationDeniedException;
+import org.keycloak.authorization.client.representation.AuthorizationRequestMetadata;
import org.keycloak.authorization.client.representation.EntitlementRequest;
import org.keycloak.authorization.client.representation.EntitlementResponse;
import org.keycloak.authorization.client.util.Http;
+import org.keycloak.authorization.client.util.HttpMethod;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.util.JsonSerialization;
@@ -23,9 +25,8 @@ public class EntitlementResource {
public EntitlementResponse getAll(String resourceServerId) {
try {
return this.http.<EntitlementResponse>get("/authz/entitlement/" + resourceServerId)
- .authorizationBearer(this.eat)
- .response()
- .json(EntitlementResponse.class).execute();
+ .authorizationBearer(eat)
+ .response().json(EntitlementResponse.class).execute();
} catch (HttpResponseException e) {
if (403 == e.getStatusCode()) {
throw new AuthorizationDeniedException(e);
@@ -39,7 +40,7 @@ public class EntitlementResource {
public EntitlementResponse get(String resourceServerId, EntitlementRequest request) {
try {
return this.http.<EntitlementResponse>post("/authz/entitlement/" + resourceServerId)
- .authorizationBearer(this.eat)
+ .authorizationBearer(eat)
.json(JsonSerialization.writeValueAsBytes(request))
.response().json(EntitlementResponse.class).execute();
} catch (HttpResponseException e) {
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java
index 49a54eb..6d7ed54 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java
@@ -3,10 +3,12 @@ package org.keycloak.authorization.policy.provider.client;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;
@@ -72,6 +74,21 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory<Client
}
@Override
+ public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
+ ClientPolicyRepresentation userRep = toRepresentation(policy, new ClientPolicyRepresentation());
+ Map<String, String> config = new HashMap<>();
+
+ try {
+ RealmModel realm = authorizationProvider.getRealm();
+ config.put("clients", JsonSerialization.writeValueAsString(userRep.getClients().stream().map(id -> realm.getClientById(id).getClientId()).collect(Collectors.toList())));
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to export user policy [" + policy.getName() + "]", cause);
+ }
+
+ representation.setConfig(config);
+ }
+
+ @Override
public PolicyProvider create(KeycloakSession session) {
return null;
}
@@ -129,7 +146,7 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory<Client
}
private void updateClients(Policy policy, Set<String> clients, AuthorizationProvider authorization) {
- RealmModel realm = authorization.getKeycloakSession().getContext().getRealm();
+ RealmModel realm = authorization.getRealm();
if (clients == null || clients.isEmpty()) {
throw new RuntimeException("No client provided.");
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
index 64bcf49..933a859 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java
@@ -108,6 +108,30 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePoli
}
}
+ @Override
+ public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
+ Map<String, String> config = new HashMap<>();
+ Set<RolePolicyRepresentation.RoleDefinition> roles = toRepresentation(policy, new RolePolicyRepresentation()).getRoles();
+
+ for (RolePolicyRepresentation.RoleDefinition roleDefinition : roles) {
+ RoleModel role = authorizationProvider.getRealm().getRoleById(roleDefinition.getId());
+
+ if (role.isClientRole()) {
+ roleDefinition.setId(ClientModel.class.cast(role.getContainer()).getClientId() + "/" + role.getName());
+ } else {
+ roleDefinition.setId(role.getName());
+ }
+ }
+
+ try {
+ config.put("roles", JsonSerialization.writeValueAsString(roles));
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to export role policy [" + policy.getName() + "]", cause);
+ }
+
+ representation.setConfig(config);
+ }
+
private void updateRoles(Policy policy, RolePolicyRepresentation representation, AuthorizationProvider authorization) {
updateRoles(policy, authorization, representation.getRoles());
}
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java
index a21bf74..5a90f93 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java
@@ -20,11 +20,13 @@ package org.keycloak.authorization.policy.provider.user;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
+import java.util.stream.Collectors;
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;
@@ -106,6 +108,23 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory<UserPoli
}
}
+ @Override
+ public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
+ UserPolicyRepresentation userRep = toRepresentation(policy, new UserPolicyRepresentation());
+ Map<String, String> config = new HashMap<>();
+
+ try {
+ UserProvider userProvider = authorizationProvider.getKeycloakSession().users();
+ RealmModel realm = authorizationProvider.getRealm();
+
+ config.put("users", JsonSerialization.writeValueAsString(userRep.getUsers().stream().map(id -> userProvider.getUserById(id, realm).getUsername()).collect(Collectors.toList())));
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to export user policy [" + policy.getName() + "]", cause);
+ }
+
+ representation.setConfig(config);
+ }
+
private void updateUsers(Policy policy, UserPolicyRepresentation representation, AuthorizationProvider authorization) {
updateUsers(policy, authorization, representation.getUsers());
}
diff --git a/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java b/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java
index 1fdfff8..78fc4c7 100644
--- a/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java
+++ b/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java
@@ -17,6 +17,8 @@
package org.keycloak.common.util;
+import java.util.Collections;
+import java.util.Map;
import java.util.Properties;
/**
@@ -24,9 +26,21 @@ import java.util.Properties;
*/
public class SystemEnvProperties extends Properties {
+ private final Map<String, String> overrides;
+
+ public SystemEnvProperties(Map<String, String> overrides) {
+ this.overrides = overrides;
+ }
+
+ public SystemEnvProperties() {
+ this.overrides = Collections.EMPTY_MAP;
+ }
+
@Override
public String getProperty(String key) {
- if (key.startsWith("env.")) {
+ if (overrides.containsKey(key)) {
+ return overrides.get(key);
+ } else if (key.startsWith("env.")) {
return System.getenv().get(key.substring(4));
} else {
return System.getProperty(key);
diff --git a/core/src/main/java/org/keycloak/AuthorizationContext.java b/core/src/main/java/org/keycloak/AuthorizationContext.java
index 93f3ff1..e096e7e 100644
--- a/core/src/main/java/org/keycloak/AuthorizationContext.java
+++ b/core/src/main/java/org/keycloak/AuthorizationContext.java
@@ -32,17 +32,19 @@ import java.util.Map;
public class AuthorizationContext {
private final AccessToken authzToken;
+ private final PathConfig current;
private final Map<String, PathConfig> paths;
private boolean granted;
- public AuthorizationContext(AccessToken authzToken, Map<String, PathConfig> paths) {
+ public AuthorizationContext(AccessToken authzToken, PathConfig current, Map<String, PathConfig> paths) {
this.authzToken = authzToken;
+ this.current = current;
this.paths = paths;
this.granted = true;
}
public AuthorizationContext() {
- this(null, null);
+ this(null, null, null);
this.granted = false;
}
@@ -57,9 +59,15 @@ public class AuthorizationContext {
return false;
}
- for (Permission permission : authorization.getPermissions()) {
- for (PathConfig pathHolder : this.paths.values()) {
- if (pathHolder.getName().equals(resourceName)) {
+ if (current != null) {
+ if (current.getName().equals(resourceName)) {
+ return true;
+ }
+ }
+
+ if (hasResourcePermission(resourceName)) {
+ for (Permission permission : authorization.getPermissions()) {
+ for (PathConfig pathHolder : paths.values()) {
if (pathHolder.getId().equals(permission.getResourceSetId())) {
if (permission.getScopes().contains(scopeName)) {
return true;
@@ -83,13 +91,15 @@ public class AuthorizationContext {
return false;
}
+ if (current != null) {
+ if (current.getName().equals(resourceName)) {
+ return true;
+ }
+ }
+
for (Permission permission : authorization.getPermissions()) {
- for (PathConfig pathHolder : this.paths.values()) {
- if (pathHolder.getName().equals(resourceName)) {
- if (pathHolder.getId().equals(permission.getResourceSetId())) {
- return true;
- }
- }
+ if (permission.getResourceSetName().equals(resourceName) || permission.getResourceSetId().equals(resourceName)) {
+ return true;
}
}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
index dd94537..a495cad 100644
--- a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
@@ -62,7 +62,7 @@ public class PolicyEnforcerConfig {
}
public List<PathConfig> getPaths() {
- return Collections.unmodifiableList(this.paths);
+ return this.paths;
}
public EnforcementMode getEnforcementMode() {
examples/demo-template/pom.xml 1(+0 -1)
diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml
index a737bf8..bd239fb 100755
--- a/examples/demo-template/pom.xml
+++ b/examples/demo-template/pom.xml
@@ -51,7 +51,6 @@
<module>example-ear</module>
<module>admin-access-app</module>
<module>angular-product-app</module>
- <module>angular2-product-app</module>
<module>database-service</module>
<module>third-party</module>
<module>third-party-cdi</module>
examples/demo-template/README.md 27(+3 -24)
diff --git a/examples/demo-template/README.md b/examples/demo-template/README.md
index 52b3001..950b226 100755
--- a/examples/demo-template/README.md
+++ b/examples/demo-template/README.md
@@ -202,28 +202,7 @@ An Angular JS example using Keycloak to secure it.
If you are already logged in, you will not be asked for a username and password, but you will be redirected to
an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
-Step 10: Angular2 JS Example
-----------------------------------
-An Angular2 JS example using Keycloak to secure it. Angular2 is in beta version yet.
-
-To install angular2
-```
-$ cd keycloak/examples/demo-template/angular2-product-app/src/main/webapp/
-$ npm install
-```
-
-Transpile TypeScript to JavaScript before running the application.
-```
-$ npm run tsc
-```
-
-[http://localhost:8080/angular2-product](http://localhost:8080/angular2-product)
-
-If you are already logged in, you will not be asked for a username and password, but you will be redirected to
-an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
-
-
-Step 11: Pure HTML5/Javascript Example
+Step 10: Pure HTML5/Javascript Example
----------------------------------
An pure HTML5/Javascript example using Keycloak to secure it.
@@ -232,7 +211,7 @@ An pure HTML5/Javascript example using Keycloak to secure it.
If you are already logged in, you will not be asked for a username and password, but you will be redirected to
an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
-Step 12: Service Account Example
+Step 11: Service Account Example
================================
An example for retrieve service account dedicated to the Client Application itself (not to any user).
@@ -240,7 +219,7 @@ An example for retrieve service account dedicated to the Client Application itse
Client authentication is done with OAuth2 Client Credentials Grant in out-of-bound request (Not Keycloak login screen displayed) .
-Step 13: Offline Access Example
+Step 12: Offline Access Example
===============================
An example for retrieve offline token, which is then saved to the database and can be used by application anytime later. Offline token
is valid even if user is already logged out from SSO. Server restart also won't invalidate offline token. Offline token can be revoked by the user in
diff --git a/integration/client-cli/client-registration-cli/pom.xml b/integration/client-cli/client-registration-cli/pom.xml
index e9ca274..c194dde 100755
--- a/integration/client-cli/client-registration-cli/pom.xml
+++ b/integration/client-cli/client-registration-cli/pom.xml
@@ -52,13 +52,6 @@
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-deploy-plugin</artifactId>
- <configuration>
- <skip>true</skip>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<!--version>2.4.3</version-->
<executions>
misc/CrossDataCenter.md 4(+3 -1)
diff --git a/misc/CrossDataCenter.md b/misc/CrossDataCenter.md
index 4146eaa..3313f3f 100644
--- a/misc/CrossDataCenter.md
+++ b/misc/CrossDataCenter.md
@@ -3,6 +3,8 @@ Test Cross-Data-Center scenario (test with external JDG server)
These are temporary notes. This docs should be removed once we have cross-DC support finished and properly documented.
+Note that these steps are already automated, see Cross-DC tests section in [HOW-TO-RUN.md](../testsuite/integration-arquillian/HOW-TO-RUN.md) document.
+
What is working right now is:
- Propagating of invalidation messages for "realms" and "users" caches
- All the other things provided by ClusterProvider, which is:
@@ -18,7 +20,7 @@ Basic setup
This is setup with 2 keycloak nodes, which are NOT in cluster. They just share the same database and they will be configured with "work" infinispan cache with remoteStore, which will point
to external JDG server.
-
+
JDG Server setup
----------------
- Download JDG 7.0 server and unzip to some folder
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
index 17ae121..76b0779 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
@@ -154,7 +154,8 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
if (clustered) {
String nodeName = config.get("nodeName", System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME));
- configureTransport(gcb, nodeName);
+ String jgroupsUdpMcastAddr = config.get("jgroupsUdpMcastAddr", System.getProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR));
+ configureTransport(gcb, nodeName, jgroupsUdpMcastAddr);
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
@@ -317,24 +318,40 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
return cb.build();
}
- protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName) {
+ private static final Object CHANNEL_INIT_SYNCHRONIZER = new Object();
+
+ protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName, String jgroupsUdpMcastAddr) {
if (nodeName == null) {
gcb.transport().defaultTransport();
} else {
FileLookup fileLookup = FileLookupFactory.newInstance();
- try {
- // Compatibility with Wildfly
- JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
- channel.setName(nodeName);
- JGroupsTransport transport = new JGroupsTransport(channel);
-
- gcb.transport().nodeName(nodeName);
- gcb.transport().transport(transport);
-
- logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
- } catch (Exception e) {
- throw new RuntimeException(e);
+ synchronized (CHANNEL_INIT_SYNCHRONIZER) {
+ String originalMcastAddr = System.getProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
+ if (jgroupsUdpMcastAddr == null) {
+ System.getProperties().remove(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
+ } else {
+ System.setProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR, jgroupsUdpMcastAddr);
+ }
+ try {
+ // Compatibility with Wildfly
+ JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
+ channel.setName(nodeName);
+ JGroupsTransport transport = new JGroupsTransport(channel);
+
+ gcb.transport().nodeName(nodeName);
+ gcb.transport().transport(transport);
+
+ logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (originalMcastAddr == null) {
+ System.getProperties().remove(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
+ } else {
+ System.setProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR, originalMcastAddr);
+ }
+ }
}
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
index 8618a69..7fd2652 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
@@ -53,6 +53,7 @@ public interface InfinispanConnectionProvider extends Provider {
// System property used on Wildfly to identify distributedCache address and sticky session route
String JBOSS_NODE_NAME = "jboss.node.name";
+ String JGROUPS_UDP_MCAST_ADDR = "jgroups.udp.mcast_addr";
<K, V> Cache<K, V> getCache(String name);
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 759c284..4d46f81 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
@@ -30,13 +30,17 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati
private String id;
private String name;
private Set<String> resources;
+ private Set<String> resourceTypes;
+ private Set<String> scopes;
private String serverId;
- public static PolicyRemovedEvent create(String id, String name, Set<String> resources, String serverId) {
+ public static PolicyRemovedEvent create(String id, String name, Set<String> resources, Set<String> resourceTypes, Set<String> scopes, String serverId) {
PolicyRemovedEvent event = new PolicyRemovedEvent();
event.id = id;
event.name = name;
event.resources = resources;
+ event.resourceTypes = resourceTypes;
+ event.scopes = scopes;
event.serverId = serverId;
return event;
}
@@ -53,6 +57,6 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
- cache.policyRemoval(id, name, resources, serverId, invalidations);
+ cache.policyRemoval(id, name, resources, resourceTypes, scopes, 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 b576bda..d613c57 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
@@ -30,13 +30,17 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati
private String id;
private String name;
private static Set<String> resources;
+ private Set<String> resourceTypes;
+ private Set<String> scopes;
private String serverId;
- public static PolicyUpdatedEvent create(String id, String name, Set<String> resources, String serverId) {
+ public static PolicyUpdatedEvent create(String id, String name, Set<String> resources, Set<String> resourceTypes, Set<String> scopes, String serverId) {
PolicyUpdatedEvent event = new PolicyUpdatedEvent();
event.id = id;
event.name = name;
event.resources = resources;
+ event.resourceTypes = resourceTypes;
+ event.scopes = scopes;
event.serverId = serverId;
return event;
}
@@ -53,6 +57,6 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
- cache.policyUpdated(id, name, resources, serverId, invalidations);
+ cache.policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java
index c5dc7fc..c74e4fe 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java
@@ -17,21 +17,19 @@
package org.keycloak.models.cache.infinispan.authorization;
+import static org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS;
+
import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.keycloak.Config;
-import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.CacheRealmProviderFactory;
import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
import org.keycloak.models.cache.authorization.CachedStoreProviderFactory;
-import org.keycloak.models.cache.infinispan.RealmCacheManager;
-import org.keycloak.models.cache.infinispan.RealmCacheSession;
+import org.keycloak.models.cache.infinispan.ClearCacheEvent;
import org.keycloak.models.cache.infinispan.entities.Revisioned;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
@@ -59,21 +57,17 @@ public class InfinispanCacheStoreFactoryProviderFactory implements CachedStorePr
Cache<String, Revisioned> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME);
Cache<String, Long> revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME);
storeCache = new StoreFactoryCacheManager(cache, revisions);
-
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
- cluster.registerListener(ClusterProvider.ALL, (ClusterEvent event) -> {
+ cluster.registerListener(ClusterProvider.ALL, (ClusterEvent event) -> {
if (event instanceof InvalidationEvent) {
InvalidationEvent invalidationEvent = (InvalidationEvent) event;
storeCache.invalidationEventReceived(invalidationEvent);
}
});
- cluster.registerListener(AUTHORIZATION_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
-
- storeCache.clear();
-
- });
+ cluster.registerListener(AUTHORIZATION_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> storeCache.clear());
+ cluster.registerListener(REALM_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> storeCache.clear());
log.debug("Registered cluster listeners");
}
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 970d1b9..7660c96 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
@@ -47,7 +47,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override
public Policy getDelegateForUpdate() {
if (updated == null) {
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getResourceServerId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), cached.getResourceServerId());
updated = cacheSession.getPolicyStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
if (updated == null) throw new IllegalStateException("Not found in database");
}
@@ -96,7 +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());
+ cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getScopesIds(), cached.getResourceServerId());
updated.setName(name);
}
@@ -235,7 +235,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
getDelegateForUpdate();
HashSet<String> resources = new HashSet<>();
resources.add(resource.getId());
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(), cached.getResourceServerId());
updated.addResource(resource);
}
@@ -245,7 +245,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
getDelegateForUpdate();
HashSet<String> resources = new HashSet<>();
resources.add(resource.getId());
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(), cached.getResourceServerId());
updated.removeResource(resource);
}
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 7943589..63eb8a7 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
@@ -102,7 +102,7 @@ public class StoreFactoryCacheManager extends CacheManager {
addInvalidations(InResourcePredicate.create().resource(id), invalidations);
}
- public void policyUpdated(String id, String name, Set<String> resources, String serverId, Set<String> invalidations) {
+ public void policyUpdated(String id, String name, Set<String> resources, Set<String> resourceTypes, Set<String> scopes, String serverId, Set<String> invalidations) {
invalidations.add(id);
invalidations.add(StoreFactoryCacheSession.getPolicyByNameCacheKey(name, serverId));
@@ -111,10 +111,22 @@ public class StoreFactoryCacheManager extends CacheManager {
invalidations.add(StoreFactoryCacheSession.getPolicyByResource(resource, serverId));
}
}
+
+ if (resourceTypes != null) {
+ for (String type : resourceTypes) {
+ invalidations.add(StoreFactoryCacheSession.getPolicyByResourceType(type, serverId));
+ }
+ }
+
+ if (scopes != null) {
+ for (String scope : scopes) {
+ invalidations.add(StoreFactoryCacheSession.getPolicyByScope(scope, serverId));
+ }
+ }
}
- public void policyRemoval(String id, String name, Set<String> resources, String serverId, Set<String> invalidations) {
- policyUpdated(id, name, resources, serverId, invalidations);
+ public void policyRemoval(String id, String name, Set<String> resources, Set<String> resourceTypes, Set<String> scopes, String serverId, Set<String> invalidations) {
+ policyUpdated(id, name, resources, resourceTypes, scopes, 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 3e4c205..10be78d 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
@@ -23,6 +23,7 @@ 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.BiFunction;
import java.util.function.Supplier;
@@ -252,12 +253,30 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId));
}
- public void registerPolicyInvalidation(String id, String name, Set<String> resources, String serverId) {
- cache.policyUpdated(id, name, resources, serverId, invalidations);
+ public void registerPolicyInvalidation(String id, String name, Set<String> resources, Set<String> scopes, String serverId) {
+ Set<String> resourceTypes = getResourceTypes(resources, serverId);
+ cache.policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
PolicyAdapter adapter = managedPolicies.get(id);
if (adapter != null) adapter.invalidateFlag();
- invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, serverId));
+ invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, resourceTypes, scopes, serverId));
+ }
+
+ private Set<String> getResourceTypes(Set<String> resources, String serverId) {
+ if (resources == null) {
+ return Collections.emptySet();
+ }
+
+ return resources.stream().map(resourceId -> {
+ Resource resource = getResourceStore().findById(resourceId, serverId);
+ String type = resource.getType();
+
+ if (type != null) {
+ return type;
+ }
+
+ return null;
+ }).filter(Objects::nonNull).collect(Collectors.toSet());
}
public ResourceServerStore getResourceServerStoreDelegate() {
@@ -626,7 +645,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
@Override
public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
Policy policy = getPolicyStoreDelegate().create(representation, resourceServer);
- registerPolicyInvalidation(policy.getId(), policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), resourceServer.getId());
+ registerPolicyInvalidation(policy.getId(), representation.getName(), representation.getResources(), representation.getScopes(), resourceServer.getId());
return policy;
}
@@ -637,8 +656,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
if (policy == null) return;
cache.invalidateObject(id);
- 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);
+ Set<String> resources = policy.getResources().stream().map(resource -> resource.getId()).collect(Collectors.toSet());
+ ResourceServer resourceServer = policy.getResourceServer();
+ Set<String> resourceTypes = getResourceTypes(resources, resourceServer.getId());
+ Set<String> scopes = policy.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet());
+ invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), resources, resourceTypes, scopes, resourceServer.getId()));
+ cache.policyRemoval(id, policy.getName(), resources, resourceTypes, scopes, resourceServer.getId(), invalidations);
getPolicyStoreDelegate().delete(id);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
index c9832ff..4480f7a 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
@@ -220,7 +220,7 @@ public abstract class CacheManager {
addInvalidationsFromEvent(event, invalidations);
- getLogger().debugf("Invalidating %d cache items after received event %s", invalidations.size(), event);
+ getLogger().debugf("[%s] Invalidating %d cache items after received event %s", cache.getCacheManager().getAddress(), invalidations.size(), event);
for (String invalidation : invalidations) {
invalidateObject(invalidation);
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
index 52c1309..5fe725f 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
@@ -164,9 +164,8 @@ public class RealmCacheSession implements CacheRealmProvider {
@Override
public void clear() {
- cache.clear();
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
- cluster.notify(InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), true);
+ cluster.notify(InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), false);
}
@Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java
index 173c434..24742e5 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java
@@ -81,6 +81,11 @@ public class ActionTokenReducedKey implements Serializable {
&& Objects.equals(this.actionVerificationNonce, other.getActionVerificationNonce());
}
+ @Override
+ public String toString() {
+ return "userId=" + userId + ", actionId=" + actionId + ", actionVerificationNonce=" + actionVerificationNonce;
+ }
+
public static class ExternalizerImpl implements Externalizer<ActionTokenReducedKey> {
@Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
index 7c0f663..7e3c76e 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
@@ -53,7 +53,7 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
public void writeObject(ObjectOutput output, ActionTokenValueEntity t) throws IOException {
output.writeByte(VERSION_1);
- output.writeBoolean(! t.notes.isEmpty());
+ output.writeBoolean(t.notes.isEmpty());
if (! t.notes.isEmpty()) {
output.writeObject(t.notes);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
index 127879a..f02fb5a 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
@@ -19,8 +19,8 @@ package org.keycloak.models.sessions.infinispan;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.models.*;
-import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
-import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
+import org.keycloak.models.cache.infinispan.events.AddInvalidatedActionTokenEvent;
+import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.*;
@@ -58,7 +58,12 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
- this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue), false);
+ AddInvalidatedActionTokenEvent event = new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue);
+ this.tx.notify(cluster, generateActionTokenEventId(), event, false);
+ }
+
+ private static String generateActionTokenEventId() {
+ return InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS + "/" + UUID.randomUUID();
}
@Override
@@ -93,6 +98,6 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
}
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
- this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new RemoveActionTokensSpecificEvent(userId, actionId), false);
+ this.tx.notify(cluster, generateActionTokenEventId(), new RemoveActionTokensSpecificEvent(userId, actionId), false);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java
index a8c5e38..f67f28f 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java
@@ -23,14 +23,16 @@ import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.*;
-import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
-import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
+import org.keycloak.models.cache.infinispan.events.AddInvalidatedActionTokenEvent;
+import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.infinispan.Cache;
import org.infinispan.context.Flag;
+import org.infinispan.remoting.transport.Address;
+import org.jboss.logging.Logger;
/**
*
@@ -38,6 +40,10 @@ import org.infinispan.context.Flag;
*/
public class InfinispanActionTokenStoreProviderFactory implements ActionTokenStoreProviderFactory {
+ private static final Logger LOG = Logger.getLogger(InfinispanActionTokenStoreProviderFactory.class);
+
+ private volatile Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache;
+
public static final String ACTION_TOKEN_EVENTS = "ACTION_TOKEN_EVENTS";
/**
@@ -49,43 +55,65 @@ public class InfinispanActionTokenStoreProviderFactory implements ActionTokenSto
@Override
public ActionTokenStoreProvider create(KeycloakSession session) {
+ return new InfinispanActionTokenStoreProvider(session, this.actionTokenCache);
+ }
+
+ @Override
+ public void init(Scope config) {
+ this.config = config;
+ }
+
+ private static Cache<ActionTokenReducedKey, ActionTokenValueEntity> initActionTokenCache(KeycloakSession session) {
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
- Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
+ Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
+ final Address cacheAddress = cache.getCacheManager().getAddress();
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
- cluster.registerListener(ACTION_TOKEN_EVENTS, event -> {
+ cluster.registerListener(ClusterProvider.ALL, event -> {
if (event instanceof RemoveActionTokensSpecificEvent) {
RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
- actionTokenCache
+ LOG.debugf("[%s] Removing token invalidation for user+action: userId=%s, actionId=%s", cacheAddress, e.getUserId(), e.getActionId());
+
+ cache
.getAdvancedCache()
.withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)
.keySet()
.stream()
.filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
- .forEach(actionTokenCache::remove);
+ .forEach(cache::remove);
} else if (event instanceof AddInvalidatedActionTokenEvent) {
AddInvalidatedActionTokenEvent e = (AddInvalidatedActionTokenEvent) event;
+ LOG.debugf("[%s] Invalidating token %s", cacheAddress, e.getKey());
if (e.getExpirationInSecs() == DEFAULT_CACHE_EXPIRATION) {
- actionTokenCache.put(e.getKey(), e.getTokenValue());
+ cache.put(e.getKey(), e.getTokenValue());
} else {
- actionTokenCache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
+ cache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
}
}
});
- return new InfinispanActionTokenStoreProvider(session, actionTokenCache);
- }
+ LOG.debugf("[%s] Registered cluster listeners", cacheAddress);
- @Override
- public void init(Scope config) {
- this.config = config;
+ return cache;
}
@Override
public void postInit(KeycloakSessionFactory factory) {
+ Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = this.actionTokenCache;
+
+ // It is necessary to put the cache initialization here, otherwise the cache would be initialized lazily, that
+ // means also listeners will start only after first cache initialization - that would be too late
+ if (cache == null) {
+ synchronized (this) {
+ cache = this.actionTokenCache;
+ if (cache == null) {
+ this.actionTokenCache = initActionTokenCache(factory.create());
+ }
+ }
+ }
}
@Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
index 83e970d..a9589cc 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
@@ -92,7 +92,7 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.registerListener(AUTHENTICATION_SESSION_EVENTS, this::updateAuthNotes);
- log.debug("Registered cluster listeners");
+ log.debugf("[%s] Registered cluster listeners", authSessionsCache.getCacheManager().getAddress());
}
}
}
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 718d19a..98f3152 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
@@ -355,6 +355,24 @@ public class JpaRealmProvider implements RealmProvider {
return false;
}
+ GroupModel.GroupRemovedEvent event = new GroupModel.GroupRemovedEvent() {
+ @Override
+ public RealmModel getRealm() {
+ return realm;
+ }
+
+ @Override
+ public GroupModel getGroup() {
+ return group;
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ };
+ session.getKeycloakSessionFactory().publish(event);
+
session.users().preRemove(realm, group);
realm.removeDefaultGroup(group);
diff --git a/server-spi/src/main/java/org/keycloak/models/GroupModel.java b/server-spi/src/main/java/org/keycloak/models/GroupModel.java
index 5a0e006..aac0ddb 100755
--- a/server-spi/src/main/java/org/keycloak/models/GroupModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/GroupModel.java
@@ -17,6 +17,8 @@
package org.keycloak.models;
+import org.keycloak.provider.ProviderEvent;
+
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -26,6 +28,11 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public interface GroupModel extends RoleMapperModel {
+ interface GroupRemovedEvent extends ProviderEvent {
+ RealmModel getRealm();
+ GroupModel getGroup();
+ KeycloakSession getKeycloakSession();
+ }
String getId();
String getName();
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 f3367af..10d59d9 100755
--- a/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
+++ b/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
@@ -22,6 +22,7 @@ import org.keycloak.policy.PasswordPolicyProvider;
import java.io.Serializable;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@@ -32,62 +33,33 @@ public class PasswordPolicy implements Serializable {
public static final String HASH_ALGORITHM_ID = "hashAlgorithm";
- public static final String HASH_ALGORITHM_DEFAULT = "pbkdf2";
+ public static final String HASH_ALGORITHM_DEFAULT = "pbkdf2-sha256";
public static final String HASH_ITERATIONS_ID = "hashIterations";
- public static final int HASH_ITERATIONS_DEFAULT = 20000;
+ public static final int HASH_ITERATIONS_DEFAULT = 27500;
public static final String PASSWORD_HISTORY_ID = "passwordHistory";
public static final String FORCE_EXPIRED_ID = "forceExpiredPasswordChange";
- private String policyString;
private Map<String, Object> policyConfig;
+ private Builder builder;
public static PasswordPolicy empty() {
return new PasswordPolicy(null, new HashMap<>());
}
- public static PasswordPolicy parse(KeycloakSession session, String policyString) {
- Map<String, Object> policyConfig = new HashMap<>();
-
- if (policyString != null && !policyString.trim().isEmpty()) {
- for (String policy : policyString.split(" and ")) {
- policy = policy.trim();
-
- String key;
- String config = null;
-
- int i = policy.indexOf('(');
- if (i == -1) {
- key = policy.trim();
- } else {
- key = policy.substring(0, i).trim();
- config = policy.substring(i + 1, policy.length() - 1);
- }
-
- PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, key);
- if (provider == null) {
- throw new PasswordPolicyConfigException("Password policy not found");
- }
-
- Object o;
- try {
- o = provider.parseConfig(config);
- } catch (PasswordPolicyConfigException e) {
- throw new ModelException("Invalid config for " + key + ": " + e.getMessage());
- }
-
- policyConfig.put(key, o);
- }
- }
+ public static Builder build() {
+ return new Builder();
+ }
- return new PasswordPolicy(policyString, policyConfig);
+ public static PasswordPolicy parse(KeycloakSession session, String policyString) {
+ return new Builder(policyString).build(session);
}
- private PasswordPolicy(String policyString, Map<String, Object> policyConfig) {
- this.policyString = policyString;
+ private PasswordPolicy(Builder builder, Map<String, Object> policyConfig) {
+ this.builder = builder;
this.policyConfig = policyConfig;
}
@@ -111,7 +83,7 @@ public class PasswordPolicy implements Serializable {
if (policyConfig.containsKey(HASH_ITERATIONS_ID)) {
return getPolicyConfig(HASH_ITERATIONS_ID);
} else {
- return HASH_ITERATIONS_DEFAULT;
+ return -1;
}
}
@@ -133,7 +105,117 @@ public class PasswordPolicy implements Serializable {
@Override
public String toString() {
- return policyString;
+ return builder.asString();
+ }
+
+ public Builder toBuilder() {
+ return builder.clone();
+ }
+
+ public static class Builder {
+
+ private LinkedHashMap<String, String> map;
+
+ private Builder() {
+ this.map = new LinkedHashMap<>();
+ }
+
+ private Builder(LinkedHashMap<String, String> map) {
+ this.map = map;
+ }
+
+ private Builder(String policyString) {
+ map = new LinkedHashMap<>();
+
+ if (policyString != null && !policyString.trim().isEmpty()) {
+ for (String policy : policyString.split(" and ")) {
+ policy = policy.trim();
+
+ String key;
+ String config = null;
+
+ int i = policy.indexOf('(');
+ if (i == -1) {
+ key = policy.trim();
+ } else {
+ key = policy.substring(0, i).trim();
+ config = policy.substring(i + 1, policy.length() - 1);
+ }
+
+ map.put(key, config);
+ }
+ }
+ }
+
+ public boolean contains(String key) {
+ return map.containsKey(key);
+ }
+
+ public String get(String key) {
+ return map.get(key);
+ }
+
+ public Builder put(String key, String value) {
+ map.put(key, value);
+ return this;
+ }
+
+ public Builder remove(String key) {
+ map.remove(key);
+ return this;
+ }
+
+ public PasswordPolicy build(KeycloakSession session) {
+ Map<String, Object> config = new HashMap<>();
+ for (Map.Entry<String, String> e : map.entrySet()) {
+
+ PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, e.getKey());
+ if (provider == null) {
+ throw new PasswordPolicyConfigException("Password policy not found");
+ }
+
+ Object o;
+ try {
+ o = provider.parseConfig(e.getValue());
+ } catch (PasswordPolicyConfigException ex) {
+ throw new ModelException("Invalid config for " + e.getKey() + ": " + ex.getMessage());
+ }
+
+ config.put(e.getKey(), o);
+ }
+ return new PasswordPolicy(this, config);
+ }
+
+ public String asString() {
+ if (map.isEmpty()) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (Map.Entry<String, String> e : map.entrySet()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(" and ");
+ }
+
+ sb.append(e.getKey());
+
+ String c = e.getValue();
+ if (c != null && !c.trim().isEmpty()) {
+ sb.append("(");
+ sb.append(c);
+ sb.append(")");
+ }
+ }
+ return sb.toString();
+ }
+
+ public Builder clone() {
+ return new Builder((LinkedHashMap<String, String>) map.clone());
+ }
+
}
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
index 8a55ff1..644b90a 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
@@ -21,14 +21,17 @@ package org.keycloak.authorization;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.evaluator.Evaluators;
import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
-import org.keycloak.authorization.store.AuthorizationStoreFactory;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceServerStore;
import org.keycloak.authorization.store.ResourceStore;
@@ -37,7 +40,6 @@ import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
-import org.keycloak.models.cache.authorization.CachedStoreProviderFactory;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.provider.Provider;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
@@ -143,6 +145,61 @@ public final class AuthorizationProvider implements Provider {
return new PolicyStore() {
@Override
public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
+ Set<String> resources = representation.getResources();
+
+ if (resources != null) {
+ representation.setResources(resources.stream().map(id -> {
+ Resource resource = getResourceStore().findById(id, resourceServer.getId());
+
+ if (resource == null) {
+ resource = getResourceStore().findByName(id, resourceServer.getId());
+ }
+
+ if (resource == null) {
+ throw new RuntimeException("Resource [" + id + "] does not exist");
+ }
+
+ return resource.getId();
+ }).collect(Collectors.toSet()));
+ }
+
+ Set<String> scopes = representation.getScopes();
+
+ if (scopes != null) {
+ representation.setScopes(scopes.stream().map(id -> {
+ Scope scope = getScopeStore().findById(id, resourceServer.getId());
+
+ if (scope == null) {
+ scope = getScopeStore().findByName(id, resourceServer.getId());
+ }
+
+ if (scope == null) {
+ throw new RuntimeException("Scope [" + id + "] does not exist");
+ }
+
+ return scope.getId();
+ }).collect(Collectors.toSet()));
+ }
+
+
+ Set<String> policies = representation.getPolicies();
+
+ if (policies != null) {
+ representation.setPolicies(policies.stream().map(id -> {
+ Policy policy = getPolicyStore().findById(id, resourceServer.getId());
+
+ if (policy == null) {
+ policy = getPolicyStore().findByName(id, resourceServer.getId());
+ }
+
+ if (policy == null) {
+ throw new RuntimeException("Policy [" + id + "] does not exist");
+ }
+
+ return policy.getId();
+ }).collect(Collectors.toSet()));
+ }
+
return RepresentationToModel.toModel(representation, AuthorizationProvider.this, policyStore.create(representation, resourceServer));
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
index 00e1191..2eda4ac 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
@@ -42,6 +42,6 @@ public final class Evaluators {
}
public PermissionEvaluator schedule(List<ResourcePermission> permissions, EvaluationContext evaluationContext) {
- return new DefaultPermissionEvaluator(new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, this.policyEvaluator));
+ return new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, this.policyEvaluator);
}
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
index dfda6a7..f2da3a5 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
@@ -17,12 +17,16 @@
*/
package org.keycloak.authorization.permission.evaluator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
import org.keycloak.authorization.Decision;
import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.evaluation.PolicyEvaluator;
-
-import java.util.Iterator;
+import org.keycloak.authorization.policy.evaluation.Result;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -50,4 +54,23 @@ class IterablePermissionEvaluator implements PermissionEvaluator {
decision.onError(cause);
}
}
+
+ @Override
+ public List<Result> evaluate() {
+ AtomicReference<List<Result>> result = new AtomicReference<>();
+
+ evaluate(new DecisionResultCollector() {
+ @Override
+ public void onError(Throwable cause) {
+ throw new RuntimeException("Failed to evaluate permissions", cause);
+ }
+
+ @Override
+ protected void onComplete(List<Result> results) {
+ result.set(results);
+ }
+ });
+
+ return result.get();
+ }
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
index c129caf..587856f 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
@@ -17,7 +17,10 @@
*/
package org.keycloak.authorization.permission.evaluator;
+import java.util.List;
+
import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.policy.evaluation.Result;
/**
* An {@link PermissionEvaluator} represents a source of {@link org.keycloak.authorization.permission.ResourcePermission}, responsible for emitting these permissions
@@ -28,4 +31,5 @@ import org.keycloak.authorization.Decision;
public interface PermissionEvaluator {
void evaluate(Decision decision);
+ List<Result> evaluate();
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
index 02312ba..2ffc049 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
@@ -19,6 +19,7 @@
package org.keycloak.authorization.policy.evaluation;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -33,7 +34,7 @@ import org.keycloak.representations.idm.authorization.DecisionStrategy;
*/
public abstract class DecisionResultCollector implements Decision<DefaultEvaluation> {
- private Map<ResourcePermission, Result> results = new HashMap();
+ private Map<ResourcePermission, Result> results = new LinkedHashMap<>();
@Override
public void onDecision(DefaultEvaluation evaluation) {
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
index af95b00..a2a8689 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java
@@ -60,7 +60,11 @@ public interface PolicyProviderFactory<R extends AbstractPolicyRepresentation> e
}
+ default void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
+ representation.setConfig(policy.getConfig());
+ }
+
default PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) {
return null;
}
-}
+}
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java
index 6c170e1..6a6c1ff 100644
--- a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java
@@ -37,12 +37,14 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProvider {
private final String providerId;
private final String pbkdf2Algorithm;
+ private int defaultIterations;
public static final int DERIVED_KEY_SIZE = 512;
- public Pbkdf2PasswordHashProvider(String providerId, String pbkdf2Algorithm) {
+ public Pbkdf2PasswordHashProvider(String providerId, String pbkdf2Algorithm, int defaultIterations) {
this.providerId = providerId;
this.pbkdf2Algorithm = pbkdf2Algorithm;
+ this.defaultIterations = defaultIterations;
}
@Override
@@ -52,6 +54,10 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProvider {
@Override
public void encode(String rawPassword, int iterations, CredentialModel credential) {
+ if (iterations == -1) {
+ iterations = defaultIterations;
+ }
+
byte[] salt = getSalt();
String encodedPassword = encode(rawPassword, iterations, salt);
diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java
index ecd917d..44e0e12 100644
--- a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java
@@ -30,9 +30,11 @@ public class Pbkdf2PasswordHashProviderFactory implements PasswordHashProviderFa
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
+ public static final int DEFAULT_ITERATIONS = 20000;
+
@Override
public PasswordHashProvider create(KeycloakSession session) {
- return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM);
+ return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM, 20000);
}
@Override
diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java
index c6453d1..040879f 100644
--- a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java
@@ -15,9 +15,11 @@ public class Pbkdf2Sha256PasswordHashProviderFactory implements PasswordHashProv
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";
+ public static final int DEFAULT_ITERATIONS = 27500;
+
@Override
public PasswordHashProvider create(KeycloakSession session) {
- return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM);
+ return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM, DEFAULT_ITERATIONS);
}
@Override
diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java
index 5f838a1..2e0700d 100644
--- a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java
@@ -15,9 +15,11 @@ public class Pbkdf2Sha512PasswordHashProviderFactory implements PasswordHashProv
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA512";
+ public static final int DEFAULT_ITERATIONS = 30000;
+
@Override
public PasswordHashProvider create(KeycloakSession session) {
- return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM);
+ return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM, DEFAULT_ITERATIONS);
}
@Override
diff --git a/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java b/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
index cba5ed6..128b344 100644
--- a/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
+++ b/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
@@ -156,5 +156,25 @@ public enum ResourceType {
/**
*
*/
- , COMPONENT;
+ , COMPONENT
+
+ /**
+ *
+ */
+ , AUTHORIZATION_RESOURCE_SERVER
+
+ /**
+ *
+ */
+ , AUTHORIZATION_RESOURCE
+
+ /**
+ *
+ */
+ , AUTHORIZATION_SCOPE
+
+ /**
+ *
+ */
+ , AUTHORIZATION_POLICY;
}
diff --git a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java
index 0cb3eb6..17cd0ac 100644
--- a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java
+++ b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo3_2_0.java
@@ -23,17 +23,27 @@ import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class MigrateTo3_2_0 implements Migration {
- public static final ModelVersion VERSION = new ModelVersion("3.1.0");
+
+ public static final ModelVersion VERSION = new ModelVersion("3.2.0");
@Override
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
+ PasswordPolicy.Builder builder = realm.getPasswordPolicy().toBuilder();
+ if (!builder.contains(PasswordPolicy.HASH_ALGORITHM_ID) && "20000".equals(builder.get(PasswordPolicy.HASH_ITERATIONS_ID))) {
+ realm.setPasswordPolicy(builder.remove(PasswordPolicy.HASH_ITERATIONS_ID).build(session));
+ }
+
ClientModel realmAccess = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID);
if (realmAccess != null) {
addRoles(realmAccess);
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 5ae5904..6a7aeaa 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
@@ -771,11 +771,7 @@ public class ModelToRepresentation {
return rep;
}
- public static ScopeRepresentation toRepresentation(Scope model, AuthorizationProvider authorizationProvider) {
- return toRepresentation(model, authorizationProvider, true);
- }
-
- public static ScopeRepresentation toRepresentation(Scope model, AuthorizationProvider authorizationProvider, boolean deep) {
+ public static ScopeRepresentation toRepresentation(Scope model) {
ScopeRepresentation scope = new ScopeRepresentation();
scope.setId(model.getId());
@@ -798,6 +794,10 @@ public class ModelToRepresentation {
}
public static <R extends AbstractPolicyRepresentation> R toRepresentation(Policy policy, Class<R> representationType, AuthorizationProvider authorization) {
+ return toRepresentation(policy, representationType, authorization, false);
+ }
+
+ public static <R extends AbstractPolicyRepresentation> R toRepresentation(Policy policy, Class<R> representationType, AuthorizationProvider authorization, boolean export) {
R representation;
try {
@@ -816,7 +816,11 @@ public class ModelToRepresentation {
representation.setLogic(policy.getLogic());
if (representation instanceof PolicyRepresentation) {
- PolicyRepresentation.class.cast(representation).setConfig(policy.getConfig());
+ if (providerFactory != null && export) {
+ providerFactory.onExport(policy, PolicyRepresentation.class.cast(representation), authorization);
+ } else {
+ PolicyRepresentation.class.cast(representation).setConfig(policy.getConfig());
+ }
} else {
representation = (R) providerFactory.toRepresentation(policy, representation);
}
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 e102d3d..4a4b4fb 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
@@ -2171,13 +2171,13 @@ public class RepresentationToModel {
private static void updateResources(Set<String> resourceIds, Policy policy, StoreFactory storeFactory) {
if (resourceIds != null) {
if (resourceIds.isEmpty()) {
- for (Scope scope : new HashSet<Scope>(policy.getScopes())) {
- policy.removeScope(scope);
+ for (Resource resource : new HashSet<>(policy.getResources())) {
+ policy.removeResource(resource);
}
}
for (String resourceId : resourceIds) {
boolean hasResource = false;
- for (Resource resourceModel : new HashSet<Resource>(policy.getResources())) {
+ for (Resource resourceModel : new HashSet<>(policy.getResources())) {
if (resourceModel.getId().equals(resourceId) || resourceModel.getName().equals(resourceId)) {
hasResource = true;
}
@@ -2196,7 +2196,7 @@ public class RepresentationToModel {
}
}
- for (Resource resourceModel : new HashSet<Resource>(policy.getResources())) {
+ for (Resource resourceModel : new HashSet<>(policy.getResources())) {
boolean hasResource = false;
for (String resourceId : resourceIds) {
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
index 695ab28..be4dd45 100644
--- a/server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
@@ -60,7 +60,7 @@ public class HashIterationsPasswordPolicyProviderFactory implements PasswordPoli
@Override
public Object parseConfig(String value) {
- return value != null ? Integer.parseInt(value) : PasswordPolicy.HASH_ITERATIONS_DEFAULT;
+ return parseInteger(value, -1);
}
@Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
index af63974..c3d1de4 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
@@ -47,7 +47,7 @@ import java.util.Map;
* <li>{@code realm} the {@link RealmModel}</li>
* <li>{@code user} the current {@link UserModel}</li>
* <li>{@code session} the active {@link KeycloakSession}</li>
- * <li>{@code clientSession} the current {@link org.keycloak.sessions.AuthenticationSessionModel}</li>
+ * <li>{@code authenticationSession} the current {@link org.keycloak.sessions.AuthenticationSessionModel}</li>
* <li>{@code httpRequest} the current {@link org.jboss.resteasy.spi.HttpRequest}</li>
* <li>{@code LOG} a {@link org.jboss.logging.Logger} scoped to {@link ScriptBasedAuthenticator}/li>
* </ol>
@@ -160,7 +160,7 @@ public class ScriptBasedAuthenticator implements Authenticator {
bindings.put("user", context.getUser());
bindings.put("session", context.getSession());
bindings.put("httpRequest", context.getHttpRequest());
- bindings.put("clientSession", context.getAuthenticationSession());
+ bindings.put("authenticationSession", context.getAuthenticationSession());
bindings.put("LOG", LOGGER);
});
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticatorFactory.java
index 4a1003c..f5fabe2 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticatorFactory.java
@@ -146,7 +146,7 @@ public class ScriptBasedAuthenticatorFactory implements AuthenticatorFactory, En
}
script.setDefaultValue(scriptTemplate);
script.setHelpText("The script used to authenticate. Scripts must at least define a function with the name 'authenticate(context)' that accepts a context (AuthenticationFlowContext) parameter.\n" +
- "This authenticator exposes the following additional variables: 'script', 'realm', 'user', 'session', 'httpRequest', 'LOG'");
+ "This authenticator exposes the following additional variables: 'script', 'realm', 'user', 'session', 'authenticationSession', 'httpRequest', 'LOG'");
return asList(name, description, script);
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
index d062856..535634b 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
@@ -24,6 +24,7 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
import javax.ws.rs.Path;
@@ -37,27 +38,29 @@ public class AuthorizationService {
private final KeycloakSession session;
private final ResourceServer resourceServer;
private final AuthorizationProvider authorization;
+ private final AdminEventBuilder adminEvent;
- public AuthorizationService(KeycloakSession session, ClientModel client, AdminPermissionEvaluator auth) {
+ public AuthorizationService(KeycloakSession session, ClientModel client, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.session = session;
this.client = client;
this.authorization = session.getProvider(AuthorizationProvider.class);
+ this.adminEvent = adminEvent;
this.resourceServer = this.authorization.getStoreFactory().getResourceServerStore().findByClient(this.client.getId());
this.auth = auth;
}
@Path("/resource-server")
public ResourceServerService resourceServer() {
- ResourceServerService resource = new ResourceServerService(this.authorization, this.resourceServer, this.client, this.auth);
+ ResourceServerService resource = new ResourceServerService(this.authorization, this.resourceServer, this.client, this.auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(resource);
return resource;
}
- public void enable() {
+ public void enable(boolean newClient) {
if (!isEnabled()) {
- resourceServer().create();
+ resourceServer().create(newClient);
}
}
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 fbcffd4..6973b4d 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
@@ -23,24 +23,25 @@ import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class PermissionService extends PolicyService {
- public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) {
- super(resourceServer, authorization, auth);
+ public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+ super(resourceServer, authorization, auth, adminEvent);
}
@Override
protected PolicyResourceService doCreatePolicyResource(Policy policy) {
- return new PolicyTypeResourceService(policy, resourceServer, authorization, auth);
+ return new PolicyTypeResourceService(policy, resourceServer, authorization, auth, adminEvent);
}
@Override
protected PolicyTypeService doCreatePolicyTypeResource(String type) {
- return new PolicyTypeService(type, resourceServer, authorization, auth) {
+ return new PolicyTypeService(type, resourceServer, authorization, auth, adminEvent) {
@Override
protected List<Object> doSearch(Integer firstResult, Integer maxResult, Map<String, String[]> filters) {
filters.put("permission", new String[] {Boolean.TRUE.toString()});
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
index 0a913dd..ecebaae 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -35,12 +35,11 @@ import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.OAuthErrorException;
import org.keycloak.authorization.AuthorizationProvider;
-import org.keycloak.authorization.util.Permissions;
-import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest;
import org.keycloak.authorization.admin.representation.PolicyEvaluationResponseBuilder;
import org.keycloak.authorization.attribute.Attributes;
import org.keycloak.authorization.common.KeycloakEvaluationContext;
@@ -54,6 +53,7 @@ import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.authorization.util.Permissions;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
@@ -61,11 +61,14 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
-import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
+import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
@@ -118,18 +121,18 @@ public class PolicyEvaluationService {
this.auth.realm().requireViewAuthorization();
CloseableKeycloakIdentity identity = createIdentity(evaluationRequest);
try {
- EvaluationContext evaluationContext = createEvaluationContext(evaluationRequest, identity);
- Decision decisionCollector = new Decision();
- authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate(decisionCollector);
- if (decisionCollector.error != null) {
- throw decisionCollector.error;
- }
- return Response.ok(PolicyEvaluationResponseBuilder.build(decisionCollector.results, resourceServer, authorization, identity)).build();
+ return Response.ok(PolicyEvaluationResponseBuilder.build(evaluate(evaluationRequest, createEvaluationContext(evaluationRequest, identity)), resourceServer, authorization, identity)).build();
+ } catch (Exception e) {
+ throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, "Error while evaluating permissions.", Status.INTERNAL_SERVER_ERROR);
} finally {
identity.close();
}
}
+ private List<Result> evaluate(PolicyEvaluationRequest evaluationRequest, EvaluationContext evaluationContext) {
+ return authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate();
+ }
+
private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation, KeycloakIdentity identity) {
return new KeycloakEvaluationContext(identity, this.authorization.getKeycloakSession()) {
@Override
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java
index 51bc2a6..b32899f 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyResourceService.java
@@ -26,8 +26,10 @@ import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.authorization.AuthorizationProvider;
@@ -36,6 +38,8 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
@@ -43,6 +47,7 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.util.JsonSerialization;
/**
@@ -54,19 +59,21 @@ public class PolicyResourceService {
protected final ResourceServer resourceServer;
protected final AuthorizationProvider authorization;
protected final AdminPermissionEvaluator auth;
+ private final AdminEventBuilder adminEvent;
- public PolicyResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) {
+ public PolicyResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.policy = policy;
this.resourceServer = resourceServer;
this.authorization = authorization;
this.auth = auth;
+ this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_POLICY);
}
@PUT
@Consumes("application/json")
@Produces("application/json")
@NoCache
- public Response update(String payload) {
+ public Response update(@Context UriInfo uriInfo,String payload) {
this.auth.realm().requireManageAuthorization();
AbstractPolicyRepresentation representation = doCreateRepresentation(payload);
@@ -79,11 +86,14 @@ public class PolicyResourceService {
RepresentationToModel.toModel(representation, authorization, policy);
+
+ audit(uriInfo, representation, OperationType.UPDATE);
+
return Response.status(Status.CREATED).build();
}
@DELETE
- public Response delete() {
+ public Response delete(@Context UriInfo uriInfo) {
this.auth.realm().requireManageAuthorization();
if (policy == null) {
@@ -98,6 +108,10 @@ public class PolicyResourceService {
policyStore.delete(policy.getId());
+ if (authorization.getRealm().isAdminEventsEnabled()) {
+ audit(uriInfo, toRepresentation(policy, authorization), OperationType.DELETE);
+ }
+
return Response.noContent().build();
}
@@ -225,4 +239,10 @@ public class PolicyResourceService {
protected Policy getPolicy() {
return policy;
}
+
+ private void audit(@Context UriInfo uriInfo, AbstractPolicyRepresentation policy, OperationType operation) {
+ if (authorization.getRealm().isAdminEventsEnabled()) {
+ adminEvent.operation(operation).resourcePath(uriInfo).representation(policy).success();
+ }
+ }
}
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 ccb0d23..33e6299 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
@@ -33,9 +33,11 @@ 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.Response.Status;
+import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
@@ -46,13 +48,17 @@ import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.Constants;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.util.JsonSerialization;
/**
@@ -63,11 +69,13 @@ public class PolicyService {
protected final ResourceServer resourceServer;
protected final AuthorizationProvider authorization;
protected final AdminPermissionEvaluator auth;
+ protected final AdminEventBuilder adminEvent;
- public PolicyService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) {
+ public PolicyService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.resourceServer = resourceServer;
this.authorization = authorization;
this.auth = auth;
+ this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_POLICY);
}
@Path("{type}")
@@ -84,18 +92,18 @@ public class PolicyService {
}
protected PolicyTypeService doCreatePolicyTypeResource(String type) {
- return new PolicyTypeService(type, resourceServer, authorization, auth);
+ return new PolicyTypeService(type, resourceServer, authorization, auth, adminEvent);
}
protected Object doCreatePolicyResource(Policy policy) {
- return new PolicyResourceService(policy, resourceServer, authorization, auth);
+ return new PolicyResourceService(policy, resourceServer, authorization, auth, adminEvent);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@NoCache
- public Response create(String payload) {
+ public Response create(@Context UriInfo uriInfo, String payload) {
this.auth.realm().requireManageAuthorization();
AbstractPolicyRepresentation representation = doCreateRepresentation(payload);
@@ -103,6 +111,8 @@ public class PolicyService {
representation.setId(policy.getId());
+ audit(uriInfo, representation, representation.getId(), OperationType.CREATE);
+
return Response.status(Status.CREATED).entity(representation).build();
}
@@ -280,4 +290,14 @@ public class PolicyService {
findAssociatedPolicies(associated, policies);
});
}
+
+ private void audit(@Context UriInfo uriInfo, AbstractPolicyRepresentation resource, String id, OperationType operation) {
+ if (authorization.getRealm().isAdminEventsEnabled()) {
+ if (id != null) {
+ adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success();
+ } else {
+ adminEvent.operation(operation).resourcePath(uriInfo).representation(resource).success();
+ }
+ }
+ }
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java
index 71a6695..1297b6a 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeResourceService.java
@@ -25,6 +25,7 @@ import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.util.JsonSerialization;
/**
@@ -32,8 +33,8 @@ import org.keycloak.util.JsonSerialization;
*/
public class PolicyTypeResourceService extends PolicyResourceService {
- public PolicyTypeResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) {
- super(policy, resourceServer, authorization, auth);
+ public PolicyTypeResourceService(Policy policy, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+ super(policy, resourceServer, authorization, auth, adminEvent);
}
@Override
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 25877e0..4b41e94 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyTypeService.java
@@ -31,6 +31,7 @@ import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.util.JsonSerialization;
/**
@@ -40,8 +41,8 @@ public class PolicyTypeService extends PolicyService {
private final String type;
- PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) {
- super(resourceServer, authorization, auth);
+ PolicyTypeService(String type, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+ super(resourceServer, authorization, auth, adminEvent);
this.type = type;
}
@@ -60,7 +61,7 @@ public class PolicyTypeService extends PolicyService {
@Override
protected Object doCreatePolicyResource(Policy policy) {
- return new PolicyTypeResourceService(policy, resourceServer,authorization, auth);
+ return new PolicyTypeResourceService(policy, resourceServer,authorization, auth, adminEvent);
}
@Override
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 4b59450..81d556e 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
@@ -52,7 +52,7 @@ public class PolicyEvaluationResponseBuilder {
AccessToken accessToken = identity.getAccessToken();
AccessToken.Authorization authorizationData = new AccessToken.Authorization();
- authorizationData.setPermissions(Permissions.permits(results, authorization, resourceServer.getId()));
+ authorizationData.setPermissions(Permissions.permits(results, null, authorization, resourceServer));
accessToken.setAuthorization(authorizationData);
response.setRpt(accessToken);
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
index a9dc6e0..e52da9a 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
@@ -40,12 +40,15 @@ import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
import org.keycloak.exportimport.util.ExportUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.Logic;
@@ -54,6 +57,7 @@ import org.keycloak.representations.idm.authorization.ResourcePermissionRepresen
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -62,19 +66,24 @@ public class ResourceServerService {
private final AuthorizationProvider authorization;
private final AdminPermissionEvaluator auth;
+ private final AdminEventBuilder adminEvent;
private final KeycloakSession session;
private ResourceServer resourceServer;
private final ClientModel client;
- public ResourceServerService(AuthorizationProvider authorization, ResourceServer resourceServer, ClientModel client, AdminPermissionEvaluator auth) {
+ @Context
+ private UriInfo uriInfo;
+
+ public ResourceServerService(AuthorizationProvider authorization, ResourceServer resourceServer, ClientModel client, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.authorization = authorization;
this.session = authorization.getKeycloakSession();
this.client = client;
this.resourceServer = resourceServer;
this.auth = auth;
+ this.adminEvent = adminEvent;
}
- public void create() {
+ public void create(boolean newClient) {
this.auth.realm().requireManageAuthorization();
UserModel serviceAccount = this.session.users().getServiceAccount(client);
@@ -86,6 +95,7 @@ public class ResourceServerService {
this.resourceServer = this.authorization.getStoreFactory().getResourceServerStore().create(this.client.getId());
createDefaultRoles(serviceAccount);
createDefaultPermission(createDefaultResource(), createDefaultPolicy());
+ audit(OperationType.CREATE, uriInfo, newClient);
}
@PUT
@@ -95,7 +105,7 @@ public class ResourceServerService {
this.auth.realm().requireManageAuthorization();
this.resourceServer.setAllowRemoteResourceManagement(server.isAllowRemoteResourceManagement());
this.resourceServer.setPolicyEnforcementMode(server.getPolicyEnforcementMode());
-
+ audit(OperationType.UPDATE, uriInfo, false);
return Response.noContent().build();
}
@@ -105,17 +115,19 @@ public class ResourceServerService {
ResourceStore resourceStore = storeFactory.getResourceStore();
String id = resourceServer.getId();
+ PolicyStore policyStore = storeFactory.getPolicyStore();
+
+ policyStore.findByResourceServer(id).forEach(scope -> policyStore.delete(scope.getId()));
+
resourceStore.findByResourceServer(id).forEach(resource -> resourceStore.delete(resource.getId()));
ScopeStore scopeStore = storeFactory.getScopeStore();
scopeStore.findByResourceServer(id).forEach(scope -> scopeStore.delete(scope.getId()));
- PolicyStore policyStore = storeFactory.getPolicyStore();
-
- policyStore.findByResourceServer(id).forEach(scope -> policyStore.delete(scope.getId()));
-
storeFactory.getResourceServerStore().delete(id);
+
+ audit(OperationType.DELETE, uriInfo, false);
}
@GET
@@ -143,12 +155,14 @@ public class ResourceServerService {
RepresentationToModel.toModel(rep, authorization);
+ audit(OperationType.UPDATE, uriInfo, false);
+
return Response.noContent().build();
}
@Path("/resource")
public ResourceSetService getResourceSetResource() {
- ResourceSetService resource = new ResourceSetService(this.resourceServer, this.authorization, this.auth);
+ ResourceSetService resource = new ResourceSetService(this.resourceServer, this.authorization, this.auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -157,7 +171,7 @@ public class ResourceServerService {
@Path("/scope")
public ScopeService getScopeResource() {
- ScopeService resource = new ScopeService(this.resourceServer, this.authorization, this.auth);
+ ScopeService resource = new ScopeService(this.resourceServer, this.authorization, this.auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -166,7 +180,7 @@ public class ResourceServerService {
@Path("/policy")
public PolicyService getPolicyResource() {
- PolicyService resource = new PolicyService(this.resourceServer, this.authorization, this.auth);
+ PolicyService resource = new PolicyService(this.resourceServer, this.authorization, this.auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -176,7 +190,7 @@ public class ResourceServerService {
@Path("/permission")
public Object getPermissionTypeResource() {
this.auth.realm().requireViewAuthorization();
- PermissionService resource = new PermissionService(this.resourceServer, this.authorization, this.auth);
+ PermissionService resource = new PermissionService(this.resourceServer, this.authorization, this.auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -239,4 +253,14 @@ public class ResourceServerService {
serviceAccount.grantRole(umaProtectionRole);
}
}
+
+ private void audit(OperationType operation, UriInfo uriInfo, boolean newClient) {
+ if (newClient) {
+ adminEvent.resource(ResourceType.AUTHORIZATION_RESOURCE_SERVER).operation(operation).resourcePath(uriInfo, client.getId())
+ .representation(ModelToRepresentation.toRepresentation(resourceServer, client)).success();
+ } else {
+ adminEvent.resource(ResourceType.AUTHORIZATION_RESOURCE_SERVER).operation(operation).resourcePath(uriInfo)
+ .representation(ModelToRepresentation.toRepresentation(resourceServer, client)).success();
+ }
+ }
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
index c8eaafe..73afdf0 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
@@ -26,6 +26,8 @@ import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
@@ -38,6 +40,7 @@ import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -48,8 +51,10 @@ 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.Response;
import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
import java.util.ArrayList;
import java.util.Collections;
@@ -70,17 +75,25 @@ public class ResourceSetService {
private final AuthorizationProvider authorization;
private final AdminPermissionEvaluator auth;
+ private final AdminEventBuilder adminEvent;
private ResourceServer resourceServer;
- public ResourceSetService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) {
+ public ResourceSetService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.resourceServer = resourceServer;
this.authorization = authorization;
this.auth = auth;
+ this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_RESOURCE);
}
@POST
@Consumes("application/json")
@Produces("application/json")
+ public Response create(@Context UriInfo uriInfo, ResourceRepresentation resource) {
+ Response response = create(resource);
+ audit(uriInfo, resource, resource.getId(), OperationType.CREATE);
+ return response;
+ }
+
public Response create(ResourceRepresentation resource) {
requireManage();
StoreFactory storeFactory = this.authorization.getStoreFactory();
@@ -128,7 +141,7 @@ public class ResourceSetService {
@PUT
@Consumes("application/json")
@Produces("application/json")
- public Response update(@PathParam("id") String id, ResourceRepresentation resource) {
+ public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, ResourceRepresentation resource) {
requireManage();
resource.setId(id);
StoreFactory storeFactory = this.authorization.getStoreFactory();
@@ -141,12 +154,14 @@ public class ResourceSetService {
toModel(resource, resourceServer, authorization);
+ audit(uriInfo, resource, OperationType.UPDATE);
+
return Response.noContent().build();
}
@Path("{id}")
@DELETE
- public Response delete(@PathParam("id") String id) {
+ public Response delete(@Context UriInfo uriInfo, @PathParam("id") String id) {
requireManage();
StoreFactory storeFactory = authorization.getStoreFactory();
Resource resource = storeFactory.getResourceStore().findById(id, resourceServer.getId());
@@ -168,6 +183,10 @@ public class ResourceSetService {
storeFactory.getResourceStore().delete(id);
+ if (authorization.getRealm().isAdminEventsEnabled()) {
+ audit(uriInfo, toRepresentation(resource, resourceServer, authorization), OperationType.DELETE);
+ }
+
return Response.noContent().build();
}
@@ -376,4 +395,18 @@ public class ResourceSetService {
this.auth.realm().requireManageAuthorization();
}
}
+
+ private void audit(@Context UriInfo uriInfo, ResourceRepresentation resource, OperationType operation) {
+ audit(uriInfo, resource, null, operation);
+ }
+
+ private void audit(@Context UriInfo uriInfo, ResourceRepresentation resource, String id, OperationType operation) {
+ if (authorization.getRealm().isAdminEventsEnabled()) {
+ if (id != null) {
+ adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success();
+ } else {
+ adminEvent.operation(operation).resourcePath(uriInfo).representation(resource).success();
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java
index 157b3a0..ed328f5 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java
@@ -25,12 +25,15 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.StoreFactory;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -41,9 +44,11 @@ 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.Response.Status;
+import javax.ws.rs.core.UriInfo;
import java.util.Arrays;
import java.util.HashMap;
@@ -61,23 +66,27 @@ public class ScopeService {
private final AuthorizationProvider authorization;
private final AdminPermissionEvaluator auth;
+ private final AdminEventBuilder adminEvent;
private ResourceServer resourceServer;
- public ScopeService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth) {
+ public ScopeService(ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.resourceServer = resourceServer;
this.authorization = authorization;
this.auth = auth;
+ this.adminEvent = adminEvent.resource(ResourceType.AUTHORIZATION_SCOPE);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- public Response create(ScopeRepresentation scope) {
- this.auth.realm().requireManageAuthorization();
+ public Response create(@Context UriInfo uriInfo, ScopeRepresentation scope) {
+ this.auth.realm().requireManageAuthorization();
Scope model = toModel(scope, this.resourceServer, authorization);
scope.setId(model.getId());
+ audit(uriInfo, scope, scope.getId(), OperationType.CREATE);
+
return Response.status(Status.CREATED).entity(scope).build();
}
@@ -85,8 +94,8 @@ public class ScopeService {
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- public Response update(@PathParam("id") String id, ScopeRepresentation scope) {
- this.auth.realm().requireManageAuthorization();
+ public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, ScopeRepresentation scope) {
+ this.auth.realm().requireManageAuthorization();
scope.setId(id);
StoreFactory storeFactory = authorization.getStoreFactory();
Scope model = storeFactory.getScopeStore().findById(scope.getId(), resourceServer.getId());
@@ -97,12 +106,14 @@ public class ScopeService {
toModel(scope, resourceServer, authorization);
+ audit(uriInfo, scope, OperationType.UPDATE);
+
return Response.noContent().build();
}
@Path("{id}")
@DELETE
- public Response delete(@PathParam("id") String id) {
+ public Response delete(@Context UriInfo uriInfo, @PathParam("id") String id) {
this.auth.realm().requireManageAuthorization();
StoreFactory storeFactory = authorization.getStoreFactory();
List<Resource> resources = storeFactory.getResourceStore().findByScope(Arrays.asList(id), resourceServer.getId());
@@ -130,6 +141,10 @@ public class ScopeService {
storeFactory.getScopeStore().delete(id);
+ if (authorization.getRealm().isAdminEventsEnabled()) {
+ audit(uriInfo, toRepresentation(scope), OperationType.DELETE);
+ }
+
return Response.noContent().build();
}
@@ -144,7 +159,7 @@ public class ScopeService {
return Response.status(Status.NOT_FOUND).build();
}
- return Response.ok(toRepresentation(model, this.authorization)).build();
+ return Response.ok(toRepresentation(model)).build();
}
@Path("{id}/resources")
@@ -212,22 +227,17 @@ public class ScopeService {
return Response.status(Status.OK).build();
}
- return Response.ok(toRepresentation(model, authorization)).build();
+ return Response.ok(toRepresentation(model)).build();
}
@GET
@Produces("application/json")
public Response findAll(@QueryParam("scopeId") String id,
@QueryParam("name") String name,
- @QueryParam("deep") Boolean deep,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult) {
this.auth.realm().requireViewAuthorization();
- if (deep == null) {
- deep = true;
- }
-
Map<String, String[]> search = new HashMap<>();
if (id != null && !"".equals(id.trim())) {
@@ -238,11 +248,24 @@ public class ScopeService {
search.put("name", new String[] {name});
}
- Boolean finalDeep = deep;
return Response.ok(
this.authorization.getStoreFactory().getScopeStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream()
- .map(scope -> toRepresentation(scope, this.authorization, finalDeep))
+ .map(scope -> toRepresentation(scope))
.collect(Collectors.toList()))
.build();
}
+
+ private void audit(@Context UriInfo uriInfo, ScopeRepresentation resource, OperationType operation) {
+ audit(uriInfo, resource, null, operation);
+ }
+
+ private void audit(@Context UriInfo uriInfo, ScopeRepresentation resource, String id, OperationType operation) {
+ if (authorization.getRealm().isAdminEventsEnabled()) {
+ if (id != null) {
+ adminEvent.operation(operation).resourcePath(uriInfo, id).representation(resource).success();
+ } else {
+ adminEvent.operation(operation).resourcePath(uriInfo).representation(resource).success();
+ }
+ }
+ }
}
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 875a0e0..d407613 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -25,11 +25,11 @@ import org.keycloak.authorization.authorization.representation.AuthorizationRequ
import org.keycloak.authorization.authorization.representation.AuthorizationResponse;
import org.keycloak.authorization.common.KeycloakEvaluationContext;
import org.keycloak.authorization.common.KeycloakIdentity;
+import org.keycloak.authorization.entitlement.representation.EntitlementResponse;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission;
-import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.protection.permission.PermissionTicket;
import org.keycloak.authorization.store.ResourceStore;
@@ -47,14 +47,11 @@ import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.resources.Cors;
-import org.keycloak.services.resources.RealmsResource;
import javax.ws.rs.Consumes;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
-import javax.ws.rs.container.AsyncResponse;
-import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@@ -97,7 +94,7 @@ public class AuthorizationTokenService {
@POST
@Consumes("application/json")
@Produces("application/json")
- public void authorize(AuthorizationRequest authorizationRequest, @Suspended AsyncResponse asyncResponse) {
+ public Response authorize(AuthorizationRequest authorizationRequest) {
KeycloakEvaluationContext evaluationContext = new KeycloakEvaluationContext(this.authorization.getKeycloakSession());
KeycloakIdentity identity = (KeycloakIdentity) evaluationContext.getIdentity();
@@ -109,40 +106,47 @@ public class AuthorizationTokenService {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Invalid authorization request.", Status.BAD_REQUEST);
}
- PermissionTicket ticket = verifyPermissionTicket(authorizationRequest);
+ try {
+ PermissionTicket ticket = verifyPermissionTicket(authorizationRequest);
+ ResourceServer resourceServer = authorization.getStoreFactory().getResourceServerStore().findById(ticket.getResourceServerId());
- authorization.evaluators().from(createPermissions(ticket, authorizationRequest, authorization), evaluationContext).evaluate(new DecisionResultCollector() {
- @Override
- public void onComplete(List<Result> results) {
- List<Permission> entitlements = Permissions.permits(results, authorization, ticket.getResourceServerId());
+ if (resourceServer == null) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.FORBIDDEN);
+ }
- if (entitlements.isEmpty()) {
- HashMap<Object, Object> error = new HashMap<>();
+ List<Result> result = authorization.evaluators().from(createPermissions(ticket, authorizationRequest, authorization), evaluationContext).evaluate();
- error.put(OAuth2Constants.ERROR, "not_authorized");
+ List<Permission> entitlements = Permissions.permits(result, authorizationRequest.getMetadata(), authorization, resourceServer);
- asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.FORBIDDEN)
- .entity(error))
- .allowedOrigins(identity.getAccessToken())
- .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
- } else {
- AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken()));
- asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.CREATED).entity(response)).allowedOrigins(identity.getAccessToken())
- .allowedMethods("POST")
- .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
- }
+ if (!entitlements.isEmpty()) {
+ AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken()));
+ return Cors.add(httpRequest, Response.status(Status.CREATED).entity(response)).allowedOrigins(identity.getAccessToken())
+ .allowedMethods("POST")
+ .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
}
+ } catch (Exception cause) {
+ logger.error(cause);
+ throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, "Error while evaluating permissions.", Status.INTERNAL_SERVER_ERROR);
+ }
- @Override
- public void onError(Throwable cause) {
- logger.error("failed authorize", cause);
- asyncResponse.resume(cause);
- }
- });
+ HashMap<Object, Object> error = new HashMap<>();
+
+ error.put(OAuth2Constants.ERROR, "not_authorized");
+
+ return Cors.add(httpRequest, Response.status(Status.FORBIDDEN)
+ .entity(error))
+ .allowedOrigins(identity.getAccessToken())
+ .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
}
private List<ResourcePermission> createPermissions(PermissionTicket ticket, AuthorizationRequest request, AuthorizationProvider authorization) {
StoreFactory storeFactory = authorization.getStoreFactory();
+ ResourceServer resourceServer = authorization.getStoreFactory().getResourceServerStore().findById(ticket.getResourceServerId());
+
+ if (resourceServer == null) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.FORBIDDEN);
+ }
+
Map<String, Set<String>> permissionsToEvaluate = new HashMap<>();
ticket.getResources().forEach(requestedResource -> {
@@ -231,8 +235,6 @@ public class AuthorizationTokenService {
}
}
- ResourceServer resourceServer = authorization.getStoreFactory().getResourceServerStore().findById(ticket.getResourceServerId());
-
return permissionsToEvaluate.entrySet().stream()
.flatMap((Function<Entry<String, Set<String>>, Stream<ResourcePermission>>) entry -> {
String key = entry.getKey();
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequest.java b/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequest.java
index d4f0f24..2faf12f 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequest.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequest.java
@@ -23,6 +23,7 @@ package org.keycloak.authorization.authorization.representation;
*/
public class AuthorizationRequest {
+ private AuthorizationRequestMetadata metadata;
private String ticket;
private String rpt;
@@ -31,10 +32,6 @@ public class AuthorizationRequest {
this.rpt = rpt;
}
- public AuthorizationRequest(String ticket) {
- this(ticket, null);
- }
-
public AuthorizationRequest() {
this(null, null);
}
@@ -46,4 +43,8 @@ public class AuthorizationRequest {
public String getRpt() {
return this.rpt;
}
+
+ public AuthorizationRequestMetadata getMetadata() {
+ return metadata;
+ }
}
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequestMetadata.java b/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequestMetadata.java
new file mode 100644
index 0000000..faa90ce
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/authorization/representation/AuthorizationRequestMetadata.java
@@ -0,0 +1,51 @@
+/*
+ * 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.authorization.authorization.representation;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.sun.org.apache.xpath.internal.operations.Bool;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class AuthorizationRequestMetadata {
+
+ public static final String INCLUDE_RESOURCE_NAME = "include_resource_name";
+
+ @JsonProperty(INCLUDE_RESOURCE_NAME)
+ private boolean includeResourceName = true;
+
+ private int limit;
+
+ public boolean isIncludeResourceName() {
+ return includeResourceName;
+ }
+
+ public void setIncludeResourceName(boolean includeResourceName) {
+ this.includeResourceName = includeResourceName;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public void setLimit(int limit) {
+ this.limit = limit;
+ }
+}
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 71058ac..54097bb 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
@@ -21,12 +21,12 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
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;
@@ -37,8 +37,7 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
-import javax.ws.rs.container.AsyncResponse;
-import javax.ws.rs.container.Suspended;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@@ -48,7 +47,7 @@ import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.authorization.AuthorizationProvider;
-import org.keycloak.authorization.authorization.AuthorizationTokenService;
+import org.keycloak.authorization.authorization.representation.AuthorizationRequestMetadata;
import org.keycloak.authorization.common.KeycloakEvaluationContext;
import org.keycloak.authorization.common.KeycloakIdentity;
import org.keycloak.authorization.entitlement.representation.EntitlementRequest;
@@ -57,8 +56,8 @@ import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission;
-import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
import org.keycloak.authorization.policy.evaluation.Result;
+import org.keycloak.authorization.protection.permission.representation.PermissionRequest;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
@@ -74,7 +73,6 @@ import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
-import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.resources.Cors;
@@ -106,7 +104,7 @@ public class EntitlementService {
@GET()
@Produces("application/json")
@Consumes("application/json")
- public void getAll(@PathParam("resource_server_id") String resourceServerId, @Suspended AsyncResponse asyncResponse) {
+ public Response getAll(@PathParam("resource_server_id") String resourceServerId) {
KeycloakIdentity identity = new KeycloakIdentity(this.authorization.getKeycloakSession());
if (resourceServerId == null) {
@@ -123,39 +121,18 @@ public class EntitlementService {
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(client.getId());
- authorization.evaluators().from(Permissions.all(resourceServer, identity, authorization), new KeycloakEvaluationContext(this.authorization.getKeycloakSession())).evaluate(new DecisionResultCollector() {
-
- @Override
- public void onError(Throwable cause) {
- logger.error("failed", cause);
- asyncResponse.resume(cause);
- }
-
- @Override
- protected void onComplete(List<Result> results) {
- List<Permission> entitlements = Permissions.permits(results, authorization, resourceServer.getId());
-
- if (entitlements.isEmpty()) {
- HashMap<Object, Object> error = new HashMap<>();
-
- error.put(OAuth2Constants.ERROR, "not_authorized");
+ if (resourceServer == null) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.FORBIDDEN);
+ }
- asyncResponse.resume(Cors.add(request, Response.status(Status.FORBIDDEN)
- .entity(error))
- .allowedOrigins(identity.getAccessToken())
- .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
- } else {
- asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements, identity.getAccessToken())))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
- }
- }
- });
+ return evaluate(null, Permissions.all(resourceServer, identity, authorization), identity, resourceServer);
}
@Path("{resource_server_id}")
@POST
@Consumes("application/json")
@Produces("application/json")
- public void get(@PathParam("resource_server_id") String resourceServerId, EntitlementRequest entitlementRequest, @Suspended AsyncResponse asyncResponse) {
+ public Response get(@PathParam("resource_server_id") String resourceServerId, EntitlementRequest entitlementRequest) {
KeycloakIdentity identity = new KeycloakIdentity(this.authorization.getKeycloakSession());
if (entitlementRequest == null) {
@@ -177,41 +154,34 @@ public class EntitlementService {
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(client.getId());
- try {
- authorization.evaluators().from(createPermissions(entitlementRequest, resourceServer, authorization), new KeycloakEvaluationContext(this.authorization.getKeycloakSession())).evaluate(new DecisionResultCollector() {
- @Override
- public void onError(Throwable cause) {
- logger.error("failed", cause);
- asyncResponse.resume(cause);
- }
+ if (resourceServer == null) {
+ throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.FORBIDDEN);
+ }
- @Override
- protected void onComplete(List<Result> results) {
- List<Permission> entitlements = Permissions.permits(results, authorization, resourceServer.getId());
+ return evaluate(entitlementRequest.getMetadata(), createPermissions(entitlementRequest, resourceServer, authorization), identity, resourceServer);
+ }
- if (entitlements.isEmpty()) {
- HashMap<Object, Object> error = new HashMap<>();
+ private Response evaluate(AuthorizationRequestMetadata metadata, List<ResourcePermission> permissions, KeycloakIdentity identity, ResourceServer resourceServer) {
+ try {
+ List<Result> result = authorization.evaluators().from(permissions, new KeycloakEvaluationContext(this.authorization.getKeycloakSession())).evaluate();
+ List<Permission> entitlements = Permissions.permits(result, metadata, authorization, resourceServer);
- error.put(OAuth2Constants.ERROR, "not_authorized");
+ if (!entitlements.isEmpty()) {
+ return Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements, identity.getAccessToken())))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+ }
+ } catch (Exception cause) {
+ logger.error(cause);
+ throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, "Error while evaluating permissions.", Status.INTERNAL_SERVER_ERROR);
+ }
- asyncResponse.resume(Cors.add(request, Response.status(Status.FORBIDDEN)
- .entity(error))
- .allowedOrigins(identity.getAccessToken())
- .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
- } else {
- asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements, identity.getAccessToken())))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
- }
- }
- });
- } catch (Exception e) {
- String message = e.getMessage();
+ HashMap<Object, Object> error = new HashMap<>();
- if (message == null) {
- message = "Could not process authorization request";
- }
+ error.put(OAuth2Constants.ERROR, "not_authorized");
- asyncResponse.resume(ErrorResponse.error(message, Status.BAD_REQUEST));
- }
+ return Cors.add(request, Response.status(Status.FORBIDDEN)
+ .entity(error))
+ .allowedOrigins(identity.getAccessToken())
+ .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
}
private String createRequestingPartyToken(List<Permission> permissions, AccessToken accessToken) {
@@ -226,9 +196,15 @@ public class EntitlementService {
private List<ResourcePermission> createPermissions(EntitlementRequest entitlementRequest, ResourceServer resourceServer, AuthorizationProvider authorization) {
StoreFactory storeFactory = authorization.getStoreFactory();
- Map<String, Set<String>> permissionsToEvaluate = new HashMap<>();
+ Map<String, Set<String>> permissionsToEvaluate = new LinkedHashMap<>();
+ AuthorizationRequestMetadata metadata = entitlementRequest.getMetadata();
+ Integer limit = metadata != null && metadata.getLimit() > 0 ? metadata.getLimit() : null;
+
+ for (PermissionRequest requestedResource : entitlementRequest.getPermissions()) {
+ if (limit != null && limit <= 0) {
+ break;
+ }
- entitlementRequest.getPermissions().forEach(requestedResource -> {
Resource resource;
if (requestedResource.getResourceSetId() != null) {
@@ -242,14 +218,17 @@ public class EntitlementService {
}
Set<ScopeRepresentation> requestedScopes = requestedResource.getScopes().stream().map(ScopeRepresentation::new).collect(Collectors.toSet());
- Set<String> collect = requestedScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet());
+ Set<String> scopeNames = requestedScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet());
if (resource != null) {
- permissionsToEvaluate.put(resource.getId(), collect);
+ permissionsToEvaluate.put(resource.getId(), scopeNames);
+ if (limit != null) {
+ limit--;
+ }
} else {
ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore();
ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore();
- List<Resource> resources = new ArrayList<Resource>();
+ List<Resource> resources = new ArrayList<>();
resources.addAll(resourceStore.findByScope(requestedScopes.stream().map(scopeRepresentation -> {
Scope scope = scopeStore.findByName(scopeRepresentation.getName(), resourceServer.getId());
@@ -262,17 +241,21 @@ public class EntitlementService {
}).filter(s -> s != null).collect(Collectors.toList()), resourceServer.getId()));
for (Resource resource1 : resources) {
- permissionsToEvaluate.put(resource1.getId(), collect);
+ permissionsToEvaluate.put(resource1.getId(), scopeNames);
+ if (limit != null) {
+ limit--;
+ }
}
- permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", collect);
+ permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", scopeNames);
}
- });
+ }
String rpt = entitlementRequest.getRpt();
if (rpt != null && !"".equals(rpt)) {
KeycloakContext context = authorization.getKeycloakSession().getContext();
+
if (!Tokens.verifySignature(session, context.getRealm(), rpt)) {
throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN);
}
@@ -292,7 +275,11 @@ public class EntitlementService {
List<Permission> permissions = authorizationData.getPermissions();
if (permissions != null) {
- permissions.forEach(permission -> {
+ for (Permission permission : permissions) {
+ if (limit != null && limit <= 0) {
+ break;
+ }
+
Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId(), resourceServer.getId());
if (resourcePermission != null) {
@@ -301,6 +288,9 @@ public class EntitlementService {
if (scopes == null) {
scopes = new HashSet<>();
permissionsToEvaluate.put(resourcePermission.getId(), scopes);
+ if (limit != null) {
+ limit--;
+ }
}
Set<String> scopePermission = permission.getScopes();
@@ -309,7 +299,7 @@ public class EntitlementService {
scopes.addAll(scopePermission);
}
}
- });
+ }
}
}
}
diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/representation/EntitlementRequest.java b/services/src/main/java/org/keycloak/authorization/entitlement/representation/EntitlementRequest.java
index 444645a..f5a5745 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/representation/EntitlementRequest.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/representation/EntitlementRequest.java
@@ -1,24 +1,78 @@
package org.keycloak.authorization.entitlement.representation;
-import org.keycloak.authorization.protection.permission.representation.PermissionRequest;
-
import java.util.ArrayList;
import java.util.List;
+import org.keycloak.authorization.authorization.representation.AuthorizationRequestMetadata;
+import org.keycloak.authorization.protection.permission.representation.PermissionRequest;
+
/**
+ * <p>An {@code {@link EntitlementRequest} represents a request sent to the server containing the permissions being requested.
+ *
+ * <p>Along with an entitlement request additional {@link AuthorizationRequestMetadata} information can be passed in order to define what clients expect from
+ * the server when evaluating the requested permissions and when returning with a response.
+ *
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class EntitlementRequest {
private String rpt;
+ private AuthorizationRequestMetadata metadata;
private List<PermissionRequest> permissions = new ArrayList<>();
+ /**
+ * Returns the permissions being requested.
+ *
+ * @return the permissions being requested (not {@code null})
+ */
public List<PermissionRequest> getPermissions() {
return permissions;
}
+ /**
+ * Set the permissions being requested
+ *
+ * @param permissions the permissions being requests (not {@code null})
+ */
+ public void setPermissions(List<PermissionRequest> permissions) {
+ this.permissions = permissions;
+ }
+
+ /**
+ * Returns a {@code String} representing a previously issued RPT which permissions will be included the response in addition to the new ones being requested.
+ *
+ * @return a previously issued RPT (may be {@code null})
+ */
public String getRpt() {
return rpt;
}
+
+ /**
+ * A {@code String} representing a previously issued RPT which permissions will be included the response in addition to the new ones being requested.
+ *
+ * @param rpt a previously issued RPT. If {@code null}, only the requested permissions are evaluated
+ */
+ public void setRpt(String rpt) {
+ this.rpt = rpt;
+ }
+
+ /**
+ * Return the {@link Metadata} associated with this request.
+ *
+ * @return
+ */
+ public AuthorizationRequestMetadata getMetadata() {
+ return metadata;
+ }
+
+ /**
+ * The {@link Metadata} associated with this request. The metadata defines specific information that should be considered
+ * by the server when evaluating and returning permissions.
+ *
+ * @param metadata the {@link Metadata} associated with this request (may be {@code null})
+ */
+ public void setMetadata(AuthorizationRequestMetadata metadata) {
+ this.metadata = metadata;
+ }
}
diff --git a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
index 45fb6fe..3779279 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
@@ -27,12 +27,17 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.protection.permission.PermissionService;
import org.keycloak.authorization.protection.permission.PermissionsService;
import org.keycloak.authorization.protection.resource.ResourceService;
+import org.keycloak.common.ClientConnection;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.resources.admin.AdminAuth;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response.Status;
/**
@@ -41,6 +46,8 @@ import javax.ws.rs.core.Response.Status;
public class ProtectionService {
private final AuthorizationProvider authorization;
+ @Context
+ protected ClientConnection clientConnection;
public ProtectionService(AuthorizationProvider authorization) {
this.authorization = authorization;
@@ -50,7 +57,12 @@ public class ProtectionService {
public Object resource() {
KeycloakIdentity identity = createIdentity();
ResourceServer resourceServer = getResourceServer(identity);
- ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null);
+ RealmModel realm = authorization.getRealm();
+ ClientModel client = realm.getClientById(identity.getId());
+ KeycloakSession keycloakSession = authorization.getKeycloakSession();
+ UserModel serviceAccount = keycloakSession.users().getServiceAccount(client);
+ AdminEventBuilder adminEvent = new AdminEventBuilder(realm, new AdminAuth(realm, identity.getAccessToken(), serviceAccount, client), keycloakSession, clientConnection);
+ ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null, adminEvent.realm(realm).authClient(client).authUser(serviceAccount));
ResteasyProviderFactory.getInstance().injectProperties(resourceManager);
diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
index 1695ba5..c2e11dc 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
@@ -31,8 +31,10 @@ 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.Response;
import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.admin.ResourceSetService;
@@ -68,10 +70,10 @@ public class ResourceService {
@POST
@Consumes("application/json")
@Produces("application/json")
- public Response create(UmaResourceRepresentation umaResource) {
+ public Response create(@Context UriInfo uriInfo, UmaResourceRepresentation umaResource) {
checkResourceServerSettings();
ResourceRepresentation resource = toResourceRepresentation(umaResource);
- Response response = this.resourceManager.create(resource);
+ Response response = this.resourceManager.create(uriInfo, resource);
if (response.getEntity() instanceof ResourceRepresentation) {
return Response.status(Status.CREATED).entity(toUmaRepresentation((ResourceRepresentation) response.getEntity())).build();
@@ -84,9 +86,9 @@ public class ResourceService {
@PUT
@Consumes("application/json")
@Produces("application/json")
- public Response update(@PathParam("id") String id, UmaResourceRepresentation representation) {
+ public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, UmaResourceRepresentation representation) {
ResourceRepresentation resource = toResourceRepresentation(representation);
- Response response = this.resourceManager.update(id, resource);
+ Response response = this.resourceManager.update(uriInfo, id, resource);
if (response.getEntity() instanceof ResourceRepresentation) {
return Response.noContent().build();
@@ -97,9 +99,9 @@ public class ResourceService {
@Path("/{id}")
@DELETE
- public Response delete(@PathParam("id") String id) {
+ public Response delete(@Context UriInfo uriInfo, @PathParam("id") String id) {
checkResourceServerSettings();
- return this.resourceManager.delete(id);
+ return this.resourceManager.delete(uriInfo, id);
}
@Path("/{id}")
diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
index 3ee7812..b0e5daa 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -23,6 +23,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -31,6 +32,7 @@ import java.util.stream.Collectors;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision.Effect;
+import org.keycloak.authorization.authorization.representation.AuthorizationRequestMetadata;
import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
@@ -140,8 +142,8 @@ public final class Permissions {
return permissions;
}
- public static List<Permission> permits(List<Result> evaluation, AuthorizationProvider authorizationProvider, String resourceServerId) {
- Map<String, Permission> permissions = new HashMap<>();
+ public static List<Permission> permits(List<Result> evaluation, AuthorizationRequestMetadata metadata, AuthorizationProvider authorizationProvider, ResourceServer resourceServer) {
+ Map<String, Permission> permissions = new LinkedHashMap<>();
for (Result result : evaluation) {
Set<Scope> deniedScopes = new HashSet<>();
@@ -194,14 +196,14 @@ public final class Permissions {
if (deniedCount == 0) {
result.setStatus(Effect.PERMIT);
- grantPermission(authorizationProvider, permissions, permission, resourceServerId);
+ grantPermission(authorizationProvider, permissions, permission, resourceServer, metadata);
} else {
// if a full deny or resource denied or the requested scopes were denied
if (deniedCount == results.size() || resourceDenied || (!deniedScopes.isEmpty() && grantedScopes.isEmpty())) {
result.setStatus(Effect.DENY);
} else {
result.setStatus(Effect.PERMIT);
- grantPermission(authorizationProvider, permissions, permission, resourceServerId);
+ grantPermission(authorizationProvider, permissions, permission, resourceServer, metadata);
}
}
}
@@ -218,7 +220,7 @@ public final class Permissions {
return "scope".equals(policy.getType());
}
- private static void grantPermission(AuthorizationProvider authorizationProvider, Map<String, Permission> permissions, ResourcePermission permission, String resourceServer) {
+ private static void grantPermission(AuthorizationProvider authorizationProvider, Map<String, Permission> permissions, ResourcePermission permission, ResourceServer resourceServer, AuthorizationRequestMetadata metadata) {
List<Resource> resources = new ArrayList<>();
Resource resource = permission.getResource();
Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet());
@@ -230,14 +232,14 @@ public final class Permissions {
if (!permissionScopes.isEmpty()) {
ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore();
- resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer));
+ resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()));
}
}
if (!resources.isEmpty()) {
for (Resource allowedResource : resources) {
String resourceId = allowedResource.getId();
- String resourceName = allowedResource.getName();
+ String resourceName = metadata == null || metadata.isIncludeResourceName() ? allowedResource.getName() : null;
Permission evalPermission = permissions.get(allowedResource.getId());
if (evalPermission == null) {
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 7eaf246..facce6c 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -343,7 +343,7 @@ public class ExportUtils {
representation.setPolicies(policies);
List<ScopeRepresentation> scopes = storeFactory.getScopeStore().findByResourceServer(settingsModel.getId()).stream().map(scope -> {
- ScopeRepresentation rep = toRepresentation(scope, authorization);
+ ScopeRepresentation rep = toRepresentation(scope);
rep.setId(null);
rep.setPolicies(null);
@@ -358,11 +358,8 @@ public class ExportUtils {
}
private static PolicyRepresentation createPolicyRepresentation(AuthorizationProvider authorizationProvider, Policy policy) {
- KeycloakSession session = authorizationProvider.getKeycloakSession();
- RealmModel realm = authorizationProvider.getRealm();
-
try {
- PolicyRepresentation rep = toRepresentation(policy, PolicyRepresentation.class, authorizationProvider);
+ PolicyRepresentation rep = toRepresentation(policy, PolicyRepresentation.class, authorizationProvider, true);
rep.setId(null);
@@ -370,24 +367,6 @@ public class ExportUtils {
rep.setConfig(config);
- String roles = config.get("roles");
-
- if (roles != null && !roles.isEmpty()) {
- List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
- config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleMap -> {
- roleMap.put("id", realm.getRoleById(roleMap.get("id").toString()).getName());
- return roleMap;
- }).collect(Collectors.toList())));
- }
-
- String users = config.get("users");
-
- if (users != null && !users.isEmpty()) {
- UserProvider userManager = session.users();
- List<String> userIds = JsonSerialization.readValue(users, List.class);
- config.put("users", JsonSerialization.writeValueAsString(userIds.stream().map(userId -> userManager.getUserById(userId, realm).getUsername()).collect(Collectors.toList())));
- }
-
Set<Scope> scopes = policy.getScopes();
if (!scopes.isEmpty()) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
index dfae565..4fc73f7 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
@@ -151,7 +151,7 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
}
private void configureAuthorizationSettings(KeycloakSession session, ClientModel client, ClientManager.InstallationAdapterConfig rep) {
- if (new AuthorizationService(session, client, null).isEnabled()) {
+ if (new AuthorizationService(session, client, null, null).isEnabled()) {
PolicyEnforcerConfig enforcerConfig = new PolicyEnforcerConfig();
enforcerConfig.setEnforcementMode(null);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
index 09b39c4..374581d 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
@@ -64,24 +64,32 @@ public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapp
}
@Override
- public final IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
- setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+ public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+ setIDTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
return token;
}
@Override
- public final AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
- setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+ public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+ setAccessTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
return token;
}
@Override
- public final AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
- setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+ public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+ setUserInfoTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
return token;
}
- private void setSubject(IDToken token, String pairwiseSub) {
+ protected void setIDTokenSubject(IDToken token, String pairwiseSub) {
+ token.setSubject(pairwiseSub);
+ }
+
+ protected void setAccessTokenSubject(IDToken token, String pairwiseSub) {
+ token.setSubject(pairwiseSub);
+ }
+
+ protected void setUserInfoTokenSubject(IDToken token, String pairwiseSub) {
token.getOtherClaims().put("sub", pairwiseSub);
}
@@ -115,6 +123,4 @@ public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapp
public final String getId() {
return "oidc-" + getIdPrefix() + PROVIDER_ID_SUFFIX;
}
-}
-
-
+}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA256PairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA256PairwiseSubMapper.java
index 28a0b87..f69f2b1 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA256PairwiseSubMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA256PairwiseSubMapper.java
@@ -79,7 +79,7 @@ public class SHA256PairwiseSubMapper extends AbstractPairwiseSubMapper {
Charset charset = Charset.forName("UTF-8");
byte[] salt = saltStr.getBytes(charset);
String pairwiseSub = generateSub(sectorIdentifier, localSub, salt);
- logger.infof("local sub = '%s', pairwise sub = '%s'", localSub, pairwiseSub);
+ logger.tracef("local sub = '%s', pairwise sub = '%s'", localSub, pairwiseSub);
return pairwiseSub;
}
@@ -109,4 +109,4 @@ public class SHA256PairwiseSubMapper extends AbstractPairwiseSubMapper {
public String getIdPrefix() {
return PROVIDER_ID;
}
-}
+}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 0134229..0869c5d 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -30,6 +30,7 @@ import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.ProviderManagerDeployer;
import org.keycloak.provider.ProviderManagerRegistry;
import org.keycloak.provider.Spi;
+import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import java.util.HashMap;
import java.util.HashSet;
@@ -93,6 +94,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
}
// make the session factory ready for hot deployment
ProviderManagerRegistry.SINGLETON.setDeployer(this);
+ AdminPermissions.registerListener(this);
}
protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 95dc02a..bab0440 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -236,8 +236,6 @@ public class RealmManager {
realm.setLoginWithEmailAllowed(true);
realm.setEventsListeners(Collections.singleton("jboss-logging"));
-
- realm.setPasswordPolicy(PasswordPolicy.parse(session, "hashIterations(20000)"));
}
public boolean removeRealm(RealmModel realm) {
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 32a86be..77440be 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
@@ -149,36 +149,13 @@ public class ClientResource {
try {
updateClientFromRep(rep, client, session);
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
+ updateAuthorizationSettings(rep);
return Response.noContent().build();
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
}
}
- public void updateClientFromRep(ClientRepresentation rep, ClientModel client, KeycloakSession session) throws ModelDuplicateException {
- if (TRUE.equals(rep.isServiceAccountsEnabled())) {
- UserModel serviceAccount = this.session.users().getServiceAccount(client);
-
- if (serviceAccount == null) {
- new ClientManager(new RealmManager(session)).enableServiceAccount(client);
- }
- }
-
- if (!rep.getClientId().equals(client.getClientId())) {
- new ClientManager(new RealmManager(session)).clientIdChanged(client, rep.getClientId());
- }
-
- RepresentationToModel.updateClient(rep, client);
-
- if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
- if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
- authorization().enable();
- } else {
- authorization().disable();
- }
- }
- }
-
/**
* Get representation of the client
*
@@ -535,7 +512,7 @@ public class ClientResource {
public AuthorizationService authorization() {
ProfileHelper.requireFeature(Profile.Feature.AUTHORIZATION);
- AuthorizationService resource = new AuthorizationService(this.session, this.client, this.auth);
+ AuthorizationService resource = new AuthorizationService(this.session, this.client, this.auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -592,4 +569,30 @@ public class ClientResource {
}
}
+
+ private void updateClientFromRep(ClientRepresentation rep, ClientModel client, KeycloakSession session) throws ModelDuplicateException {
+ if (TRUE.equals(rep.isServiceAccountsEnabled())) {
+ UserModel serviceAccount = this.session.users().getServiceAccount(client);
+
+ if (serviceAccount == null) {
+ new ClientManager(new RealmManager(session)).enableServiceAccount(client);
+ }
+ }
+
+ if (!rep.getClientId().equals(client.getClientId())) {
+ new ClientManager(new RealmManager(session)).clientIdChanged(client, rep.getClientId());
+ }
+
+ RepresentationToModel.updateClient(rep, client);
+ }
+
+ private void updateAuthorizationSettings(ClientRepresentation rep) {
+ if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
+ if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
+ authorization().enable(false);
+ } else {
+ authorization().disable();
+ }
+ }
+ }
}
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 88b5fdd..decb4da 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
@@ -146,7 +146,7 @@ public class ClientsResource {
}
private AuthorizationService getAuthorizationService(ClientModel clientModel) {
- return new AuthorizationService(session, clientModel, auth);
+ return new AuthorizationService(session, clientModel, auth, adminEvent);
}
/**
@@ -184,14 +184,14 @@ public class ClientsResource {
}
}
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientModel.getId()).representation(rep).success();
+
if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) {
if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
- getAuthorizationService(clientModel).enable();
+ getAuthorizationService(clientModel).enable(true);
}
}
- adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientModel.getId()).representation(rep).success();
-
return Response.created(uriInfo.getAbsolutePathBuilder().path(clientModel.getId()).build()).build();
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
index ef1c1ae..f809e1d 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
@@ -16,9 +16,16 @@
*/
package org.keycloak.services.resources.admin.permissions;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderEvent;
+import org.keycloak.provider.ProviderEventListener;
+import org.keycloak.provider.ProviderEventManager;
import org.keycloak.services.resources.admin.AdminAuth;
/**
@@ -43,5 +50,31 @@ public class AdminPermissions {
return new MgmtPermissions(session, realm);
}
+ public static void registerListener(ProviderEventManager manager) {
+ manager.register(new ProviderEventListener() {
+ @Override
+ public void onEvent(ProviderEvent event) {
+ if (event instanceof RoleContainerModel.RoleRemovedEvent) {
+ RoleContainerModel.RoleRemovedEvent cast = (RoleContainerModel.RoleRemovedEvent)event;
+ RoleModel role = cast.getRole();
+ RealmModel realm;
+ if (role.getContainer() instanceof ClientModel) {
+ realm = ((ClientModel)role.getContainer()).getRealm();
+
+ } else {
+ realm = (RealmModel)role.getContainer();
+ }
+ management(cast.getKeycloakSession(), realm).roles().setPermissionsEnabled(role, false);
+ } else if (event instanceof RealmModel.ClientRemovedEvent) {
+ RealmModel.ClientRemovedEvent cast = (RealmModel.ClientRemovedEvent)event;
+ management(cast.getKeycloakSession(), cast.getClient().getRealm()).clients().setPermissionsEnabled(cast.getClient(), false);
+ } else if (event instanceof GroupModel.GroupRemovedEvent) {
+ GroupModel.GroupRemovedEvent cast = (GroupModel.GroupRemovedEvent)event;
+ management(cast.getKeycloakSession(), cast.getRealm()).groups().setPermissionsEnabled(cast.getGroup(), false);
+ }
+ }
+ });
+ }
+
}
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 1f41e70..95e440e 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
@@ -446,7 +446,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
@Override
public ResourceServer resourceServer(ClientModel client) {
- return authz.getStoreFactory().getResourceServerStore().findByClient(client.getId());
+ return root.resourceServer(client);
}
@Override
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 0475b46..d586c36 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
@@ -73,10 +73,6 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
return "manage.membership.permission.group." + group.getId();
}
- public static String getGroupSuffix(GroupModel group) {
- return ModelToRepresentation.buildGroupPath(group).replace('/', '.');
- }
-
public static String getViewPermissionGroup(GroupModel group) {
return "view.permission.group." + group.getId();
}
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 94fa957..ffcc0f0 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
@@ -222,11 +222,11 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
}
public ResourceServer findOrCreateResourceServer(ClientModel client) {
- ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId());
- if (server == null) {
- server = authz.getStoreFactory().getResourceServerStore().create(client.getId());
- }
- return server;
+ return initializeRealmResourceServer();
+ }
+
+ public ResourceServer resourceServer(ClientModel client) {
+ return realmResourceServer();
}
@Override
@@ -234,6 +234,7 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
if (realmResourceServer != null) return realmResourceServer;
ResourceServerStore resourceServerStore = authz.getStoreFactory().getResourceServerStore();
ClientModel client = getRealmManagementClient();
+ if (client == null) return null;
realmResourceServer = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId());
return realmResourceServer;
@@ -242,7 +243,11 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
public ResourceServer initializeRealmResourceServer() {
if (realmResourceServer != null) return realmResourceServer;
ClientModel client = getRealmManagementClient();
- return findOrCreateResourceServer(client);
+ realmResourceServer = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId());
+ if (realmResourceServer == null) {
+ realmResourceServer = authz.getStoreFactory().getResourceServerStore().create(client.getId());
+ }
+ return realmResourceServer;
}
protected Scope manageScope;
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 a5fee73..e01f4eb 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
@@ -33,7 +33,9 @@ import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.services.ForbiddenException;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -61,26 +63,26 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
@Override
public void setPermissionsEnabled(RoleModel role, boolean enable) {
if (enable) {
- ResourceServer server = getResourceServer(role);
- if (authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId()) != null) {
- return;
- }
- createResource(role);
+ initialize(role);
} else {
- ResourceServer server = resourceServer(role);
- if (server == null) return;
- Policy policy = mapRolePermission(role);
- if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId());
- policy = mapClientScopePermission(role);
- if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId());
- policy = mapCompositePermission(role);
- if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId());
-
- Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId());
- if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId());
+ disablePermissions(role);
}
}
+ private void disablePermissions(RoleModel role) {
+ ResourceServer server = resourceServer(role);
+ if (server == null) return;
+ Policy policy = mapRolePermission(role);
+ if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+ policy = mapClientScopePermission(role);
+ if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+ policy = mapCompositePermission(role);
+ if (policy != null) authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+ Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId());
+ if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId());
+ }
+
@Override
public Map<String, String> getPermissions(RoleModel role) {
Map<String, String> scopes = new HashMap<>();
@@ -94,10 +96,6 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
public Policy mapRolePermission(RoleModel role) {
ResourceServer server = resourceServer(role);
if (server == null) return null;
-
- Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId());
- if (resource == null) return null;
-
return authz.getStoreFactory().getPolicyStore().findByName(getMapRolePermissionName(role), server.getId());
}
@@ -106,9 +104,6 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
ResourceServer server = resourceServer(role);
if (server == null) return null;
- Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId());
- if (resource == null) return null;
-
return authz.getStoreFactory().getPolicyStore().findByName(getMapCompositePermissionName(role), server.getId());
}
@@ -117,9 +112,6 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
ResourceServer server = resourceServer(role);
if (server == null) return null;
- Resource resource = authz.getStoreFactory().getResourceStore().findByName(getRoleResourceName(role), server.getId());
- if (resource == null) return null;
-
return authz.getStoreFactory().getPolicyStore().findByName(getMapClientScopePermissionName(role), server.getId());
}
@@ -134,7 +126,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
@Override
public ResourceServer resourceServer(RoleModel role) {
ClientModel client = getRoleClient(role);
- return authz.getStoreFactory().getResourceServerStore().findByClient(client.getId());
+ return root.resourceServer(client);
}
/**
@@ -157,14 +149,16 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
return false;
}
- ResourceServer resourceServer = getResourceServer(role);
+ ResourceServer resourceServer = resourceServer(role);
+ if (resourceServer == null) return false;
+
Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapRolePermissionName(role), resourceServer.getId());
- if (policy.getAssociatedPolicies().isEmpty()) {
+ if (policy == null || policy.getAssociatedPolicies().isEmpty()) {
return false;
}
Resource roleResource = resource(role);
- Scope mapRoleScope = getMapRoleScope(resourceServer);
+ Scope mapRoleScope = mapRoleScope(resourceServer);
return root.evaluatePermission(roleResource, mapRoleScope, resourceServer);
}
@@ -235,14 +229,16 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
return false;
}
- ResourceServer resourceServer = getResourceServer(role);
+ ResourceServer resourceServer = resourceServer(role);
+ if (resourceServer == null) return false;
+
Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapCompositePermissionName(role), resourceServer.getId());
- if (policy.getAssociatedPolicies().isEmpty()) {
+ if (policy == null || policy.getAssociatedPolicies().isEmpty()) {
return false;
}
Resource roleResource = resource(role);
- Scope scope = getMapCompositeScope(resourceServer);
+ Scope scope = mapCompositeScope(resourceServer);
return root.evaluatePermission(roleResource, scope, resourceServer);
}
@@ -268,14 +264,16 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
return false;
}
- ResourceServer resourceServer = getResourceServer(role);
+ ResourceServer resourceServer = resourceServer(role);
+ if (resourceServer == null) return false;
+
Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getMapClientScopePermissionName(role), resourceServer.getId());
- if (policy.getAssociatedPolicies().isEmpty()) {
+ if (policy == null || policy.getAssociatedPolicies().isEmpty()) {
return false;
}
Resource roleResource = resource(role);
- Scope scope = getMapClientScope(resourceServer);
+ Scope scope = mapClientScope(resourceServer);
return root.evaluatePermission(roleResource, scope, resourceServer);
}
@@ -365,68 +363,87 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
return Helper.createRolePolicy(authz, server, role, policyName);
}
- private Scope getMapRoleScope(ResourceServer server) {
- Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_SCOPE, server.getId());
- if (scope == null) {
- scope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_SCOPE, server);
- }
- return scope;
+ private Scope mapRoleScope(ResourceServer server) {
+ return authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_SCOPE, server.getId());
}
- private Scope getMapClientScope(ResourceServer server) {
- Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_CLIENT_SCOPE_SCOPE, server.getId());
- if (scope == null) {
- scope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_CLIENT_SCOPE_SCOPE, server);
- }
- return scope;
+ private Scope mapClientScope(ResourceServer server) {
+ return authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_CLIENT_SCOPE_SCOPE, server.getId());
}
- private Scope getMapCompositeScope(ResourceServer server) {
- Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_COMPOSITE_SCOPE, server.getId());
- if (scope == null) {
- scope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_COMPOSITE_SCOPE, server);
- }
- return scope;
+ private Scope mapCompositeScope(ResourceServer server) {
+ return authz.getStoreFactory().getScopeStore().findByName(MAP_ROLE_COMPOSITE_SCOPE, server.getId());
}
- private Resource createResource(RoleModel role) {
- ResourceServer server = getResourceServer(role);
- Resource resource = authz.getStoreFactory().getResourceStore().create(getRoleResourceName(role), server, server.getClientId());
- resource.setType("Role");
- Scope mapRoleScope = getMapRoleScope(server);
- Policy mapRolePermission = Helper.addEmptyScopePermission(authz, server, getMapRolePermissionName(role), resource, mapRoleScope);
- mapRolePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+ private void initialize(RoleModel role) {
+ ResourceServer server = resourceServer(role);
+ if (server == null) {
+ ClientModel client = getRoleClient(role);
+ server = root.findOrCreateResourceServer(client);
+ }
+ Scope mapRoleScope = mapRoleScope(server);
+ if (mapRoleScope == null) {
+ mapRoleScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_SCOPE, server);
+ }
+ Scope mapClientScope = mapClientScope(server);
+ if (mapClientScope == null) {
+ mapClientScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_CLIENT_SCOPE_SCOPE, server);
+ }
+ Scope mapCompositeScope = mapCompositeScope(server);
+ if (mapCompositeScope == null) {
+ mapCompositeScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLE_COMPOSITE_SCOPE, server);
+ }
+
+ String roleResourceName = getRoleResourceName(role);
+ Resource resource = authz.getStoreFactory().getResourceStore().findByName(roleResourceName, server.getId());
+ if (resource == null) {
+ resource = authz.getStoreFactory().getResourceStore().create(roleResourceName, server, server.getClientId());
+ Set<Scope> scopeset = new HashSet<>();
+ scopeset.add(mapClientScope);
+ scopeset.add(mapCompositeScope);
+ scopeset.add(mapRoleScope);
+ resource.updateScopes(scopeset);
+ resource.setType("Role");
+ }
+ Policy mapRolePermission = mapRolePermission(role);
+ if (mapRolePermission == null) {
+ mapRolePermission = Helper.addEmptyScopePermission(authz, server, getMapRolePermissionName(role), resource, mapRoleScope);
+ mapRolePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+ }
- Scope mapClientScope = getMapClientScope(server);
- Policy mapClientScopePermission = Helper.addEmptyScopePermission(authz, server, getMapClientScopePermissionName(role), resource, mapClientScope);
- mapClientScopePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+ Policy mapClientScopePermission = mapClientScopePermission(role);
+ if (mapClientScopePermission == null) {
+ mapClientScopePermission = Helper.addEmptyScopePermission(authz, server, getMapClientScopePermissionName(role), resource, mapClientScope);
+ mapClientScopePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+ }
- Scope mapCompositeScope = getMapCompositeScope(server);
- Policy mapCompositePermission = Helper.addEmptyScopePermission(authz, server, getMapCompositePermissionName(role), resource, mapCompositeScope);
- mapCompositePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
- return resource;
+ Policy mapCompositePermission = mapCompositePermission(role);
+ if (mapCompositePermission == null) {
+ mapCompositePermission = Helper.addEmptyScopePermission(authz, server, getMapCompositePermissionName(role), resource, mapCompositeScope);
+ mapCompositePermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+ }
}
private String getMapRolePermissionName(RoleModel role) {
- return MAP_ROLE_SCOPE + ".permission." + role.getName();
+ return MAP_ROLE_SCOPE + ".permission." + role.getId();
}
private String getMapClientScopePermissionName(RoleModel role) {
- return MAP_ROLE_CLIENT_SCOPE_SCOPE + ".permission." + role.getName();
+ return MAP_ROLE_CLIENT_SCOPE_SCOPE + ".permission." + role.getId();
}
private String getMapCompositePermissionName(RoleModel role) {
- return MAP_ROLE_COMPOSITE_SCOPE + ".permission." + role.getName();
+ return MAP_ROLE_COMPOSITE_SCOPE + ".permission." + role.getId();
}
- private ResourceServer getResourceServer(RoleModel role) {
+ private ResourceServer sdfgetResourceServer(RoleModel role) {
ClientModel client = getRoleClient(role);
return root.findOrCreateResourceServer(client);
}
private static String getRoleResourceName(RoleModel role) {
- return "role.resource." + role.getName();
+ return "role.resource." + role.getId();
}
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 6666a7e..13f8aca 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
@@ -129,43 +129,51 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
if (resource == null) return false;
- Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId());
+ Policy policy = managePermission();
return policy != null;
}
@Override
public void setPermissionsEnabled(boolean enable) {
- ClientModel client = root.getRealmManagementClient();
if (enable) {
initialize();
} else {
- ResourceServer server = authz.getStoreFactory().getResourceServerStore().findByClient(client.getId());
- if (server == null) return;
- Policy policy = managePermission();
- if (policy == null) {
- authz.getStoreFactory().getPolicyStore().delete(policy.getId());
-
- }
- policy = viewPermission();
- if (policy == null) {
- authz.getStoreFactory().getPolicyStore().delete(policy.getId());
-
- }
- policy = mapRolesPermission();
- if (policy == null) {
- authz.getStoreFactory().getPolicyStore().delete(policy.getId());
-
- }
- policy = manageGroupMembershipPermission();
- if (policy == null) {
- authz.getStoreFactory().getPolicyStore().delete(policy.getId());
-
- }
- Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
- if (usersResource == null) {
- authz.getStoreFactory().getResourceStore().delete(usersResource.getId());
- }
+ deletePermissionSetup();
+ }
+ }
+
+ private void deletePermissionSetup() {
+ ResourceServer server = root.realmResourceServer();
+ if (server == null) return;
+ Policy policy = managePermission();
+ if (policy == null) {
+ authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+ }
+ policy = viewPermission();
+ if (policy == null) {
+ authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+ }
+ policy = mapRolesPermission();
+ if (policy == null) {
+ authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+ }
+ policy = manageGroupMembershipPermission();
+ if (policy == null) {
+ authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+ }
+ policy = impersonatePermission();
+ if (policy == null) {
+ authz.getStoreFactory().getPolicyStore().delete(policy.getId());
+
+ }
+ Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+ if (usersResource == null) {
+ authz.getStoreFactory().getResourceStore().delete(usersResource.getId());
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 15315d6..c16bb6a 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -70,8 +70,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
@@ -88,6 +90,8 @@ public class KeycloakApplication extends Application {
public static final String KEYCLOAK_EMBEDDED = "keycloak.embedded";
+ public static final String SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES = "keycloak.server.context.config.property-overrides";
+
private static final Logger logger = Logger.getLogger(KeycloakApplication.class);
protected boolean embedded = false;
@@ -262,7 +266,7 @@ public class KeycloakApplication extends Application {
public static void loadConfig(ServletContext context) {
try {
JsonNode node = null;
-
+
String dmrConfig = loadDmrConfig(context);
if (dmrConfig != null) {
node = new ObjectMapper().readTree(dmrConfig);
@@ -287,7 +291,13 @@ public class KeycloakApplication extends Application {
}
if (node != null) {
- Properties properties = new SystemEnvProperties();
+ Map<String, String> propertyOverridesMap = new HashMap<>();
+ String propertyOverrides = context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES);
+ if (context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES) != null) {
+ JsonNode jsonObj = new ObjectMapper().readTree(propertyOverrides);
+ jsonObj.fields().forEachRemaining(e -> propertyOverridesMap.put(e.getKey(), e.getValue().asText()));
+ }
+ Properties properties = new SystemEnvProperties(propertyOverridesMap);
Config.init(new JsonConfigProvider(node, properties));
} else {
throw new RuntimeException("Keycloak config not found.");
diff --git a/services/src/main/resources/scripts/authenticator-template.js b/services/src/main/resources/scripts/authenticator-template.js
index f18dfdf..514337d 100644
--- a/services/src/main/resources/scripts/authenticator-template.js
+++ b/services/src/main/resources/scripts/authenticator-template.js
@@ -15,7 +15,7 @@ AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationF
* session - current KeycloakSession {@see org.keycloak.models.KeycloakSession}
* httpRequest - current HttpRequest {@see org.jboss.resteasy.spi.HttpRequest}
* script - current script {@see org.keycloak.models.ScriptModel}
- * clientSession - current client session {@see org.keycloak.models.ClientSessionModel}
+ * authenticationSession - current client session {@see org.keycloak.sessions.AuthenticationSessionModel}
* LOG - current logger {@see org.jboss.logging.Logger}
*
* You one can extract current http request headers via:
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
index fb23bcd..8dff789 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
@@ -26,7 +26,7 @@ import org.junit.rules.TemporaryFolder;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
-import org.keycloak.credential.hash.Pbkdf2PasswordHashProviderFactory;
+import org.keycloak.credential.hash.Pbkdf2Sha256PasswordHashProviderFactory;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
@@ -85,7 +85,7 @@ public class AddUserTest {
CredentialRepresentation credentials = user.getCredentials().get(0);
- assertEquals(Pbkdf2PasswordHashProviderFactory.ID, credentials.getAlgorithm());
+ assertEquals(Pbkdf2Sha256PasswordHashProviderFactory.ID, credentials.getAlgorithm());
assertEquals(new Integer(100000), credentials.getHashIterations());
KeycloakServer server = new KeycloakServer();
diff --git a/testsuite/integration-arquillian/HOW-TO-RUN.md b/testsuite/integration-arquillian/HOW-TO-RUN.md
index 10becee..02cb7d5 100644
--- a/testsuite/integration-arquillian/HOW-TO-RUN.md
+++ b/testsuite/integration-arquillian/HOW-TO-RUN.md
@@ -417,4 +417,34 @@ and argument: `-p 8181`
3) Run loadbalancer (class `SimpleUndertowLoadBalancer`) without arguments and system properties. Loadbalancer runs on port 8180, so you can access Keycloak on `http://localhost:8180/auth`
+## Cross-DC tests
+Cross-DC tests use 2 data centers, each with one automatically started and one manually controlled backend servers
+(currently only Keycloak on Undertow), and 1 frontend loadbalancer server node that sits in front of all servers.
+The browser usually communicates directly with the frontent node and the test controls where the HTTP requests
+land by adjusting load balancer configuration (e.g. to direct the traffic to only a single DC).
+
+For an example of a test, see [org.keycloak.testsuite.crossdc.ActionTokenCrossDCTest](tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java).
+
+The cross DC requires setting a profile specifying used cache server (currently only Infinispan) by specifying
+`cache-server-infinispan` profile in maven.
+
+#### Run Cross-DC Tests from Maven
+
+First compile the Infinispan/JDG test server via the following command:
+
+ `mvn -Pcache-server-infinispan -f testsuite/integration-arquillian -DskipTests clean install`
+
+or
+
+ `mvn -Pcache-server-jdg -f testsuite/integration-arquillian -DskipTests clean install`
+
+Then you can run the tests using the following command (adjust the test specification according to your needs):
+
+ `mvn -Pcache-server-infinispan -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base test`
+
+or
+
+ `mvn -Pcache-server-jdg -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base test`
+
+_Someone using IntelliJ IDEA, please describe steps for that IDE_
diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml
index 03d9d95..7e36d1a 100644
--- a/testsuite/integration-arquillian/pom.xml
+++ b/testsuite/integration-arquillian/pom.xml
@@ -45,7 +45,8 @@
<selenium.version>2.53.0</selenium.version>
<arquillian-drone.version>2.0.1.Final</arquillian-drone.version>
<arquillian-graphene.version>2.1.0.Alpha3</arquillian-graphene.version>
- <arquillian-wildfly-container.version>2.0.0.Final</arquillian-wildfly-container.version>
+ <arquillian-wildfly-container.version>2.1.0.Alpha2</arquillian-wildfly-container.version>
+ <arquillian-infinispan-container.version>1.2.0.Beta2</arquillian-infinispan-container.version>
<version.shrinkwrap.resolvers>2.2.2</version.shrinkwrap.resolvers>
<undertow-embedded.version>1.0.0.Alpha2</undertow-embedded.version>
@@ -88,6 +89,11 @@
<scope>import</scope>
</dependency>
<dependency>
+ <groupId>org.infinispan.arquillian.container</groupId>
+ <artifactId>infinispan-arquillian-impl</artifactId>
+ <version>${arquillian-infinispan-container.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.wildfly.arquillian</groupId>
<artifactId>wildfly-arquillian-container-managed</artifactId>
<version>${arquillian-wildfly-container.version}</version>
diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml
index 47a1f20..1764447 100644
--- a/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml
+++ b/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml
@@ -35,7 +35,7 @@
<app.server.jboss.groupId>org.jboss.eap</app.server.jboss.groupId>
<app.server.jboss.artifactId>wildfly-dist</app.server.jboss.artifactId>
<app.server.jboss.version>${eap.version}</app.server.jboss.version>
- <app.server.jboss.unpacked.folder.name>jboss-eap-7.0</app.server.jboss.unpacked.folder.name>
+ <app.server.jboss.unpacked.folder.name>jboss-eap-7.1</app.server.jboss.unpacked.folder.name>
<app.server.oidc.adapter.artifactId>keycloak-wildfly-adapter-dist</app.server.oidc.adapter.artifactId>
<app.server.saml.adapter.artifactId>keycloak-saml-wildfly-adapter-dist</app.server.saml.adapter.artifactId>
diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml
index 975a8c5..3775738 100644
--- a/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml
+++ b/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml
@@ -436,6 +436,12 @@
</modules>
</profile>
<profile>
+ <id>app-server-wildfly10</id>
+ <modules>
+ <module>wildfly10</module>
+ </modules>
+ </profile>
+ <profile>
<id>app-server-relative</id>
<modules>
<module>relative</module>
diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/wildfly10/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/wildfly10/pom.xml
new file mode 100644
index 0000000..4d37972
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/app-server/jboss/wildfly10/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<!--
+~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+~ and other contributors as indicated by the @author tags.
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~ http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-servers-app-server-jboss</artifactId>
+ <version>3.2.0.CR1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>integration-arquillian-servers-app-server-wildfly10</artifactId>
+ <packaging>pom</packaging>
+ <name>App Server - JBoss - Wildfly 10</name>
+
+ <properties>
+ <app.server.jboss>wildfly10</app.server.jboss>
+
+ <app.server.jboss.groupId>org.wildfly</app.server.jboss.groupId>
+ <app.server.jboss.artifactId>wildfly-dist</app.server.jboss.artifactId>
+ <app.server.jboss.version>${wildfly10.version}</app.server.jboss.version>
+ <app.server.jboss.unpacked.folder.name>wildfly-${wildfly10.version}</app.server.jboss.unpacked.folder.name>
+
+ <app.server.oidc.adapter.artifactId>keycloak-wildfly-adapter-dist</app.server.oidc.adapter.artifactId>
+ <app.server.saml.adapter.artifactId>keycloak-saml-wildfly-adapter-dist</app.server.saml.adapter.artifactId>
+ </properties>
+
+</project>
diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/wildfly10/src/saml-adapter-supported b/testsuite/integration-arquillian/servers/app-server/jboss/wildfly10/src/saml-adapter-supported
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/app-server/jboss/wildfly10/src/saml-adapter-supported
@@ -0,0 +1 @@
+
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/arquillian/LoadBalancerController.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/arquillian/LoadBalancerController.java
new file mode 100644
index 0000000..1e435b2
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/arquillian/LoadBalancerController.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.arquillian;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public interface LoadBalancerController {
+
+ void enableAllBackendNodes();
+
+ void disableAllBackendNodes();
+
+ void enableBackendNodeByName(String nodeName);
+
+ void disableBackendNodeByName(String nodeName);
+
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java
index be531aa..b6f0b81 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java
@@ -20,6 +20,7 @@ package org.keycloak.testsuite.rest.resource;
import java.util.Set;
import java.util.stream.Collectors;
+import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@@ -70,4 +71,10 @@ public class TestCacheResource {
return cache.size();
}
+ @GET
+ @Path("/clear")
+ @Consumes(MediaType.TEXT_PLAIN)
+ public void clear() {
+ cache.clear();
+ }
}
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
index 4298d35..fdcb092 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
@@ -71,6 +71,11 @@
<artifactId>keycloak-undertow-adapter</artifactId>
</dependency>
<dependency>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-testsuite-providers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
</dependency>
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 87a37d2..b77d9e0 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
@@ -48,6 +48,8 @@ import org.keycloak.services.filters.KeycloakSessionServletFilter;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.resources.KeycloakApplication;
+import org.keycloak.util.JsonSerialization;
+import java.io.IOException;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
@@ -55,6 +57,7 @@ import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUndertowConfiguration> {
@@ -75,6 +78,14 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
di.setContextPath("/auth");
di.setDeploymentName("Keycloak");
di.addInitParameter(KeycloakApplication.KEYCLOAK_EMBEDDED, "true");
+ if (configuration.getKeycloakConfigPropertyOverridesMap() != null) {
+ try {
+ di.addInitParameter(KeycloakApplication.SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES,
+ JsonSerialization.writeValueAsString(configuration.getKeycloakConfigPropertyOverridesMap()));
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
di.setDefaultServletConfig(new DefaultServletConfig(true));
di.addWelcomePage("theme/keycloak/welcome/resources/index.html");
@@ -176,19 +187,14 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
log.info("Using route: " + configuration.getRoute());
}
- SetSystemProperty setRouteProperty = new SetSystemProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME, configuration.getRoute());
- try {
- DeploymentInfo di = createAuthServerDeploymentInfo();
- undertow.deploy(di);
- ResteasyDeployment deployment = (ResteasyDeployment) di.getServletContextAttributes().get(ResteasyDeployment.class.getName());
- sessionFactory = ((KeycloakApplication) deployment.getApplication()).getSessionFactory();
+ DeploymentInfo di = createAuthServerDeploymentInfo();
+ undertow.deploy(di);
+ ResteasyDeployment deployment = (ResteasyDeployment) di.getServletContextAttributes().get(ResteasyDeployment.class.getName());
+ sessionFactory = ((KeycloakApplication) deployment.getApplication()).getSessionFactory();
- setupDevConfig();
+ setupDevConfig();
- log.info("Auth server started in " + (System.currentTimeMillis() - start) + " ms\n");
- } finally {
- setRouteProperty.revert();
- }
+ log.info("Auth server started in " + (System.currentTimeMillis() - start) + " ms\n");
}
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertowConfiguration.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertowConfiguration.java
index bdf0ff7..6caca66 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertowConfiguration.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertowConfiguration.java
@@ -17,6 +17,11 @@
package org.keycloak.testsuite.arquillian.undertow;
+import org.keycloak.util.JsonSerialization;
+import com.fasterxml.jackson.core.type.TypeReference;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
import org.arquillian.undertow.UndertowContainerConfiguration;
import org.jboss.arquillian.container.spi.ConfigurationException;
import org.jboss.logging.Logger;
@@ -29,6 +34,8 @@ public class KeycloakOnUndertowConfiguration extends UndertowContainerConfigurat
private String resourcesHome;
private boolean remoteMode;
private String route;
+ private String keycloakConfigPropertyOverrides;
+ private Map<String, String> keycloakConfigPropertyOverridesMap;
private int bindHttpPortOffset = 0;
@@ -72,6 +79,18 @@ public class KeycloakOnUndertowConfiguration extends UndertowContainerConfigurat
this.remoteMode = remoteMode;
}
+ public String getKeycloakConfigPropertyOverrides() {
+ return keycloakConfigPropertyOverrides;
+ }
+
+ public void setKeycloakConfigPropertyOverrides(String keycloakConfigPropertyOverrides) {
+ this.keycloakConfigPropertyOverrides = keycloakConfigPropertyOverrides;
+ }
+
+ public Map<String, String> getKeycloakConfigPropertyOverridesMap() {
+ return keycloakConfigPropertyOverridesMap;
+ }
+
@Override
public void validate() throws ConfigurationException {
super.validate();
@@ -80,6 +99,15 @@ public class KeycloakOnUndertowConfiguration extends UndertowContainerConfigurat
int newPort = basePort + bindHttpPortOffset;
setBindHttpPort(newPort);
log.info("KeycloakOnUndertow will listen on port: " + newPort);
+
+ if (this.keycloakConfigPropertyOverrides != null) {
+ try {
+ TypeReference<HashMap<String,Object>> typeRef = new TypeReference<HashMap<String,Object>>() {};
+ this.keycloakConfigPropertyOverridesMap = JsonSerialization.sysPropertiesAwareMapper.readValue(this.keycloakConfigPropertyOverrides, typeRef);
+ } catch (IOException ex) {
+ throw new ConfigurationException(ex);
+ }
+ }
// TODO validate workerThreads
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java
index 3eda20c..3b533f1 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancer.java
@@ -18,7 +18,6 @@
package org.keycloak.testsuite.arquillian.undertow.lb;
import java.net.URI;
-import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -36,6 +35,8 @@ import io.undertow.util.AttachmentKey;
import io.undertow.util.Headers;
import org.jboss.logging.Logger;
import org.keycloak.services.managers.AuthenticationSessionManager;
+import java.util.LinkedHashMap;
+import java.util.StringTokenizer;
/**
* Loadbalancer on embedded undertow. Supports sticky session over "AUTH_SESSION_ID" cookie and failover to different node when sticky node not available.
@@ -53,8 +54,9 @@ public class SimpleUndertowLoadBalancer {
private final String host;
private final int port;
- private final String nodesString;
+ private final Map<String, URI> backendNodes;
private Undertow undertow;
+ private LoadBalancingProxyClient lb;
public static void main(String[] args) throws Exception {
@@ -77,15 +79,14 @@ public class SimpleUndertowLoadBalancer {
public SimpleUndertowLoadBalancer(String host, int port, String nodesString) {
this.host = host;
this.port = port;
- this.nodesString = nodesString;
- log.infof("Keycloak nodes: %s", nodesString);
+ this.backendNodes = parseNodes(nodesString);
+ log.infof("Keycloak nodes: %s", backendNodes);
}
public void start() {
- Map<String, String> nodes = parseNodes(nodesString);
try {
- HttpHandler proxyHandler = createHandler(nodes);
+ HttpHandler proxyHandler = createHandler();
undertow = Undertow.builder()
.addHttpListener(port, host)
@@ -104,24 +105,51 @@ public class SimpleUndertowLoadBalancer {
undertow.stop();
}
+ public void enableAllBackendNodes() {
+ backendNodes.forEach((route, uri) -> {
+ lb.removeHost(uri);
+ lb.addHost(uri, route);
+ });
+ }
- static Map<String, String> parseNodes(String nodes) {
- String[] nodesArray = nodes.split(",");
- Map<String, String> result = new HashMap<>();
+ public void disableAllBackendNodes() {
+ log.debugf("Load balancer: disabling all nodes");
+ backendNodes.values().forEach(lb::removeHost);
+ }
- for (String nodeStr : nodesArray) {
- String[] node = nodeStr.trim().split("=");
- if (node.length != 2) {
- throw new IllegalArgumentException("Illegal node format in the configuration: " + nodeStr);
- }
- result.put(node[0].trim(), node[1].trim());
+ public void enableBackendNodeByName(String nodeName) {
+ URI uri = backendNodes.get(nodeName);
+ if (uri == null) {
+ throw new IllegalArgumentException("Invalid node: " + nodeName);
+ }
+ log.debugf("Load balancer: enabling node %s", nodeName);
+ lb.addHost(uri, nodeName);
+ }
+
+ public void disableBackendNodeByName(String nodeName) {
+ URI uri = backendNodes.get(nodeName);
+ if (uri == null) {
+ throw new IllegalArgumentException("Invalid node: " + nodeName);
+ }
+ log.debugf("Load balancer: disabling node %s", nodeName);
+ lb.removeHost(uri);
+ }
+
+ static Map<String, URI> parseNodes(String nodes) {
+ StringTokenizer st = new StringTokenizer(nodes, ",");
+ Map<String, URI> result = new LinkedHashMap<>();
+
+ while (st.hasMoreElements()) {
+ String nodeStr = st.nextToken();
+ String[] node = nodeStr.trim().split("=", 2);
+ result.put(node[0].trim(), URI.create(node[1].trim()));
}
return result;
}
- private HttpHandler createHandler(Map<String, String> backendNodes) throws Exception {
+ private HttpHandler createHandler() throws Exception {
// TODO: configurable options if needed
String sessionCookieNames = AuthenticationSessionManager.AUTH_SESSION_ID;
@@ -133,15 +161,7 @@ public class SimpleUndertowLoadBalancer {
int connectionIdleTimeout = 60;
int maxRetryAttempts = backendNodes.size() - 1;
- final LoadBalancingProxyClient lb = new CustomLoadBalancingClient(new ExclusivityChecker() {
-
- @Override
- public boolean isExclusivityRequired(HttpServerExchange exchange) {
- //we always create a new connection for upgrade requests
- return exchange.getRequestHeaders().contains(Headers.UPGRADE);
- }
-
- }, maxRetryAttempts)
+ lb = new CustomLoadBalancingClient(exchange -> exchange.getRequestHeaders().contains(Headers.UPGRADE), maxRetryAttempts)
.setConnectionsPerThread(connectionsPerThread)
.setMaxQueueSize(requestQueueSize)
.setSoftMaxConnectionsPerThread(cachedConnectionsPerThread)
@@ -152,13 +172,10 @@ public class SimpleUndertowLoadBalancer {
lb.addSessionCookieName(id);
}
- for (Map.Entry<String, String> node : backendNodes.entrySet()) {
- String route = node.getKey();
- URI uri = new URI(node.getValue());
-
+ backendNodes.forEach((route, uri) -> {
lb.addHost(uri, route);
- log.infof("Added host: %s, route: %s", uri.toString(), route);
- }
+ log.debugf("Added host: %s, route: %s", uri.toString(), route);
+ });
ProxyHandler handler = new ProxyHandler(lb, maxTime, ResponseCodeHandler.HANDLE_404);
return handler;
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerConfiguration.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerConfiguration.java
index 3a0312c..12d3564 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerConfiguration.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerConfiguration.java
@@ -19,13 +19,17 @@ package org.keycloak.testsuite.arquillian.undertow.lb;
import org.arquillian.undertow.UndertowContainerConfiguration;
import org.jboss.arquillian.container.spi.ConfigurationException;
+import org.jboss.logging.Logger;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SimpleUndertowLoadBalancerConfiguration extends UndertowContainerConfiguration {
+ protected static final Logger log = Logger.getLogger(SimpleUndertowLoadBalancerConfiguration.class);
+
private String nodes = SimpleUndertowLoadBalancer.DEFAULT_NODES;
+ private int bindHttpPortOffset = 0;
public String getNodes() {
return nodes;
@@ -35,6 +39,14 @@ public class SimpleUndertowLoadBalancerConfiguration extends UndertowContainerCo
this.nodes = nodes;
}
+ public int getBindHttpPortOffset() {
+ return bindHttpPortOffset;
+ }
+
+ public void setBindHttpPortOffset(int bindHttpPortOffset) {
+ this.bindHttpPortOffset = bindHttpPortOffset;
+ }
+
@Override
public void validate() throws ConfigurationException {
super.validate();
@@ -44,5 +56,11 @@ public class SimpleUndertowLoadBalancerConfiguration extends UndertowContainerCo
} catch (Exception e) {
throw new ConfigurationException(e);
}
+
+ int basePort = getBindHttpPort();
+ int newPort = basePort + bindHttpPortOffset;
+ setBindHttpPort(newPort);
+ log.info("SimpleUndertowLoadBalancer will listen on port: " + newPort);
+
}
}
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerContainer.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerContainer.java
index 4b24c15..a7f1ffe 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerContainer.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/lb/SimpleUndertowLoadBalancerContainer.java
@@ -25,13 +25,14 @@ import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaD
import org.jboss.logging.Logger;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
+import org.keycloak.testsuite.arquillian.LoadBalancerController;
/**
* Arquillian container over {@link SimpleUndertowLoadBalancer}
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class SimpleUndertowLoadBalancerContainer implements DeployableContainer<SimpleUndertowLoadBalancerConfiguration> {
+public class SimpleUndertowLoadBalancerContainer implements DeployableContainer<SimpleUndertowLoadBalancerConfiguration>, LoadBalancerController {
private static final Logger log = Logger.getLogger(SimpleUndertowLoadBalancerContainer.class);
@@ -84,4 +85,24 @@ public class SimpleUndertowLoadBalancerContainer implements DeployableContainer<
public void undeploy(Descriptor descriptor) throws DeploymentException {
throw new UnsupportedOperationException("Not implemented");
}
+
+ @Override
+ public void enableAllBackendNodes() {
+ this.container.enableAllBackendNodes();
+ }
+
+ @Override
+ public void disableAllBackendNodes() {
+ this.container.disableAllBackendNodes();
+ }
+
+ @Override
+ public void enableBackendNodeByName(String nodeName) {
+ this.container.enableBackendNodeByName(nodeName);
+ }
+
+ @Override
+ public void disableBackendNodeByName(String nodeName) {
+ this.container.disableBackendNodeByName(nodeName);
+ }
}
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SetSystemProperty.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SetSystemProperty.java
index 86a7e2e..e64b1b2 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SetSystemProperty.java
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/SetSystemProperty.java
@@ -17,13 +17,15 @@
package org.keycloak.testsuite.arquillian.undertow;
+import java.io.Closeable;
+
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-class SetSystemProperty {
+class SetSystemProperty implements Closeable {
- private String name;
- private String oldValue;
+ private final String name;
+ private final String oldValue;
public SetSystemProperty(String name, String value) {
this.name = name;
@@ -50,4 +52,9 @@ class SetSystemProperty {
}
}
+ @Override
+ public void close() {
+ revert();
+ }
+
}
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/assembly.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/assembly.xml
new file mode 100644
index 0000000..d623853
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/assembly.xml
@@ -0,0 +1,46 @@
+<!--
+ ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+ ~ and other contributors as indicated by the @author tags.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<assembly>
+
+ <id>${cache.server.jboss}</id>
+
+ <formats>
+ <format>zip</format>
+ </formats>
+
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <fileSets>
+ <fileSet>
+ <directory>${cache.server.jboss.home}</directory>
+ <outputDirectory>cache-server-${cache.server}</outputDirectory>
+ <excludes>
+ <exclude>**/*.sh</exclude>
+ </excludes>
+ </fileSet>
+ <fileSet>
+ <directory>${cache.server.jboss.home}</directory>
+ <outputDirectory>cache-server-${cache.server}</outputDirectory>
+ <includes>
+ <include>**/*.sh</include>
+ </includes>
+ <fileMode>0755</fileMode>
+ </fileSet>
+ </fileSets>
+
+</assembly>
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-remote-store.xsl b/testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-remote-store.xsl
new file mode 100644
index 0000000..0d6f833
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/common/add-keycloak-remote-store.xsl
@@ -0,0 +1,42 @@
+<!--
+ ~ 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.
+ -->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xalan="http://xml.apache.org/xalan"
+ version="2.0"
+ exclude-result-prefixes="xalan">
+
+ <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:variable name="nsCacheServer" select="'urn:infinispan:server:core:'"/>
+
+ <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsCacheServer)]
+ /*[local-name()='cache-container' and starts-with(namespace-uri(), $nsCacheServer) and @name='local']">
+ <xsl:copy>
+ <xsl:apply-templates select="@* | node()" />
+ <local-cache name="work" start="EAGER" batching="false" />
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/common/io.xsl b/testsuite/integration-arquillian/servers/cache-server/jboss/common/io.xsl
new file mode 100644
index 0000000..03d518a
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/common/io.xsl
@@ -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.
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xalan="http://xml.apache.org/xalan"
+ version="2.0"
+ exclude-result-prefixes="xalan">
+
+ <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:param name="worker.io-threads" select="'16'"/>
+ <xsl:param name="worker.task-max-threads" select="'128'"/>
+
+ <!--set worker threads-->
+ <xsl:template match="//*[local-name()='worker' and @name='default']">
+ <worker name="default" io-threads="{$worker.io-threads}" task-max-threads="{$worker.task-max-threads}" />
+ </xsl:template>
+
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/pom.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/pom.xml
new file mode 100644
index 0000000..3ac23ac
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+~ and other contributors as indicated by the @author tags.
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~ http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-servers-cache-server-jboss</artifactId>
+ <version>3.2.0.CR1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>integration-arquillian-servers-cache-server-infinispan</artifactId>
+ <packaging>pom</packaging>
+ <name>Cache Server - JBoss - Infinispan</name>
+
+ <properties>
+ <cache.server>infinispan</cache.server>
+ <cache.server.container>cache-server-${cache.server}</cache.server.container>
+ <cache.server.home>${containers.home}/${cache.server.container}</cache.server.home>
+
+ <cache.server.jboss.groupId>org.infinispan.server</cache.server.jboss.groupId>
+ <cache.server.jboss.artifactId>infinispan-server</cache.server.jboss.artifactId>
+ <cache.server.jboss.version>${infinispan.version}</cache.server.jboss.version>
+ <cache.server.jboss.unpacked.folder.name>${cache.server.jboss.artifactId}-${infinispan.version}</cache.server.jboss.unpacked.folder.name>
+
+ <cache.server.worker.io-threads>${cache.default.worker.io-threads}</cache.server.worker.io-threads>
+ <cache.server.worker.task-max-threads>${cache.default.worker.task-max-threads}</cache.server.worker.task-max-threads>
+ </properties>
+
+</project>
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/src/.dont-delete b/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/src/.dont-delete
new file mode 100644
index 0000000..c969aca
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/src/.dont-delete
@@ -0,0 +1 @@
+This file is to mark this Maven project as a valid option for building cache server artifact
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/pom.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/pom.xml
new file mode 100644
index 0000000..8fa5902
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+~ and other contributors as indicated by the @author tags.
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~ http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-servers-cache-server-jboss</artifactId>
+ <version>3.2.0.CR1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>integration-arquillian-servers-cache-server-jdg</artifactId>
+ <packaging>pom</packaging>
+ <name>Cache Server - JDG</name>
+
+ <properties>
+ <cache.server>jdg</cache.server>
+ <cache.server.container>cache-server-${cache.server}</cache.server.container>
+ <cache.server.home>${containers.home}/${cache.server.container}</cache.server.home>
+
+ <cache.server.jboss.groupId>org.infinispan.server</cache.server.jboss.groupId>
+ <cache.server.jboss.artifactId>infinispan-server</cache.server.jboss.artifactId>
+ <cache.server.jboss.version>${jdg.version}</cache.server.jboss.version>
+ <cache.server.jboss.unpacked.folder.name>${cache.server.jboss.artifactId}-${jdg.version}</cache.server.jboss.unpacked.folder.name>
+
+ <cache.server.worker.io-threads>${cache.default.worker.io-threads}</cache.server.worker.io-threads>
+ <cache.server.worker.task-max-threads>${cache.default.worker.task-max-threads}</cache.server.worker.task-max-threads>
+ </properties>
+
+</project>
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/src/.dont-delete b/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/src/.dont-delete
new file mode 100644
index 0000000..c969aca
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/src/.dont-delete
@@ -0,0 +1 @@
+This file is to mark this Maven project as a valid option for building cache server artifact
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/pom.xml
new file mode 100644
index 0000000..8c7f830
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/pom.xml
@@ -0,0 +1,216 @@
+<?xml version="1.0"?>
+<!--
+~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+~ and other contributors as indicated by the @author tags.
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~ http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-servers-cache-server</artifactId>
+ <version>3.2.0.CR1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>integration-arquillian-servers-cache-server-jboss</artifactId>
+ <packaging>pom</packaging>
+ <name>Cache Server - JBoss Family</name>
+
+ <properties>
+ <common.resources>${project.parent.basedir}/common</common.resources>
+ <assembly.xml>${project.parent.basedir}/assembly.xml</assembly.xml>
+ <cache.server.jboss.home>${containers.home}/${cache.server.jboss.unpacked.folder.name}</cache.server.jboss.home>
+ <security.xslt>security.xsl</security.xslt>
+ </properties>
+
+ <profiles>
+
+ <profile>
+ <id>cache-server-jboss-submodules</id>
+ <activation>
+ <file>
+ <exists>src</exists>
+ </file>
+ </activation>
+ <build>
+ <plugins>
+
+ <plugin>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <requireProperty>
+ <property>cache.server</property>
+ <property>cache.server.jboss.groupId</property>
+ <property>cache.server.jboss.artifactId</property>
+ <property>cache.server.jboss.version</property>
+ <property>cache.server.jboss.unpacked.folder.name</property>
+ </requireProperty>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack-cache-server</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>${cache.server.jboss.groupId}</groupId>
+ <artifactId>${cache.server.jboss.artifactId}</artifactId>
+ <version>${cache.server.jboss.version}</version>
+ <type>zip</type>
+ <classifier>bin</classifier>
+ <outputDirectory>${containers.home}</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>configure-adapter-debug-log</id>
+ <phase>process-test-resources</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <transformationSet>
+ <dir>${cache.server.jboss.home}/standalone/configuration</dir>
+ <includes>
+ <include>standalone.xml</include>
+ </includes>
+ <stylesheet>${common.resources}/add-keycloak-remote-store.xsl</stylesheet>
+ <outputDir>${cache.server.jboss.home}/standalone/configuration</outputDir>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ <execution>
+ <id>io-worker-threads</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <transformationSet>
+ <dir>${cache.server.jboss.home}/standalone/configuration</dir>
+ <includes>
+ <include>standalone.xml</include>
+ <include>standalone-ha.xml</include>
+ </includes>
+ <stylesheet>${common.resources}/io.xsl</stylesheet>
+ <outputDir>${cache.server.jboss.home}/standalone/configuration</outputDir>
+ <parameters>
+ <parameter>
+ <name>worker.io-threads</name>
+ <value>${cache.server.worker.io-threads}</value>
+ </parameter>
+ <parameter>
+ <name>worker.task-max-threads</name>
+ <value>${cache.server.worker.task-max-threads}</value>
+ </parameter>
+ </parameters>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>enable-jboss-mgmt-admin</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${cache.server.jboss.home}/standalone/configuration</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${common.resources}</directory>
+ <includes>
+ <include>mgmt-users.properties</include>
+ </includes>
+ </resource>
+ </resources>
+ <overwrite>true</overwrite>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>create-zip</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+ <descriptor>${assembly.xml}</descriptor>
+ </descriptors>
+ <appendAssemblyId>false</appendAssemblyId>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>cache-server-infinispan</id>
+ <modules>
+ <module>infinispan</module>
+ </modules>
+ </profile>
+ <profile>
+ <id>cache-server-jdg</id>
+ <modules>
+ <module>jdg</module>
+ </modules>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/testsuite/integration-arquillian/servers/cache-server/pom.xml b/testsuite/integration-arquillian/servers/cache-server/pom.xml
new file mode 100644
index 0000000..3f5a5a0
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/cache-server/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<!--
+~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+~ and other contributors as indicated by the @author tags.
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~ http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-servers</artifactId>
+ <version>3.2.0.CR1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>integration-arquillian-servers-cache-server</artifactId>
+ <packaging>pom</packaging>
+ <name>Cache Server</name>
+
+ <properties>
+ <auth.server.worker.io-threads>${jboss.default.worker.io-threads}</auth.server.worker.io-threads>
+ <auth.server.worker.task-max-threads>${jboss.default.worker.task-max-threads}</auth.server.worker.task-max-threads>
+ </properties>
+
+ <modules>
+ <module>jboss</module>
+ </modules>
+
+</project>
diff --git a/testsuite/integration-arquillian/servers/pom.xml b/testsuite/integration-arquillian/servers/pom.xml
index a05c52c..0b7e899 100644
--- a/testsuite/integration-arquillian/servers/pom.xml
+++ b/testsuite/integration-arquillian/servers/pom.xml
@@ -33,9 +33,9 @@
<script.suffix>sh</script.suffix>
<!--app container versions-->
+ <wildfly10.version>10.1.0.Final</wildfly10.version>
<wildfly9.version>9.0.2.Final</wildfly9.version>
<wildfly8.version>8.2.1.Final</wildfly8.version>
- <eap.version>7.0.5.GA-redhat-2</eap.version>
<eap6.version>7.5.14.Final-redhat-2</eap6.version>
<jboss.as.version>7.1.1.Final</jboss.as.version>
<tomcat7.version>7.0.68</tomcat7.version>
@@ -46,13 +46,21 @@
<!--<fuse62.version>6.2.0.redhat-133</fuse62.version>-->
<fuse62.version>6.2.1.redhat-084</fuse62.version>
+ <!-- cache server versions -->
+ <infinispan.version>9.0.1.Final</infinispan.version>
+ <jdg.version>8.4.0.Final-redhat-2</jdg.version><!-- JDG 7.1.0 -->
+
<jboss.default.worker.io-threads>16</jboss.default.worker.io-threads>
<jboss.default.worker.task-max-threads>128</jboss.default.worker.task-max-threads>
+
+ <cache.default.worker.io-threads>2</cache.default.worker.io-threads>
+ <cache.default.worker.task-max-threads>4</cache.default.worker.task-max-threads>
</properties>
<modules>
<module>auth-server</module>
<module>app-server</module>
+ <module>cache-server</module>
</modules>
<profiles>
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index 7c52025..caa76aa 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -40,6 +40,7 @@
<exclude.client>-</exclude.client>
<!--exclude cluster tests by default, enabled by 'auth-server-*-cluster' profiles in tests/pom.xml-->
<exclude.cluster>**/cluster/**/*Test.java</exclude.cluster>
+ <exclude.crossdc>**/crossdc/**/*Test.java</exclude.crossdc>
<!-- exclude x509 tests by default, enabled by 'ssl' profile -->
<exclude.x509>**/x509/*Test.java</exclude.x509>
<!-- exclude undertow adapter tests. They can be added by -Dtest=org.keycloak.testsuite.adapter.undertow.**.*Test -->
@@ -125,6 +126,7 @@
<exclude>${exclude.account}</exclude>
<exclude>${exclude.client}</exclude>
<exclude>${exclude.cluster}</exclude>
+ <exclude>${exclude.crossdc}</exclude>
<exclude>${exclude.undertow.adapter}</exclude>
<exclude>${exclude.x509}</exclude>
</excludes>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAutodetectServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAutodetectServlet.java
new file mode 100644
index 0000000..9e7e3b7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAutodetectServlet.java
@@ -0,0 +1,39 @@
+/*
+ * 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 SalesPostAutodetectServlet extends SAMLServlet {
+ public static final String DEPLOYMENT_NAME = "sales-post-autodetect";
+
+ @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/arquillian/annotation/LoadBalancer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/LoadBalancer.java
new file mode 100644
index 0000000..a3652a1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/annotation/LoadBalancer.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.arquillian.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author hmlnarik
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.FIELD })
+public @interface LoadBalancer {
+ String value() default "";
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
index 7e7ee6f..94293dd 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
@@ -16,7 +16,6 @@
*/
package org.keycloak.testsuite.arquillian;
-import org.jboss.arquillian.container.spi.Container;
import org.jboss.arquillian.container.spi.ContainerRegistry;
import org.jboss.arquillian.container.spi.event.StartContainer;
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
@@ -41,10 +40,11 @@ import org.keycloak.testsuite.util.OAuthClient;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
-import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
import javax.ws.rs.NotFoundException;
/**
@@ -68,8 +68,18 @@ public class AuthServerTestEnricher {
private static final String AUTH_SERVER_CONTAINER_PROPERTY = "auth.server.container";
public static final String AUTH_SERVER_CONTAINER = System.getProperty(AUTH_SERVER_CONTAINER_PROPERTY, AUTH_SERVER_CONTAINER_DEFAULT);
+ private static final String AUTH_SERVER_BACKEND_DEFAULT = AUTH_SERVER_CONTAINER + "-backend";
+ private static final String AUTH_SERVER_BACKEND_PROPERTY = "auth.server.backend";
+ public static final String AUTH_SERVER_BACKEND = System.getProperty(AUTH_SERVER_BACKEND_PROPERTY, AUTH_SERVER_BACKEND_DEFAULT);
+
+ private static final String AUTH_SERVER_BALANCER_DEFAULT = "auth-server-balancer";
+ private static final String AUTH_SERVER_BALANCER_PROPERTY = "auth.server.balancer";
+ public static final String AUTH_SERVER_BALANCER = System.getProperty(AUTH_SERVER_BALANCER_PROPERTY, AUTH_SERVER_BALANCER_DEFAULT);
+
private static final String AUTH_SERVER_CLUSTER_PROPERTY = "auth.server.cluster";
public static final boolean AUTH_SERVER_CLUSTER = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CLUSTER_PROPERTY, "false"));
+ private static final String AUTH_SERVER_CROSS_DC_PROPERTY = "auth.server.crossdc";
+ public static final boolean AUTH_SERVER_CROSS_DC = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CROSS_DC_PROPERTY, "false"));
private static final boolean AUTH_SERVER_UNDERTOW_CLUSTER = Boolean.parseBoolean(System.getProperty("auth.server.undertow.cluster", "false"));
@@ -106,46 +116,82 @@ public class AuthServerTestEnricher {
}
public void initializeSuiteContext(@Observes(precedence = 2) BeforeSuite event) {
-
- Set<ContainerInfo> containers = new LinkedHashSet<>();
- for (Container c : containerRegistry.get().getContainers()) {
- containers.add(new ContainerInfo(c));
- }
+ Set<ContainerInfo> containers = containerRegistry.get().getContainers().stream()
+ .map(ContainerInfo::new)
+ .collect(Collectors.toSet());
suiteContext = new SuiteContext(containers);
- String authServerFrontend = null;
-
- if (AUTH_SERVER_CLUSTER) {
- // if cluster mode enabled, load-balancer is the frontend
- for (ContainerInfo c : containers) {
- if (c.getQualifier().startsWith("auth-server-balancer")) {
- authServerFrontend = c.getQualifier();
- }
+ if (AUTH_SERVER_CROSS_DC) {
+ // if cross-dc mode enabled, load-balancer is the frontend of datacenter cluster
+ containers.stream()
+ .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_BALANCER + "-cross-dc"))
+ .forEach(c -> {
+ String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
+ String dcString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("dataCenter", "0");
+ updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
+ suiteContext.addAuthServerInfo(Integer.valueOf(dcString), c);
+ });
+
+ if (suiteContext.getDcAuthServerInfo().isEmpty()) {
+ throw new IllegalStateException("Not found frontend container (load balancer): " + AUTH_SERVER_BALANCER);
}
-
- if (authServerFrontend != null) {
- log.info("Using frontend container: " + authServerFrontend);
- } else {
- throw new IllegalStateException("Not found frontend container");
+ if (suiteContext.getDcAuthServerInfo().stream().anyMatch(Objects::isNull)) {
+ throw new IllegalStateException("Frontend container (load balancer) misconfiguration");
}
- } else {
- authServerFrontend = AUTH_SERVER_CONTAINER; // single-node mode
- }
- String authServerBackend = AUTH_SERVER_CONTAINER + "-backend";
- int backends = 0;
- for (ContainerInfo container : suiteContext.getContainers()) {
- // frontend
- if (container.getQualifier().equals(authServerFrontend)) {
- updateWithAuthServerInfo(container);
- suiteContext.setAuthServerInfo(container);
+ containers.stream()
+ .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_CONTAINER + "-cross-dc-"))
+ .forEach(c -> {
+ String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
+ String dcString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("dataCenter", "0");
+ updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
+ suiteContext.addAuthServerBackendsInfo(Integer.valueOf(dcString), c);
+ });
+
+ if (suiteContext.getDcAuthServerInfo().isEmpty()) {
+ throw new RuntimeException(String.format("No auth server container matching '%s' found in arquillian.xml.", AUTH_SERVER_BACKEND));
}
- // backends
- if (AUTH_SERVER_CLUSTER && container.getQualifier().startsWith(authServerBackend)) {
- updateWithAuthServerInfo(container, ++backends);
- suiteContext.getAuthServerBackendsInfo().add(container);
+ if (suiteContext.getDcAuthServerBackendsInfo().stream().anyMatch(Objects::isNull)) {
+ throw new IllegalStateException("Frontend container (load balancer) misconfiguration");
}
+ if (suiteContext.getDcAuthServerBackendsInfo().stream().anyMatch(List::isEmpty)) {
+ throw new RuntimeException(String.format("Some data center has no auth server container matching '%s' defined in arquillian.xml.", AUTH_SERVER_BACKEND));
+ }
+
+ log.info("Using frontend containers: " + this.suiteContext.getDcAuthServerInfo().stream()
+ .map(ContainerInfo::getQualifier)
+ .collect(Collectors.joining(", ")));
+ } else if (AUTH_SERVER_CLUSTER) {
+ // if cluster mode enabled, load-balancer is the frontend
+ ContainerInfo container = containers.stream()
+ .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_BALANCER))
+ .findAny()
+ .orElseThrow(() -> new IllegalStateException("Not found frontend container: " + AUTH_SERVER_BALANCER));
+ updateWithAuthServerInfo(container);
+ suiteContext.setAuthServerInfo(container);
+
+ containers.stream()
+ .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_BACKEND))
+ .forEach(c -> {
+ String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
+ updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
+ suiteContext.addAuthServerBackendsInfo(0, c);
+ });
+
+ if (suiteContext.getAuthServerBackendsInfo().isEmpty()) {
+ throw new RuntimeException(String.format("No auth server container matching '%s' found in arquillian.xml.", AUTH_SERVER_BACKEND));
+ }
+
+ log.info("Using frontend container: " + container.getQualifier());
+ } else {
+ // frontend-only
+ ContainerInfo container = containers.stream()
+ .filter(c -> c.getQualifier().startsWith(AUTH_SERVER_CONTAINER))
+ .findAny()
+ .orElseThrow(() -> new IllegalStateException("Not found frontend container: " + AUTH_SERVER_CONTAINER));
+ updateWithAuthServerInfo(container);
+ suiteContext.setAuthServerInfo(container);
}
// Setup with 2 undertow backend nodes and no loadbalancer.
@@ -153,14 +199,6 @@ public class AuthServerTestEnricher {
// suiteContext.setAuthServerInfo(suiteContext.getAuthServerBackendsInfo().get(0));
// }
- // validate auth server setup
- if (suiteContext.getAuthServerInfo() == null) {
- throw new RuntimeException(String.format("No auth server container matching '%s' found in arquillian.xml.", authServerFrontend));
- }
- if (AUTH_SERVER_CLUSTER && suiteContext.getAuthServerBackendsInfo().isEmpty()) {
- throw new RuntimeException(String.format("No auth server container matching '%sN' found in arquillian.xml.", authServerBackend));
- }
-
if (START_MIGRATION_CONTAINER) {
// init migratedAuthServerInfo
for (ContainerInfo container : suiteContext.getContainers()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java
index 68c9f17..37abe7b 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainerInfo.java
@@ -5,6 +5,8 @@ import org.jboss.arquillian.container.spi.Container;
import java.net.URL;
import java.util.Map;
import java.util.Objects;
+import java.util.stream.Stream;
+import org.jboss.arquillian.container.spi.Container.State;
/**
*
@@ -97,4 +99,12 @@ public class ContainerInfo {
other.arquillianContainer.getContainerConfiguration().getContainerName());
}
+ public boolean isStarted() {
+ return arquillianContainer.getState() == State.STARTED;
+ }
+
+ public boolean isManual() {
+ return Objects.equals(arquillianContainer.getContainerConfiguration().getMode(), "manual");
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/RegistryCreator.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/RegistryCreator.java
index 99b6772..a2b6ea7 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/RegistryCreator.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/RegistryCreator.java
@@ -110,6 +110,8 @@ public class RegistryCreator {
if (isClassPresent(getAdapterImplClassValue(containerDef))) {
return DeployableContainer.class.isAssignableFrom(
loadClass(getAdapterImplClassValue(containerDef)));
+ } else {
+ log.warn("Cannot load adapterImpl class for " + containerDef.getContainerName());
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
index 7b9c139..7757b07 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
@@ -35,6 +35,7 @@ import org.keycloak.testsuite.arquillian.h2.H2TestEnricher;
import org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer;
import org.keycloak.testsuite.arquillian.migration.MigrationTestExecutionDecider;
import org.keycloak.testsuite.arquillian.provider.AdminClientProvider;
+import org.keycloak.testsuite.arquillian.provider.LoadBalancerControllerProvider;
import org.keycloak.testsuite.arquillian.provider.OAuthClientProvider;
import org.keycloak.testsuite.arquillian.provider.SuiteContextProvider;
import org.keycloak.testsuite.arquillian.provider.TestContextProvider;
@@ -57,7 +58,8 @@ public class KeycloakArquillianExtension implements LoadableExtension {
.service(ResourceProvider.class, SuiteContextProvider.class)
.service(ResourceProvider.class, TestContextProvider.class)
.service(ResourceProvider.class, AdminClientProvider.class)
- .service(ResourceProvider.class, OAuthClientProvider.class);
+ .service(ResourceProvider.class, OAuthClientProvider.class)
+ .service(ResourceProvider.class, LoadBalancerControllerProvider.class);
builder
.service(DeploymentScenarioGenerator.class, DeploymentTargetModifier.class)
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/LoadBalancerControllerProvider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/LoadBalancerControllerProvider.java
new file mode 100644
index 0000000..4f99feb
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/LoadBalancerControllerProvider.java
@@ -0,0 +1,67 @@
+package org.keycloak.testsuite.arquillian.provider;
+
+import org.keycloak.testsuite.arquillian.annotation.LoadBalancer;
+import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.jboss.arquillian.container.spi.event.KillContainer;
+import org.jboss.arquillian.container.spi.event.StartContainer;
+import org.jboss.arquillian.container.spi.event.StopContainer;
+import org.jboss.arquillian.core.api.Instance;
+import org.jboss.arquillian.core.api.annotation.Inject;
+import org.jboss.arquillian.core.api.annotation.Observes;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
+import org.keycloak.testsuite.arquillian.LoadBalancerController;
+import org.jboss.arquillian.container.spi.Container;
+import org.jboss.arquillian.container.spi.ContainerRegistry;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class LoadBalancerControllerProvider implements ResourceProvider {
+
+ @Inject
+ private Instance<ContainerRegistry> registry;
+
+ @Override
+ public boolean canProvide(Class<?> type) {
+ return type.equals(LoadBalancerController.class);
+ }
+
+ @Override
+ public Object lookup(ArquillianResource resource, Annotation... qualifiers) {
+ String balancerName = null;
+
+ // Check for the presence of possible qualifiers
+ for (Annotation a : qualifiers) {
+ Class<? extends Annotation> annotationType = a.annotationType();
+
+ if (annotationType.equals(LoadBalancer.class)) {
+ balancerName = ((LoadBalancer) a).value();
+ }
+ }
+
+ ContainerRegistry reg = registry.get();
+ Container container = null;
+ if (balancerName == null || "".equals(balancerName.trim())) {
+ if (reg.getContainers().size() == 1) {
+ container = reg.getContainers().get(0);
+ } else {
+ throw new IllegalArgumentException("Invalid load balancer configuration request - need to specify load balancer name in @LoadBalancerController");
+ }
+ } else {
+ container = reg.getContainer(balancerName);
+ }
+
+ if (container == null) {
+ throw new IllegalArgumentException("Invalid load balancer configuration - load balancer not found: '" + balancerName + "'");
+ }
+ if (! (container.getDeployableContainer() instanceof LoadBalancerController)) {
+ throw new IllegalArgumentException("Invalid load balancer configuration - container " + container.getName() + " is not a load balancer");
+ }
+
+ return container.getDeployableContainer();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
index fab303a..8a6d300 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
@@ -24,6 +24,7 @@ import java.util.Set;
import org.keycloak.testsuite.arquillian.migration.MigrationContext;
+import java.util.LinkedList;
import static org.keycloak.testsuite.util.MailServerConfiguration.FROM;
import static org.keycloak.testsuite.util.MailServerConfiguration.HOST;
import static org.keycloak.testsuite.util.MailServerConfiguration.PORT;
@@ -36,8 +37,8 @@ public final class SuiteContext {
private final Set<ContainerInfo> container;
- private ContainerInfo authServerInfo;
- private final List<ContainerInfo> authServerBackendsInfo = new ArrayList<>();
+ private List<ContainerInfo> authServerInfo = new LinkedList<>();
+ private final List<List<ContainerInfo>> authServerBackendsInfo = new ArrayList<>();
private ContainerInfo migratedAuthServerInfo;
private final MigrationContext migrationContext = new MigrationContext();
@@ -72,17 +73,48 @@ public final class SuiteContext {
}
public ContainerInfo getAuthServerInfo() {
+ return getAuthServerInfo(0);
+ }
+
+ public ContainerInfo getAuthServerInfo(int dcIndex) {
+ return authServerInfo.get(dcIndex);
+ }
+
+ public List<ContainerInfo> getDcAuthServerInfo() {
return authServerInfo;
}
public void setAuthServerInfo(ContainerInfo authServerInfo) {
- this.authServerInfo = authServerInfo;
+ this.authServerInfo = new LinkedList<>();
+ this.authServerInfo.add(authServerInfo);
+ }
+
+ public void addAuthServerInfo(int dcIndex, ContainerInfo serverInfo) {
+ while (dcIndex >= authServerInfo.size()) {
+ authServerInfo.add(null);
+ }
+ this.authServerInfo.set(dcIndex, serverInfo);
}
public List<ContainerInfo> getAuthServerBackendsInfo() {
+ return getAuthServerBackendsInfo(0);
+ }
+
+ public List<ContainerInfo> getAuthServerBackendsInfo(int dcIndex) {
+ return authServerBackendsInfo.get(dcIndex);
+ }
+
+ public List<List<ContainerInfo>> getDcAuthServerBackendsInfo() {
return authServerBackendsInfo;
}
+ public void addAuthServerBackendsInfo(int dcIndex, ContainerInfo container) {
+ while (dcIndex >= authServerBackendsInfo.size()) {
+ authServerBackendsInfo.add(new LinkedList<>());
+ }
+ authServerBackendsInfo.get(dcIndex).add(container);
+ }
+
public ContainerInfo getMigratedAuthServerInfo() {
return migratedAuthServerInfo;
}
@@ -96,7 +128,11 @@ public final class SuiteContext {
}
public boolean isAuthServerCluster() {
- return !authServerBackendsInfo.isEmpty();
+ return ! authServerBackendsInfo.isEmpty();
+ }
+
+ public boolean isAuthServerCrossDc() {
+ return authServerBackendsInfo.size() > 1;
}
public boolean isAuthServerMigrationEnabled() {
@@ -113,19 +149,38 @@ public final class SuiteContext {
@Override
public String toString() {
- String containers = "Auth server: " + (isAuthServerCluster() ? "\nFrontend: " : "")
- + authServerInfo.getQualifier() + "\n";
- for (ContainerInfo bInfo : getAuthServerBackendsInfo()) {
- containers += "Backend: " + bInfo + "\n";
+ StringBuilder sb = new StringBuilder("SUITE CONTEXT:\nAuth server: ");
+
+ if (isAuthServerCrossDc()) {
+ for (int i = 0; i < authServerInfo.size(); i ++) {
+ ContainerInfo frontend = this.authServerInfo.get(i);
+ sb.append("\nFrontend (dc=").append(i).append("): ").append(frontend.getQualifier()).append("\n");
+ }
+
+ for (int i = 0; i < authServerBackendsInfo.size(); i ++) {
+ int dcIndex = i;
+ getDcAuthServerBackendsInfo().get(i).forEach(bInfo -> sb.append("Backend (dc=").append(dcIndex).append("): ").append(bInfo).append("\n"));
+ }
+ } else if (isAuthServerCluster()) {
+ sb.append(isAuthServerCluster() ? "\nFrontend: " : "")
+ .append(getAuthServerInfo().getQualifier())
+ .append("\n");
+
+ getAuthServerBackendsInfo().forEach(bInfo -> sb.append(" Backend: ").append(bInfo).append("\n"));
+ } else {
+ sb.append(getAuthServerInfo().getQualifier())
+ .append("\n");
}
+
+
if (isAuthServerMigrationEnabled()) {
- containers += "Migrated from: " + System.getProperty("migrated.auth.server.version") + "\n";
+ sb.append("Migrated from: ").append(System.getProperty("migrated.auth.server.version")).append("\n");
}
+
if (isAdapterCompatTesting()) {
- containers += "Adapter backward compatibility testing mode!\n";
+ sb.append("Adapter backward compatibility testing mode!\n");
}
- return "SUITE CONTEXT:\n"
- + containers;
+ return sb.toString();
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java
index 946d0f5..4561c99 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingCacheResource.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.client.resources;
import java.util.Set;
+import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@@ -48,4 +49,8 @@ public interface TestingCacheResource {
@Produces(MediaType.APPLICATION_JSON)
int size();
+ @GET
+ @Path("/clear")
+ @Consumes(MediaType.TEXT_PLAIN)
+ void clear();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java
index e968c7d..dd5d6d8 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java
@@ -31,7 +31,7 @@ public class LogChecker {
private static final Logger log = Logger.getLogger(LogChecker.class);
- private static final String[] IGNORED = new String[] { ".*Jetty ALPN support not found.*" };
+ private static final String[] IGNORED = new String[] { ".*Jetty ALPN support not found.*", ".*org.keycloak.events.*" };
public static void checkServerLog(File logFile) throws IOException {
log.info(String.format("Checking server log: '%s'", logFile.getAbsolutePath()));
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SetSystemProperty.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SetSystemProperty.java
new file mode 100644
index 0000000..757a385
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SetSystemProperty.java
@@ -0,0 +1,60 @@
+/*
+ * 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.util;
+
+import java.io.Closeable;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SetSystemProperty implements Closeable {
+
+ private final String name;
+ private final String oldValue;
+
+ public SetSystemProperty(String name, String value) {
+ this.name = name;
+ this.oldValue = System.getProperty(name);
+
+ if (value == null) {
+ if (oldValue != null) {
+ System.getProperties().remove(name);
+ }
+ } else {
+ System.setProperty(name, value);
+ }
+ }
+
+ public void revert() {
+ String value = System.getProperty(name);
+
+ if (oldValue == null) {
+ if (value != null) {
+ System.getProperties().remove(name);
+ }
+ } else {
+ System.setProperty(name, oldValue);
+ }
+ }
+
+ @Override
+ public void close() {
+ revert();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java
index e20485c..2101c0b 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TestCleanup.java
@@ -71,6 +71,7 @@ public class TestCleanup {
if (componentIds == null) {
componentIds = new LinkedList<>();
}
+ if (componentId == null) return;
componentIds.add(componentId);
}
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 ecf48bf..420fad8 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
@@ -198,6 +198,9 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
@Page
private SAMLIDPInitiatedLogin samlidpInitiatedLoginPage;
+ @Page
+ protected SalesPostAutodetectServlet salesPostAutodetectServletPage;
+
public static final String FORBIDDEN_TEXT = "HTTP status code: 403";
@Deployment(name = BadClientSalesPostSigServlet.DEPLOYMENT_NAME)
@@ -325,6 +328,11 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
return samlServletDeployment(EmployeeServlet.DEPLOYMENT_NAME, "employee/WEB-INF/web.xml", SamlSPFacade.class, ServletTestUtils.class);
}
+ @Deployment(name = SalesPostAutodetectServlet.DEPLOYMENT_NAME)
+ protected static WebArchive salesPostAutodetect() {
+ return samlServletDeployment(SalesPostAutodetectServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+ }
+
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(loadRealm("/adapter-test/keycloak-saml/testsaml.json"));
@@ -1115,6 +1123,54 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
checkLoggedOut(differentCookieNameServletPage, testRealmSAMLPostLoginPage);
}
+ @Test
+ /* KEYCLOAK-4980 */
+ public void testAutodetectBearerOnly() throws Exception {
+ Client client = ClientBuilder.newClient();
+
+ // Do not redirect client to login page if it's an XHR
+ WebTarget target = client.target(salesPostAutodetectServletPage.toString());
+ Response response = target.request().header("X-Requested-With", "XMLHttpRequest").get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
+ // Do not redirect client to login page if it's a partial Faces request
+ response = target.request().header("Faces-Request", "partial/ajax").get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
+ // Do not redirect client to login page if it's a SOAP request
+ response = target.request().header("SOAPAction", "").get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
+ // Do not redirect client to login page if Accept header is missing
+ response = target.request().get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
+ // Do not redirect client to login page if client does not understand HTML reponses
+ response = target.request().header(HttpHeaders.ACCEPT, "application/json,text/xml").get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
+ // Redirect client to login page if it's not an XHR
+ response = target.request().header("X-Requested-With", "Dont-Know").header(HttpHeaders.ACCEPT, "*/*").get();
+ Assert.assertEquals(200, response.getStatus());
+ response.close();
+
+ // Redirect client to login page if client explicitely understands HTML responses
+ response = target.request().header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9").get();
+ Assert.assertEquals(200, response.getStatus());
+ response.close();
+
+ // Redirect client to login page if client understands all response types
+ response = target.request().header(HttpHeaders.ACCEPT, "*/*").get();
+ Assert.assertEquals(200, response.getStatus());
+ response.close();
+ client.close();
+ }
+
private URI getAuthServerSamlEndpoint(String realm) throws IllegalArgumentException, UriBuilderException {
return RealmsResource
.protocolUrl(UriBuilder.fromUri(getAuthServerRoot()))
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
index 244323b..ae122f6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
@@ -107,7 +107,9 @@ public abstract class AbstractClientTest extends AbstractAuthTest {
clientRep.setPublicClient(Boolean.FALSE);
clientRep.setAuthorizationServicesEnabled(Boolean.TRUE);
clientRep.setServiceAccountsEnabled(Boolean.TRUE);
- return createClient(clientRep);
+ String id = createClient(clientRep);
+ assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientResourcePath(id), ResourceType.AUTHORIZATION_RESOURCE_SERVER);
+ return id;
}
protected ClientRepresentation createOidcClientRep(String name) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ExportAuthorizationSettingsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ExportAuthorizationSettingsTest.java
new file mode 100644
index 0000000..6f5e65e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ExportAuthorizationSettingsTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.admin.client.authorization;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.util.ClientBuilder;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlasta Ramik</a>
+ */
+public class ExportAuthorizationSettingsTest extends AbstractAuthorizationTest {
+
+ //KEYCLOAK-4341
+ @Test
+ public void testResourceBasedPermission() throws Exception {
+ String permissionName = "resource-based-permission";
+
+ ClientResource clientResource = getClientResource();
+
+ enableAuthorizationServices();
+ AuthorizationResource authorizationResource = clientResource.authorization();
+
+ //get Default Resource
+ List<ResourceRepresentation> resources = authorizationResource.resources().findByName("Default Resource");
+ Assert.assertTrue(resources.size() == 1);
+ ResourceRepresentation resource = resources.get(0);
+
+ //get Default Policy
+ PolicyRepresentation policy = authorizationResource.policies().findByName("Default Policy");
+
+ //create Resource-based permission and add default policy/resource
+ ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+ permission.setName(permissionName);
+ permission.addPolicy(policy.getId());
+ permission.addResource(resource.getId());
+ Response create = authorizationResource.permissions().resource().create(permission);
+ try {
+ Assert.assertEquals(Status.CREATED, create.getStatusInfo());
+ } finally {
+ create.close();
+ }
+
+ //export authorization settings
+ ResourceServerRepresentation exportSettings = authorizationResource.exportSettings();
+
+ //check exported settings contains both resources/applyPolicies
+ boolean found = false;
+ for (PolicyRepresentation p : exportSettings.getPolicies()) {
+ if (p.getName().equals(permissionName)) {
+ found = true;
+ Assert.assertEquals("[\"Default Resource\"]", p.getConfig().get("resources"));
+ Assert.assertEquals("[\"Default Policy\"]", p.getConfig().get("applyPolicies"));
+ }
+ }
+ Assert.assertTrue("Permission \"role-based-permission\" was not found.", found);
+ }
+
+ //KEYCLOAK-4340
+ @Test
+ public void testRoleBasedPolicy() {
+ ClientResource clientResource = getClientResource();
+
+ enableAuthorizationServices();
+ AuthorizationResource authorizationResource = clientResource.authorization();
+
+ ClientRepresentation account = testRealmResource().clients().findByClientId("account").get(0);
+ RoleRepresentation role = testRealmResource().clients().get(account.getId()).roles().get("view-profile").toRepresentation();
+
+ PolicyRepresentation policy = new PolicyRepresentation();
+ policy.setName("role-based-policy");
+ policy.setType("role");
+ Map<String, String> config = new HashMap<>();
+ config.put("roles", "[{\"id\":\"" + role.getId() +"\"}]");
+ policy.setConfig(config);
+ Response create = authorizationResource.policies().create(policy);
+ try {
+ Assert.assertEquals(Status.CREATED, create.getStatusInfo());
+ } finally {
+ create.close();
+ }
+
+ //this call was messing up with DB, see KEYCLOAK-4340
+ authorizationResource.exportSettings();
+
+ //this call failed with NPE
+ authorizationResource.exportSettings();
+ }
+
+
+ //KEYCLOAK-4983
+ @Test
+ @Ignore
+ public void testRoleBasedPolicyWithMultipleRoles() {
+ ClientResource clientResource = getClientResource();
+
+ enableAuthorizationServices();
+ AuthorizationResource authorizationResource = clientResource.authorization();
+
+ testRealmResource().clients().create(ClientBuilder.create().clientId("test-client-1").defaultRoles("client-role").build()).close();
+ testRealmResource().clients().create(ClientBuilder.create().clientId("test-client-2").defaultRoles("client-role").build()).close();
+
+ ClientRepresentation client1 = getClientByClientId("test-client-1");
+ ClientRepresentation client2 = getClientByClientId("test-client-2");
+
+ RoleRepresentation role1 = testRealmResource().clients().get(client1.getId()).roles().get("client-role").toRepresentation();
+ RoleRepresentation role2 = testRealmResource().clients().get(client2.getId()).roles().get("client-role").toRepresentation();
+
+ PolicyRepresentation policy = new PolicyRepresentation();
+ policy.setName("role-based-policy");
+ policy.setType("role");
+ Map<String, String> config = new HashMap<>();
+ config.put("roles", "[{\"id\":\"" + role1.getId() +"\"},{\"id\":\"" + role2.getId() +"\"}]");
+ policy.setConfig(config);
+ Response create = authorizationResource.policies().create(policy);
+ try {
+ Assert.assertEquals(Status.CREATED, create.getStatusInfo());
+ } finally {
+ create.close();
+ }
+
+ //export authorization settings
+ ResourceServerRepresentation exportSettings = authorizationResource.exportSettings();
+
+ //delete test-resource-server client
+ testRealmResource().clients().get(clientResource.toRepresentation().getId()).remove();
+
+ //clear cache
+ testRealmResource().clearRealmCache();
+ //workaround for the fact that clearing realm cache doesn't clear authz cache
+ testingClient.testing("test").cache("authorization").clear();
+
+ //create new client
+ ClientRepresentation client = ClientBuilder.create()
+ .clientId(RESOURCE_SERVER_CLIENT_ID)
+ .authorizationServicesEnabled(true)
+ .serviceAccountsEnabled(true)
+ .build();
+ testRealmResource().clients().create(client).close();
+
+ //import exported settings
+ AuthorizationResource authorization = testRealmResource().clients().get(getClientByClientId(RESOURCE_SERVER_CLIENT_ID).getId()).authorization();
+ authorization.importSettings(exportSettings);
+
+ //check imported settings - TODO
+ PolicyRepresentation result = authorization.policies().findByName("role-based-policy");
+ Map<String, String> config1 = result.getConfig();
+ ResourceServerRepresentation settings = authorization.getSettings();
+ System.out.println("");
+ }
+
+ private ClientRepresentation getClientByClientId(String clientId) {
+ List<ClientRepresentation> findByClientId = testRealmResource().clients().findByClientId(clientId);
+ Assert.assertTrue(findByClientId.size() == 1);
+ return findByClientId.get(0);
+ }
+}
\ No newline at end of file
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 37d3a96..b8edcf8 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,8 +19,12 @@ package org.keycloak.testsuite.admin;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.AuthorizationProviderFactory;
+import org.keycloak.authorization.model.Resource;
import org.keycloak.models.GroupModel;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
@@ -564,7 +568,46 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
}
// testRestEvaluationMasterRealm
// testRestEvaluationMasterAdminTestRealm
+
// test role deletion that it cleans up authz objects
+ public static void setupDeleteTest(KeycloakSession session ) {
+ RealmModel realm = session.realms().getRealmByName(TEST);
+ RoleModel removedRole = realm.addRole("removedRole");
+ ClientModel client = realm.addClient("removedClient");
+ RoleModel removedClientRole = client.addRole("removedClientRole");
+ GroupModel removedGroup = realm.createGroup("removedGroup");
+ AdminPermissionManagement management = AdminPermissions.management(session, realm);
+ management.roles().setPermissionsEnabled(removedRole, true);
+ management.roles().setPermissionsEnabled(removedClientRole, true);
+ management.groups().setPermissionsEnabled(removedGroup, true);
+ management.clients().setPermissionsEnabled(client, true);
+ }
+
+ public static void invokeDelete(KeycloakSession session) {
+ RealmModel realm = session.realms().getRealmByName(TEST);
+ AdminPermissionManagement management = AdminPermissions.management(session, realm);
+ List<Resource> byResourceServer = management.authz().getStoreFactory().getResourceStore().findByResourceServer(management.realmResourceServer().getId());
+ Assert.assertEquals(4, byResourceServer.size());
+ RoleModel removedRole = realm.getRole("removedRole");
+ realm.removeRole(removedRole);
+ ClientModel client = realm.getClientByClientId("removedClient");
+ RoleModel removedClientRole = client.getRole("removedClientRole");
+ client.removeRole(removedClientRole);
+ GroupModel group = KeycloakModelUtils.findGroupByPath(realm, "removedGroup");
+ realm.removeGroup(group);
+ byResourceServer = management.authz().getStoreFactory().getResourceStore().findByResourceServer(management.realmResourceServer().getId());
+ Assert.assertEquals(1, byResourceServer.size());
+ realm.removeClient(client.getId());
+ byResourceServer = management.authz().getStoreFactory().getResourceStore().findByResourceServer(management.realmResourceServer().getId());
+ Assert.assertEquals(0, byResourceServer.size());
+ }
+
+ @Test
+ public void testRemoveCleanup() throws Exception {
+ testingClient.server().run(FineGrainAdminUnitTest::setupDeleteTest);
+ testingClient.server().run(FineGrainAdminUnitTest::invokeDelete);
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
index ba0b7c9..7f811c6 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
@@ -173,7 +173,7 @@ public class RealmTest extends AbstractAdminTest {
adminClient.realms().create(rep);
- assertEquals("hashIterations(20000)", adminClient.realm("new-realm").toRepresentation().getPasswordPolicy());
+ assertEquals(null, adminClient.realm("new-realm").toRepresentation().getPasswordPolicy());
adminClient.realms().realm("new-realm").remove();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
new file mode 100644
index 0000000..0f2ac61
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.authz;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.function.Supplier;
+
+import javax.ws.rs.core.Response;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.authorization.client.AuthzClient;
+import org.keycloak.authorization.client.Configuration;
+import org.keycloak.authorization.client.representation.AuthorizationRequestMetadata;
+import org.keycloak.authorization.client.representation.EntitlementRequest;
+import org.keycloak.authorization.client.representation.EntitlementResponse;
+import org.keycloak.authorization.client.representation.PermissionRequest;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.Permission;
+import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.RolesBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class EntitlementAPITest extends AbstractKeycloakTest {
+
+ private AuthzClient authzClient;
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ testRealms.add(RealmBuilder.create().name("authz-test")
+ .roles(RolesBuilder.create().realmRole(RoleBuilder.create().name("uma_authorization").build()))
+ .user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization"))
+ .user(UserBuilder.create().username("kolo").password("password"))
+ .client(ClientBuilder.create().clientId("resource-server-test")
+ .secret("secret")
+ .authorizationServicesEnabled(true)
+ .redirectUris("http://localhost/resource-server-test")
+ .defaultRoles("uma_protection")
+ .directAccessGrants())
+ .build());
+ }
+
+ @Before
+ public void configureAuthorization() throws Exception {
+ ClientResource client = getClient(getRealm());
+ AuthorizationResource authorization = client.authorization();
+
+ JSPolicyRepresentation policy = new JSPolicyRepresentation();
+
+ policy.setName("Default Policy");
+ policy.setCode("$evaluation.grant();");
+
+ authorization.policies().js().create(policy).close();
+
+ for (int i = 1; i <= 20; i++) {
+ ResourceRepresentation resource = new ResourceRepresentation("Resource " + i);
+
+ authorization.resources().create(resource).close();
+
+ ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
+
+ permission.setName(resource.getName() + " Permission");
+ permission.addResource(resource.getName());
+ permission.addPolicy(policy.getName());
+
+ authorization.permissions().resource().create(permission).close();
+ }
+ }
+
+ @Test
+ public void testRptRequestWithoutResourceName() {
+ AuthorizationRequestMetadata metadata = new AuthorizationRequestMetadata();
+
+ metadata.setIncludeResourceName(false);
+
+ assertResponse(metadata, () -> {
+ EntitlementRequest request = new EntitlementRequest();
+
+ request.setMetadata(metadata);
+ request.addPermission(new PermissionRequest("Resource 1"));
+
+ return getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
+ });
+ }
+
+ @Test
+ public void testRptRequestWithResourceName() {
+ AuthorizationRequestMetadata metadata = new AuthorizationRequestMetadata();
+
+ metadata.setIncludeResourceName(true);
+
+ assertResponse(metadata, () -> getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).getAll("resource-server-test"));
+
+ EntitlementRequest request = new EntitlementRequest();
+
+ request.setMetadata(metadata);
+ request.addPermission(new PermissionRequest("Resource 13"));
+
+ assertResponse(metadata, () -> getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request));
+
+ request.setMetadata(null);
+
+ assertResponse(metadata, () -> getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request));
+ }
+
+ @Test
+ public void testPermissionLimit() {
+ EntitlementRequest request = new EntitlementRequest();
+
+ for (int i = 1; i <= 10; i++) {
+ request.addPermission(new PermissionRequest("Resource " + i));
+ }
+
+ AuthorizationRequestMetadata metadata = new AuthorizationRequestMetadata();
+
+ metadata.setLimit(10);
+
+ request.setMetadata(metadata);
+
+ EntitlementResponse response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
+ AccessToken rpt = toAccessToken(response);
+
+ List<Permission> permissions = rpt.getAuthorization().getPermissions();
+
+ assertEquals(10, permissions.size());
+
+ for (int i = 0; i < 10; i++) {
+ assertEquals("Resource " + (i + 1), permissions.get(i).getResourceSetName());
+ }
+
+ request = new EntitlementRequest();
+
+ for (int i = 11; i <= 15; i++) {
+ request.addPermission(new PermissionRequest("Resource " + i));
+ }
+
+ request.setMetadata(metadata);
+ request.setRpt(response.getRpt());
+
+ response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
+ rpt = toAccessToken(response);
+
+ permissions = rpt.getAuthorization().getPermissions();
+
+ assertEquals(10, permissions.size());
+
+ for (int i = 0; i < 10; i++) {
+ if (i < 5) {
+ assertEquals("Resource " + (i + 11), permissions.get(i).getResourceSetName());
+ } else {
+ assertEquals("Resource " + (i - 4), permissions.get(i).getResourceSetName());
+ }
+ }
+
+ request = new EntitlementRequest();
+
+ for (int i = 16; i <= 18; i++) {
+ request.addPermission(new PermissionRequest("Resource " + i));
+ }
+
+ request.setMetadata(metadata);
+ request.setRpt(response.getRpt());
+
+ response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
+ rpt = toAccessToken(response);
+
+ permissions = rpt.getAuthorization().getPermissions();
+
+ assertEquals(10, permissions.size());
+ assertEquals("Resource 16", permissions.get(0).getResourceSetName());
+ assertEquals("Resource 17", permissions.get(1).getResourceSetName());
+ assertEquals("Resource 18", permissions.get(2).getResourceSetName());
+ assertEquals("Resource 11", permissions.get(3).getResourceSetName());
+ assertEquals("Resource 12", permissions.get(4).getResourceSetName());
+ assertEquals("Resource 13", permissions.get(5).getResourceSetName());
+ assertEquals("Resource 14", permissions.get(6).getResourceSetName());
+ assertEquals("Resource 15", permissions.get(7).getResourceSetName());
+ assertEquals("Resource 1", permissions.get(8).getResourceSetName());
+ assertEquals("Resource 2", permissions.get(9).getResourceSetName());
+
+ request = new EntitlementRequest();
+
+ metadata.setLimit(5);
+ request.setMetadata(metadata);
+ request.setRpt(response.getRpt());
+
+ response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request);
+ rpt = toAccessToken(response);
+
+ permissions = rpt.getAuthorization().getPermissions();
+
+ assertEquals(5, permissions.size());
+ assertEquals("Resource 16", permissions.get(0).getResourceSetName());
+ assertEquals("Resource 17", permissions.get(1).getResourceSetName());
+ assertEquals("Resource 18", permissions.get(2).getResourceSetName());
+ assertEquals("Resource 11", permissions.get(3).getResourceSetName());
+ assertEquals("Resource 12", permissions.get(4).getResourceSetName());
+ }
+
+ private void assertResponse(AuthorizationRequestMetadata metadata, Supplier<EntitlementResponse> responseSupplier) {
+ AccessToken.Authorization authorization = toAccessToken(responseSupplier.get()).getAuthorization();
+
+ List<Permission> permissions = authorization.getPermissions();
+
+ assertNotNull(permissions);
+ assertFalse(permissions.isEmpty());
+
+ for (Permission permission : permissions) {
+ if (metadata.isIncludeResourceName()) {
+ assertNotNull(permission.getResourceSetName());
+ } else {
+ assertNull(permission.getResourceSetName());
+ }
+ }
+ }
+
+ private AccessToken toAccessToken(EntitlementResponse response) {
+ AccessToken accessToken;
+
+ try {
+ accessToken = new JWSInput(response.getRpt()).readJsonContent(AccessToken.class);
+ } catch (JWSInputException cause) {
+ throw new RuntimeException("Failed to deserialize RPT", cause);
+ }
+ return accessToken;
+ }
+
+ private RealmResource getRealm() throws Exception {
+ return adminClient.realm("authz-test");
+ }
+
+ private ClientResource getClient(RealmResource realm) {
+ ClientsResource clients = realm.clients();
+ return clients.findByClientId("resource-server-test").stream().map(representation -> clients.get(representation.getId())).findFirst().orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]"));
+ }
+
+ private AuthzClient getAuthzClient() {
+ if (authzClient == null) {
+ try {
+ authzClient = AuthzClient.create(JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/default-keycloak.json"), Configuration.class));
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to create authz client", cause);
+ }
+ }
+
+ return authzClient;
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
index c5bb785..8666e04 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
@@ -18,6 +18,7 @@
package org.keycloak.testsuite.client;
+import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
@@ -44,6 +45,7 @@ import org.keycloak.testsuite.util.UserInfoClientUtil;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
+import java.util.Base64;
import java.util.Collections;
import java.util.List;
@@ -319,11 +321,20 @@ public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrati
oauth.openLoginForm();
loginResponse = new OAuthClient.AuthorizationEndpointResponse(oauth);
accessTokenResponse = oauth.doAccessTokenRequest(loginResponse.getCode(), pairwiseClient.getClientSecret());
+
+ // Assert token payloads don't contain more than one "sub"
+ String accessTokenPayload = getPayload(accessTokenResponse.getAccessToken());
+ Assert.assertEquals(1, StringUtils.countMatches(accessTokenPayload, "\"sub\""));
+ String idTokenPayload = getPayload(accessTokenResponse.getIdToken());
+ Assert.assertEquals(1, StringUtils.countMatches(idTokenPayload, "\"sub\""));
+ String refreshTokenPayload = getPayload(accessTokenResponse.getRefreshToken());
+ Assert.assertEquals(1, StringUtils.countMatches(refreshTokenPayload, "\"sub\""));
+
accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
Assert.assertEquals("test-user", accessToken.getPreferredUsername());
Assert.assertEquals("test-user@localhost", accessToken.getEmail());
- // Assert pairwise client has different subject like userId
+ // Assert pairwise client has different subject than userId
String pairwiseUserId = accessToken.getSubject();
Assert.assertNotEquals(pairwiseUserId, user.getId());
@@ -339,4 +350,9 @@ public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrati
jaxrsClient.close();
}
}
-}
+
+ private String getPayload(String token) {
+ String payloadBase64 = token.split("\\.")[1];
+ return new String(Base64.getDecoder().decode(payloadBase64));
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java
new file mode 100644
index 0000000..84527f7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractAdminCrossDCTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.crossdc;
+
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.events.EventsListenerProviderFactory;
+import org.keycloak.testsuite.util.TestCleanup;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public abstract class AbstractAdminCrossDCTest extends AbstractCrossDCTest {
+
+ protected static final String REALM_NAME = "admin-client-test";
+
+ protected RealmResource realm;
+ protected String realmId;
+
+
+ @Override
+ public void configureTestRealm(RealmRepresentation testRealm) {
+ findTestApp(testRealm).setDirectAccessGrantsEnabled(true);
+ }
+
+
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ super.addTestRealms(testRealms);
+
+ RealmRepresentation adminRealmRep = new RealmRepresentation();
+ adminRealmRep.setId(REALM_NAME);
+ adminRealmRep.setRealm(REALM_NAME);
+ adminRealmRep.setEnabled(true);
+ Map<String, String> config = new HashMap<>();
+ config.put("from", "auto@keycloak.org");
+ config.put("host", "localhost");
+ config.put("port", "3025");
+ adminRealmRep.setSmtpServer(config);
+
+ List<String> eventListeners = new ArrayList<>();
+ eventListeners.add(JBossLoggingEventListenerProviderFactory.ID);
+ eventListeners.add(EventsListenerProviderFactory.PROVIDER_ID);
+ adminRealmRep.setEventsListeners(eventListeners);
+
+ testRealms.add(adminRealmRep);
+ }
+
+ @Before
+ public void setRealm() {
+ realm = adminClient.realm(REALM_NAME);
+ realmId = realm.toRepresentation().getId();
+ }
+
+ @Override
+ protected TestCleanup getCleanup() {
+ return getCleanup(REALM_NAME);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java
new file mode 100644
index 0000000..aa674ca
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/AbstractCrossDCTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.crossdc;
+
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.models.Constants;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+import org.keycloak.testsuite.arquillian.LoadBalancerController;
+import org.keycloak.testsuite.arquillian.annotation.LoadBalancer;
+import org.keycloak.testsuite.auth.page.AuthRealm;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.jboss.arquillian.container.test.api.ContainerController;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest {
+
+ @ArquillianResource
+ @LoadBalancer(value = "auth-server-balancer-cross-dc")
+ protected LoadBalancerController loadBalancerCtrl;
+
+ @ArquillianResource
+ protected ContainerController containerController;
+
+ protected Map<ContainerInfo, Keycloak> backendAdminClients = new HashMap<>();
+
+ @After
+ @Before
+ public void enableOnlyFirstNodeInFirstDc() {
+ this.loadBalancerCtrl.disableAllBackendNodes();
+ loadBalancerCtrl.enableBackendNodeByName(getAutomaticallyStartedBackendNodes(0)
+ .findFirst()
+ .orElseThrow(() -> new IllegalStateException("No node is started automatically"))
+ .getQualifier()
+ );
+ }
+
+ @Before
+ public void terminateManuallyStartedServers() {
+ log.debug("Halting all nodes that are started manually");
+ this.suiteContext.getDcAuthServerBackendsInfo().stream()
+ .flatMap(List::stream)
+ .filter(ContainerInfo::isStarted)
+ .filter(ContainerInfo::isManual)
+ .map(ContainerInfo::getQualifier)
+ .forEach(containerController::stop);
+ }
+
+ @Override
+ public void importTestRealms() {
+ enableOnlyFirstNodeInFirstDc();
+ super.importTestRealms();
+ }
+
+ @Override
+ public void afterAbstractKeycloakTest() {
+ enableOnlyFirstNodeInFirstDc();
+ super.afterAbstractKeycloakTest();
+ }
+
+ @Override
+ public void deleteCookies() {
+ enableOnlyFirstNodeInFirstDc();
+ super.deleteCookies();
+ }
+
+ @Before
+ public void initLoadBalancer() {
+ log.debug("Initializing load balancer - only enabling started nodes in the first DC");
+ this.loadBalancerCtrl.disableAllBackendNodes();
+ // Enable only the started nodes in each datacenter
+ this.suiteContext.getDcAuthServerBackendsInfo().get(0).stream()
+ .filter(ContainerInfo::isStarted)
+ .map(ContainerInfo::getQualifier)
+ .forEach(loadBalancerCtrl::enableBackendNodeByName);
+ }
+
+ protected Keycloak createAdminClientFor(ContainerInfo node) {
+ log.info("Initializing admin client for " + node.getContextRoot() + "/auth");
+ return Keycloak.getInstance(node.getContextRoot() + "/auth", AuthRealm.MASTER, AuthRealm.ADMIN, AuthRealm.ADMIN, Constants.ADMIN_CLI_CLIENT_ID);
+ }
+
+ protected Keycloak getAdminClientFor(ContainerInfo node) {
+ Keycloak adminClient = backendAdminClients.get(node);
+ if (adminClient == null && node.equals(suiteContext.getAuthServerInfo())) {
+ adminClient = this.adminClient;
+ }
+ return adminClient;
+ }
+
+ public void disableDcOnLoadBalancer(int dcIndex) {
+ log.infof("Disabling load balancer for dc=%d", dcIndex);
+ this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).forEach(c -> loadBalancerCtrl.disableBackendNodeByName(c.getQualifier()));
+ }
+
+ /**
+ * Enables all started nodes in the given data center
+ * @param dcIndex
+ */
+ public void enableDcOnLoadBalancer(int dcIndex) {
+ log.infof("Enabling load balancer for dc=%d", dcIndex);
+ final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
+ if (! dcNodes.stream().anyMatch(ContainerInfo::isStarted)) {
+ log.warnf("No node is started in DC %d", dcIndex);
+ } else {
+ dcNodes.stream()
+ .filter(ContainerInfo::isStarted)
+ .forEach(c -> loadBalancerCtrl.enableBackendNodeByName(c.getQualifier()));
+ }
+ }
+
+ public void disableLoadBalancerNode(int dcIndex, int nodeIndex) {
+ log.infof("Disabling load balancer for dc=%d, node=%d", dcIndex, nodeIndex);
+ loadBalancerCtrl.disableBackendNodeByName(this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).get(nodeIndex).getQualifier());
+ }
+
+ public void enableLoadBalancerNode(int dcIndex, int nodeIndex) {
+ log.infof("Enabling load balancer for dc=%d, node=%d", dcIndex, nodeIndex);
+ final ContainerInfo backendNode = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).get(nodeIndex);
+ if (backendNode == null) {
+ throw new IllegalArgumentException("Invalid node with index " + nodeIndex + " for DC " + dcIndex);
+ }
+ if (! backendNode.isStarted()) {
+ log.warnf("Node %s is not started in DC %d", backendNode.getQualifier(), dcIndex);
+ }
+ loadBalancerCtrl.enableBackendNodeByName(backendNode.getQualifier());
+ }
+
+ public Stream<ContainerInfo> getManuallyStartedBackendNodes(int dcIndex) {
+ final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
+ return dcNodes.stream().filter(ContainerInfo::isManual);
+ }
+
+ public Stream<ContainerInfo> getAutomaticallyStartedBackendNodes(int dcIndex) {
+ final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
+ return dcNodes.stream().filter(c -> ! c.isManual());
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java
new file mode 100644
index 0000000..45e7571
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.crossdc;
+
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.events.admin.ResourceType;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.Retry;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+import org.keycloak.testsuite.page.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.util.AdminEventPaths;
+import org.keycloak.testsuite.util.GreenMailRule;
+import org.keycloak.testsuite.util.MailUtils;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.ws.rs.core.Response;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class ActionTokenCrossDCTest extends AbstractAdminCrossDCTest {
+
+ @Rule
+ public GreenMailRule greenMail = new GreenMailRule();
+
+ @Page
+ protected LoginPasswordUpdatePage passwordUpdatePage;
+
+ @Page
+ protected ErrorPage errorPage;
+
+ private String createUser(UserRepresentation userRep) {
+ Response response = realm.users().create(userRep);
+ String createdId = ApiUtil.getCreatedId(response);
+ response.close();
+
+ getCleanup().addUserId(createdId);
+
+ return createdId;
+ }
+
+ @Test
+ public void sendResetPasswordEmailSuccessWorksInCrossDc() throws IOException, MessagingException {
+ UserRepresentation userRep = new UserRepresentation();
+ userRep.setEnabled(true);
+ userRep.setUsername("user1");
+ userRep.setEmail("user1@test.com");
+
+ String id = createUser(userRep);
+
+ UserResource user = realm.users().get(id);
+ List<String> actions = new LinkedList<>();
+ actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+ user.executeActionsEmail(actions);
+
+ Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+
+ MimeMessage message = greenMail.getReceivedMessages()[0];
+
+ String link = MailUtils.getPasswordResetEmailLink(message);
+
+ driver.navigate().to(link);
+
+ passwordUpdatePage.assertCurrent();
+
+ passwordUpdatePage.changePassword("new-pass", "new-pass");
+
+ assertEquals("Your account has been updated.", driver.getTitle());
+
+ disableDcOnLoadBalancer(0);
+ enableDcOnLoadBalancer(1);
+
+ Retry.execute(() -> {
+ driver.navigate().to(link);
+ errorPage.assertCurrent();
+ }, 3, 400);
+ }
+
+ @Ignore("KEYCLOAK-5030")
+ @Test
+ public void sendResetPasswordEmailAfterNewNodeAdded() throws IOException, MessagingException {
+ disableDcOnLoadBalancer(1);
+
+ UserRepresentation userRep = new UserRepresentation();
+ userRep.setEnabled(true);
+ userRep.setUsername("user1");
+ userRep.setEmail("user1@test.com");
+
+ String id = createUser(userRep);
+
+ UserResource user = realm.users().get(id);
+ List<String> actions = new LinkedList<>();
+ actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+ user.executeActionsEmail(actions);
+
+ Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+
+ MimeMessage message = greenMail.getReceivedMessages()[0];
+
+ String link = MailUtils.getPasswordResetEmailLink(message);
+
+ driver.navigate().to(link);
+
+ passwordUpdatePage.assertCurrent();
+
+ passwordUpdatePage.changePassword("new-pass", "new-pass");
+
+ assertEquals("Your account has been updated.", driver.getTitle());
+
+ disableDcOnLoadBalancer(0);
+ getManuallyStartedBackendNodes(1)
+ .findFirst()
+ .ifPresent(c -> {
+ containerController.start(c.getQualifier());
+ loadBalancerCtrl.enableBackendNodeByName(c.getQualifier());
+ });
+
+ driver.navigate().to(link);
+
+ errorPage.assertCurrent();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
index f923cf4..b29abc1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
@@ -627,10 +627,12 @@ public class ExportImportUtil {
assertPredicate(scopes, scopePredicates);
List<PolicyRepresentation> policies = authzResource.policies().policies();
- Assert.assertEquals(11, policies.size());
+ Assert.assertEquals(13, policies.size());
List<Predicate<PolicyRepresentation>> policyPredicates = new ArrayList<>();
policyPredicates.add(policyRepresentation -> "Any Admin Policy".equals(policyRepresentation.getName()));
policyPredicates.add(policyRepresentation -> "Any User Policy".equals(policyRepresentation.getName()));
+ policyPredicates.add(representation -> "Client and Realm Role Policy".equals(representation.getName()));
+ policyPredicates.add(representation -> "Client Test Policy".equals(representation.getName()));
policyPredicates.add(policyRepresentation -> "Only Premium User Policy".equals(policyRepresentation.getName()));
policyPredicates.add(policyRepresentation -> "wburke policy".equals(policyRepresentation.getName()));
policyPredicates.add(policyRepresentation -> "All Users Policy".equals(policyRepresentation.getName()));
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 0784f01..6763e7d 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
@@ -103,34 +103,38 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
@Test
public void testPasswordRehashedOnAlgorithmChanged() throws Exception {
+ setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha256PasswordHashProviderFactory.ID + ") and hashIterations(1)");
+
String username = "testPasswordRehashedOnAlgorithmChanged";
createUser(username);
CredentialModel credential = fetchCredentials(username);
- assertEquals(Pbkdf2PasswordHashProviderFactory.ID, credential.getAlgorithm());
+ assertEquals(Pbkdf2Sha256PasswordHashProviderFactory.ID, credential.getAlgorithm());
- assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 20000);
+ assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 1);
- setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha256PasswordHashProviderFactory.ID + ")");
+ setPasswordPolicy("hashAlgorithm(" + Pbkdf2PasswordHashProviderFactory.ID + ") and hashIterations(1)");
loginPage.open();
loginPage.login(username, "password");
credential = fetchCredentials(username);
- assertEquals(Pbkdf2Sha256PasswordHashProviderFactory.ID, credential.getAlgorithm());
- assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 20000);
+ assertEquals(Pbkdf2PasswordHashProviderFactory.ID, credential.getAlgorithm());
+ assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 1);
}
@Test
public void testPasswordRehashedOnIterationsChanged() throws Exception {
+ setPasswordPolicy("hashIterations(10000)");
+
String username = "testPasswordRehashedOnIterationsChanged";
createUser(username);
CredentialModel credential = fetchCredentials(username);
- assertEquals(20000, credential.getHashIterations());
+ assertEquals(10000, credential.getHashIterations());
setPasswordPolicy("hashIterations(1)");
@@ -140,7 +144,7 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
credential = fetchCredentials(username);
assertEquals(1, credential.getHashIterations());
- assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 1);
+ assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 1);
}
@Test
@@ -154,13 +158,23 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
}
@Test
+ public void testDefault() throws Exception {
+ setPasswordPolicy("");
+ String username = "testDefault";
+ createUser(username);
+
+ CredentialModel credential = fetchCredentials(username);
+ assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 27500);
+ }
+
+ @Test
public void testPbkdf2Sha256() throws Exception {
setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha256PasswordHashProviderFactory.ID + ")");
String username = "testPbkdf2Sha256";
createUser(username);
CredentialModel credential = fetchCredentials(username);
- assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 20000);
+ assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 27500);
}
@Test
@@ -170,7 +184,7 @@ public class PasswordHashingTest extends AbstractTestRealmKeycloakTest {
createUser(username);
CredentialModel credential = fetchCredentials(username);
- assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA512", 20000);
+ assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA512", 30000);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
index 04a758e..a769687 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
@@ -59,6 +59,7 @@ import org.keycloak.testsuite.runonserver.RunHelpers;
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
import org.keycloak.testsuite.util.OAuthClient;
+import static org.junit.Assert.assertNull;
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT_LINKS;
import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
@@ -132,6 +133,7 @@ public class MigrationTest extends AbstractKeycloakTest {
testMigrationTo2_5_0();
testMigrationTo2_5_1();
testMigrationTo3_0_0();
+ testMigrationTo3_2_0();
}
@Test
@@ -210,7 +212,12 @@ public class MigrationTest extends AbstractKeycloakTest {
private void testMigrationTo3_0_0() {
testRoleManageAccountLinks(masterRealm, migrationRealm);
}
-
+
+ private void testMigrationTo3_2_0() {
+ assertNull(masterRealm.toRepresentation().getPasswordPolicy());
+ assertNull(migrationRealm.toRepresentation().getPasswordPolicy());
+ }
+
private void testRoleManageAccountLinks(RealmResource... realms) {
log.info("testing role manage account links");
for (RealmResource realm : realms) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-autodetect/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-autodetect/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..b00a1c0
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-autodetect/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,44 @@
+<!--
+ ~ 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_9.xsd">
+ <SP entityID="http://localhost:8081/sales-post/"
+ sslPolicy="EXTERNAL"
+ nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ logoutPage="/logout.jsp"
+ forceAuthentication="false"
+ autodetectBearerOnly="true">
+ <PrincipalNameMapping policy="FROM_NAME_ID"/>
+ <RoleIdentifiers>
+ <Attribute name="Role"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp">
+ <SingleSignOnService requestBinding="POST"
+ bindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+ />
+
+ <SingleLogoutService
+ requestBinding="POST"
+ responseBinding="POST"
+ postBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+ redirectBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+ />
+ </IDP>
+ </SP>
+</keycloak-saml-adapter>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
index 6bc040f..ecf986f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -103,6 +103,7 @@
<property name="outputToConsole">${backends.console.output}</property>
<property name="managementPort">${auth.server.backend1.management.port}</property>
<property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+ <property name="bindHttpPortOffset">${auth.server.backend1.port.offset}</property>
</configuration>
</container>
<container qualifier="auth-server-${auth.server}-backend2" mode="manual" >
@@ -124,6 +125,7 @@
<property name="outputToConsole">${backends.console.output}</property>
<property name="managementPort">${auth.server.backend2.management.port}</property>
<property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+ <property name="bindHttpPortOffset">${auth.server.backend2.port.offset}</property>
</configuration>
</container>
</group>
@@ -165,6 +167,138 @@
</group>
+ <!-- Cross DC with embedded undertow. Node numbering is [centre #].[node #] -->
+ <group qualifier="auth-server-undertow-cross-dc">
+ <container qualifier="cache-server-cross-dc" mode="suite" >
+ <configuration>
+ <property name="enabled">${auth.server.undertow.crossdc}</property>
+ <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
+ <property name="jbossHome">${cache.server.home}</property>
+ <property name="serverConfig">standalone.xml</property>
+ <property name="jbossArguments">
+ -Djboss.socket.binding.port-offset=${cache.server.port.offset}
+ -Djboss.default.multicast.address=234.56.78.99
+ -Djboss.node.name=cache-server
+ ${adapter.test.props}
+ ${auth.server.profile}
+ </property>
+ <property name="javaVmArguments">
+ ${auth.server.memory.settings}
+ -Djava.net.preferIPv4Stack=true
+ </property>
+ <property name="outputToConsole">${cache.server.console.output}</property>
+ <property name="managementPort">${cache.server.management.port}</property>
+ <property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
+ </configuration>
+ </container>
+
+ <container qualifier="auth-server-balancer-cross-dc" mode="suite" >
+ <configuration>
+ <property name="enabled">${auth.server.undertow.crossdc}</property>
+ <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.lb.SimpleUndertowLoadBalancerContainer</property>
+ <property name="bindAddress">localhost</property>
+ <property name="bindHttpPort">${auth.server.http.port}</property>
+ <property name="bindHttpPortOffset">5</property>
+ <property name="nodes">auth-server-undertow-cross-dc-0.1=http://localhost:8101,auth-server-undertow-cross-dc-0.2-manual=http://localhost:8102,auth-server-undertow-cross-dc-1.1=http://localhost:8111,auth-server-undertow-cross-dc-1.2-manual=http://localhost:8112</property>
+ </configuration>
+ </container>
+
+ <container qualifier="auth-server-undertow-cross-dc-0.1" mode="suite" >
+ <configuration>
+ <property name="enabled">${auth.server.undertow.crossdc}</property>
+ <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
+ <property name="bindAddress">localhost</property>
+ <property name="bindHttpPort">${auth.server.http.port}</property>
+ <property name="bindHttpPortOffset">-79</property>
+ <property name="route">auth-server-undertow-cross-dc-0.1</property>
+ <property name="remoteMode">${undertow.remote}</property>
+ <property name="dataCenter">0</property>
+ <property name="keycloakConfigPropertyOverrides">{
+ "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.1",
+ "keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-0.1",
+ "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
+ "keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
+ "keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
+ "keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
+ "keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
+ "keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
+ "keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
+ }</property>
+ </configuration>
+ </container>
+ <container qualifier="auth-server-undertow-cross-dc-0.2-manual" mode="manual" >
+ <configuration>
+ <property name="enabled">${auth.server.undertow.crossdc}</property>
+ <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
+ <property name="bindAddress">localhost</property>
+ <property name="bindHttpPort">${auth.server.http.port}</property>
+ <property name="bindHttpPortOffset">-78</property>
+ <property name="route">auth-server-undertow-cross-dc-0.2</property>
+ <property name="remoteMode">${undertow.remote}</property>
+ <property name="dataCenter">0</property>
+ <property name="keycloakConfigPropertyOverrides">{
+ "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.1",
+ "keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-0.2",
+ "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
+ "keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
+ "keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
+ "keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
+ "keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
+ "keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
+ "keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
+ }</property>
+ </configuration>
+ </container>
+
+ <container qualifier="auth-server-undertow-cross-dc-1.1" mode="suite" >
+ <configuration>
+ <property name="enabled">${auth.server.undertow.crossdc}</property>
+ <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
+ <property name="bindAddress">localhost</property>
+ <property name="bindHttpPort">${auth.server.http.port}</property>
+ <property name="bindHttpPortOffset">-69</property>
+ <property name="route">auth-server-undertow-cross-dc-1.1</property>
+ <property name="remoteMode">${undertow.remote}</property>
+ <property name="dataCenter">1</property>
+ <property name="keycloakConfigPropertyOverrides">{
+ "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.2",
+ "keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-1.1",
+ "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
+ "keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
+ "keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
+ "keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
+ "keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
+ "keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
+ "keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
+ }</property>
+ </configuration>
+ </container>
+ <container qualifier="auth-server-undertow-cross-dc-1.2-manual" mode="manual" >
+ <configuration>
+ <property name="enabled">${auth.server.undertow.crossdc}</property>
+ <property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
+ <property name="bindAddress">localhost</property>
+ <property name="bindHttpPort">${auth.server.http.port}</property>
+ <property name="bindHttpPortOffset">-68</property>
+ <property name="route">auth-server-undertow-cross-dc-1.2</property>
+ <property name="remoteMode">${undertow.remote}</property>
+ <property name="dataCenter">1</property>
+ <property name="keycloakConfigPropertyOverrides">{
+ "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.2",
+ "keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-1.2",
+ "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
+ "keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
+ "keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
+ "keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
+ "keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
+ "keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
+ "keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
+ }</property>
+ </configuration>
+ </container>
+ </group>
+
+
<container qualifier="auth-server-balancer-wildfly" mode="suite" >
<configuration>
<property name="enabled">${auth.server.cluster}</property>
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 9d801e5..2c80ddd 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
@@ -107,15 +107,23 @@
"connectionsInfinispan": {
"default": {
+ "jgroupsUdpMcastAddr": "${keycloak.connectionsInfinispan.jgroupsUdpMcastAddr:234.56.78.90}",
+ "nodeName": "${keycloak.connectionsInfinispan.nodeName,jboss.node.name:defaultNodeName}",
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
"async": "${keycloak.connectionsInfinispan.async:false}",
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:1}",
"l1Lifespan": "${keycloak.connectionsInfinispan.l1Lifespan:600000}",
"remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:false}",
- "remoteStoreHost": "${keycloak.connectionsInfinispan.remoteStoreHost:localhost}",
+ "remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
"remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}"
}
},
+
+ "stickySessionEncoder": {
+ "infinispan": {
+ "nodeName": "${keycloak.stickySessionEncoder.nodeName,jboss.node.name:defaultNodeName}"
+ }
+ },
"truststore": {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json
index b540ab1..5f84e38 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/model/testrealm.json
@@ -283,6 +283,22 @@
}
},
{
+ "name": "Client and Realm Role Policy",
+ "type": "role",
+ "config": {
+ "roles": "[{\"id\":\"realm-management/impersonation\",\"required\":false},{\"id\":\"realm-management/manage-authorization\",\"required\":true},{\"id\":\"user\",\"required\":false}]"
+ }
+ },
+ {
+ "name": "Client Test Policy",
+ "type": "client",
+ "logic": "POSITIVE",
+ "decisionStrategy": "UNANIMOUS",
+ "config": {
+ "clients": "[\"broker\",\"admin-cli\"]"
+ }
+ },
+ {
"name": "Only Premium User Policy",
"description": "Defines that only premium users can do something",
"type": "role",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js b/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js
index 0fd70d5..766eda1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js
@@ -2,17 +2,17 @@ AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationF
function authenticate(context) {
- if (clientSession.getRealm().getName() != "${realm}") {
+ if (authenticationSession.getRealm().getName() != "${realm}") {
context.failure(AuthenticationFlowError.INVALID_CLIENT_SESSION);
return;
}
- if (clientSession.getClient().getClientId() != "${clientId}") {
+ if (authenticationSession.getClient().getClientId() != "${clientId}") {
context.failure(AuthenticationFlowError.UNKNOWN_CLIENT);
return;
}
- if (clientSession.getProtocol() != "${authMethod}") {
+ if (authenticationSession.getProtocol() != "${authMethod}") {
context.failure(AuthenticationFlowError.INVALID_CLIENT_SESSION);
return;
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
index 29b46ca..091aba2 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
@@ -152,6 +152,12 @@
</modules>
</profile>
<profile>
+ <id>app-server-wildfly10</id>
+ <modules>
+ <module>wildfly10</module>
+ </modules>
+ </profile>
+ <profile>
<id>app-server-relative</id>
<modules>
<module>relative</module>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/pom.xml
new file mode 100644
index 0000000..486da62
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!--
+~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+~ and other contributors as indicated by the @author tags.
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~ http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-tests-adapters-jboss</artifactId>
+ <version>3.2.0.CR1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>integration-arquillian-tests-adapters-wildfly</artifactId>
+
+ <name>Adapter Tests - JBoss - Wildfly 10</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly.extras.creaper</groupId>
+ <artifactId>creaper-core</artifactId>
+ <scope>test</scope>
+ <version>1.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly.core</groupId>
+ <artifactId>wildfly-cli</artifactId>
+ <scope>test</scope>
+ <version>2.2.0.Final</version>
+ </dependency>
+ </dependencies>
+
+ <properties>
+ <app.server>wildfly10</app.server>
+ </properties>
+
+</project>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/cluster/Wildfly10SAMLAdapterClusterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/cluster/Wildfly10SAMLAdapterClusterTest.java
new file mode 100644
index 0000000..f62f320
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/cluster/Wildfly10SAMLAdapterClusterTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter.cluster;
+
+import org.keycloak.testsuite.adapter.page.EmployeeServletDistributable;
+import org.keycloak.testsuite.arquillian.annotation.*;
+
+import java.io.*;
+
+import org.keycloak.testsuite.adapter.servlet.cluster.AbstractSAMLAdapterClusterTest;
+import org.keycloak.testsuite.adapter.servlet.SendUsernameServlet;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.wildfly.extras.creaper.core.*;
+import org.wildfly.extras.creaper.core.online.*;
+import org.wildfly.extras.creaper.core.online.operations.*;
+
+import static org.keycloak.testsuite.adapter.AbstractServletsAdapterTest.samlServletDeployment;
+
+/**
+ *
+ * @author hmlnarik
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10SAMLAdapterClusterTest extends AbstractSAMLAdapterClusterTest {
+
+ @TargetsContainer(value = "app-server-wildfly-" + NODE_1_NAME)
+ @Deployment(name = EmployeeServletDistributable.DEPLOYMENT_NAME, managed = false)
+ protected static WebArchive employee() {
+ return samlServletDeployment(EmployeeServletDistributable.DEPLOYMENT_NAME, EmployeeServletDistributable.DEPLOYMENT_NAME + "/WEB-INF/web.xml", SendUsernameServlet.class);
+ }
+
+ @TargetsContainer(value = "app-server-wildfly-" + NODE_2_NAME)
+ @Deployment(name = EmployeeServletDistributable.DEPLOYMENT_NAME + "_2", managed = false)
+ protected static WebArchive employee2() {
+ return employee();
+ }
+
+ @Override
+ protected void prepareWorkerNode(Integer managementPort) throws IOException, CliException, NumberFormatException {
+ log.infov("Preparing worker node ({0})", managementPort);
+
+ OnlineManagementClient clientWorkerNodeClient = ManagementClient.online(OnlineOptions
+ .standalone()
+ .hostAndPort("localhost", managementPort)
+ .build());
+ Operations op = new Operations(clientWorkerNodeClient);
+
+ Batch b = new Batch();
+ Address tcppingStack = Address
+ .subsystem("jgroups")
+ .and("stack", "tcpping");
+ b.add(tcppingStack);
+ b.add(tcppingStack.and("transport", "TCP"), Values.of("socket-binding", "jgroups-tcp"));
+ b.add(tcppingStack.and("protocol", "TCPPING"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "initial_hosts"), Values.of("value", "localhost[" + (7600 + PORT_OFFSET_NODE_1) + "],localhost[" + (7600 + PORT_OFFSET_NODE_2) + "]"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "port_range"), Values.of("value", "0"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "num_initial_members"), Values.of("value", "2"));
+ b.add(tcppingStack.and("protocol", "TCPPING").and("property", "timeout"), Values.of("value", "3000"));
+ b.add(tcppingStack.and("protocol", "MERGE3"));
+ b.add(tcppingStack.and("protocol", "FD_SOCK"), Values.of("socket-binding", "jgroups-tcp-fd"));
+ b.add(tcppingStack.and("protocol", "FD"));
+ b.add(tcppingStack.and("protocol", "VERIFY_SUSPECT"));
+ b.add(tcppingStack.and("protocol", "pbcast.NAKACK2"));
+ b.add(tcppingStack.and("protocol", "UNICAST3"));
+ b.add(tcppingStack.and("protocol", "pbcast.STABLE"));
+ b.add(tcppingStack.and("protocol", "pbcast.GMS"));
+ b.add(tcppingStack.and("protocol", "MFC"));
+ b.add(tcppingStack.and("protocol", "FRAG2"));
+ b.writeAttribute(Address.subsystem("jgroups").and("channel", "ee"), "stack", "tcpping");
+ op.batch(b);
+
+ op.add(Address.extension("org.keycloak.keycloak-saml-adapter-subsystem"), Values.of("module", "org.keycloak.keycloak-saml-adapter-subsystem"));
+ op.add(Address.subsystem("keycloak-saml"));
+
+ clientWorkerNodeClient.execute("reload");
+
+ log.infov("Worker node ({0}) Prepared", managementPort);
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10DefaultAuthzConfigAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10DefaultAuthzConfigAdapterTest.java
new file mode 100644
index 0000000..a0c23c5
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10DefaultAuthzConfigAdapterTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter.example.authorization;
+
+import org.keycloak.testsuite.adapter.example.authorization.AbstractDefaultAuthzConfigAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-wildfly10")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class Wildfly10DefaultAuthzConfigAdapterTest extends AbstractDefaultAuthzConfigAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10PermissiveModeAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10PermissiveModeAdapterTest.java
new file mode 100644
index 0000000..3dc091c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10PermissiveModeAdapterTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter.example.authorization;
+
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@RunAsClient
+@AppServerContainer("app-server-wildfly10")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class Wildfly10PermissiveModeAdapterTest extends AbstractPermissiveModeAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10PhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10PhotozExampleAdapterTest.java
new file mode 100644
index 0000000..9921e64
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10PhotozExampleAdapterTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter.example.authorization;
+
+import org.keycloak.testsuite.adapter.example.authorization.AbstractPhotozExampleAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-wildfly10")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class Wildfly10PhotozExampleAdapterTest extends AbstractPhotozExampleAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10ServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10ServletAuthzAdapterTest.java
new file mode 100644
index 0000000..ac7dcfc
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/authorization/Wildfly10ServletAuthzAdapterTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.adapter.example.authorization;
+
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@RunAsClient
+@AppServerContainer("app-server-wildfly10")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class Wildfly10ServletAuthzAdapterTest extends AbstractServletAuthzFunctionalAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/cors/Wildfly10CorsExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/cors/Wildfly10CorsExampleAdapterTest.java
new file mode 100644
index 0000000..9191b78
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/cors/Wildfly10CorsExampleAdapterTest.java
@@ -0,0 +1,12 @@
+package org.keycloak.testsuite.adapter.example.cors;
+
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10CorsExampleAdapterTest extends AbstractCorsExampleAdapterTest {
+
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10BasicAuthExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10BasicAuthExampleAdapterTest.java
new file mode 100644
index 0000000..f49dd19
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10BasicAuthExampleAdapterTest.java
@@ -0,0 +1,12 @@
+package org.keycloak.testsuite.adapter.example;
+
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10BasicAuthExampleAdapterTest extends AbstractBasicAuthExampleAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10DemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10DemoExampleAdapterTest.java
new file mode 100644
index 0000000..6117bfd
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10DemoExampleAdapterTest.java
@@ -0,0 +1,12 @@
+package org.keycloak.testsuite.adapter.example;
+
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10DemoExampleAdapterTest extends AbstractDemoExampleAdapterTest {
+
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10JSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10JSConsoleExampleAdapterTest.java
new file mode 100644
index 0000000..565ca53
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10JSConsoleExampleAdapterTest.java
@@ -0,0 +1,13 @@
+
+package org.keycloak.testsuite.adapter.example;
+
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10JSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10SAMLExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10SAMLExampleAdapterTest.java
new file mode 100644
index 0000000..d7a162c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10SAMLExampleAdapterTest.java
@@ -0,0 +1,11 @@
+package org.keycloak.testsuite.adapter.example;
+
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * @author mhajas
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10SAMLExampleAdapterTest extends AbstractSAMLExampleAdapterTest {
+
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10ClientInitiatedAccountLinkTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10ClientInitiatedAccountLinkTest.java
new file mode 100644
index 0000000..b35c561
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10ClientInitiatedAccountLinkTest.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractClientInitiatedAccountLinkTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10ClientInitiatedAccountLinkTest extends AbstractClientInitiatedAccountLinkTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OfflineServletsAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OfflineServletsAdapterTest.java
new file mode 100644
index 0000000..137bc71
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OfflineServletsAdapterTest.java
@@ -0,0 +1,11 @@
+package org.keycloak.testsuite.adapter;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractOfflineServletsAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10OfflineServletsAdapterTest extends AbstractOfflineServletsAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCAdapterTest.java
new file mode 100644
index 0000000..72a986f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCAdapterTest.java
@@ -0,0 +1,13 @@
+package org.keycloak.testsuite.adapter;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCFilterAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCFilterAdapterTest.java
new file mode 100644
index 0000000..7707f4b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCFilterAdapterTest.java
@@ -0,0 +1,12 @@
+package org.keycloak.testsuite.adapter;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractDemoFilterServletAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * Created by zschwarz on 9/14/16.
+ */
+
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10OIDCFilterAdapterTest extends AbstractDemoFilterServletAdapterTest{
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCPublicKeyRotationAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCPublicKeyRotationAdapterTest.java
new file mode 100644
index 0000000..b136e86
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCPublicKeyRotationAdapterTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractOIDCPublicKeyRotationAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10OIDCPublicKeyRotationAdapterTest extends AbstractOIDCPublicKeyRotationAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCSessionAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCSessionAdapterTest.java
new file mode 100644
index 0000000..eac4d19
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10OIDCSessionAdapterTest.java
@@ -0,0 +1,13 @@
+package org.keycloak.testsuite.adapter;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractSessionServletAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10OIDCSessionAdapterTest extends AbstractSessionServletAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10SAMLAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10SAMLAdapterTest.java
new file mode 100644
index 0000000..5610d8c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10SAMLAdapterTest.java
@@ -0,0 +1,12 @@
+package org.keycloak.testsuite.adapter;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractSAMLServletsAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * @author mhajas
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10SAMLAdapterTest extends AbstractSAMLServletsAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10SAMLFilterAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10SAMLFilterAdapterTest.java
new file mode 100644
index 0000000..e2b67b8
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/Wildfly10SAMLFilterAdapterTest.java
@@ -0,0 +1,11 @@
+package org.keycloak.testsuite.adapter;
+
+import org.keycloak.testsuite.adapter.servlet.AbstractSAMLFilterServletAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ * @author mhajas
+ */
+@AppServerContainer("app-server-wildfly10")
+public class Wildfly10SAMLFilterAdapterTest extends AbstractSAMLFilterServletAdapterTest {
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/resources/adapter-test/keycloak-saml/employee-distributable/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/resources/adapter-test/keycloak-saml/employee-distributable/WEB-INF/web.xml
new file mode 100644
index 0000000..b57928f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/resources/adapter-test/keycloak-saml/employee-distributable/WEB-INF/web.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+ ~ and other contributors as indicated by the @author tags.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <distributable/>
+
+ <absolute-ordering/>
+
+ <module-name>%CONTEXT_PATH%</module-name>
+
+ <servlet-mapping>
+ <servlet-name>javax.ws.rs.core.Application</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+ <error-page>
+ <location>/error.html</location>
+ </error-page>
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Application</web-resource-name>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>manager</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+ <login-config>
+ <auth-method>KEYCLOAK-SAML</auth-method>
+ <realm-name>demo</realm-name>
+ </login-config>
+
+ <security-role>
+ <role-name>manager</role-name>
+ </security-role>
+
+ <context-param>
+ <param-name>keycloak.sessionIdMapperUpdater.classes</param-name>
+ <param-value>org.keycloak.adapters.saml.wildfly.infinispan.InfinispanSessionCacheIdMapperUpdater</param-value>
+ </context-param>
+</web-app>
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/resource/ResourcesTable.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/resource/ResourcesTable.java
index f5dc5a7..94ed572 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/resource/ResourcesTable.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/resource/ResourcesTable.java
@@ -66,14 +66,18 @@ public class ResourcesTable extends DataTable {
public ResourceRepresentation toRepresentation(WebElement row) {
ResourceRepresentation representation = null;
List<WebElement> tds = row.findElements(tagName("td"));
- if (!(tds.isEmpty() || tds.get(0).getText().isEmpty())) {
- representation = new ResourceRepresentation();
- representation.setName(tds.get(0).getText());
- representation.setType(tds.get(1).getText());
- representation.setUri(tds.get(2).getText());
- ResourceOwnerRepresentation owner = new ResourceOwnerRepresentation();
- owner.setName(tds.get(3).getText());
- representation.setOwner(owner);
+ try {
+ if (!(tds.isEmpty() || tds.get(0).getText().isEmpty())) {
+ representation = new ResourceRepresentation();
+ representation.setName(tds.get(0).getText());
+ representation.setType(tds.get(1).getText());
+ representation.setUri(tds.get(2).getText());
+ ResourceOwnerRepresentation owner = new ResourceOwnerRepresentation();
+ owner.setName(tds.get(3).getText());
+ representation.setOwner(owner);
+ }
+ } catch (IndexOutOfBoundsException cause) {
+ // is empty
}
return representation;
}
testsuite/integration-arquillian/tests/pom.xml 182(+180 -2)
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 4e29ec3..5ff3e02 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -41,7 +41,9 @@
<properties>
<auth.server>undertow</auth.server>
<auth.server.undertow>true</auth.server.undertow>
-
+ <auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
+ <auth.server.crossdc>false</auth.server.crossdc>
+
<auth.server.container>auth-server-${auth.server}</auth.server.container>
<auth.server.home>${containers.home}/${auth.server.container}</auth.server.home>
<auth.server.config.dir>${auth.server.home}</auth.server.config.dir>
@@ -65,7 +67,17 @@
<auth.server.remote>false</auth.server.remote>
<auth.server.profile/>
<auth.server.feature/>
-
+
+ <cache.server>undefined</cache.server>
+ <cache.server.container>cache-server-${cache.server}</cache.server.container>
+ <cache.server.home>${containers.home}/${cache.server.container}</cache.server.home>
+ <cache.server.port.offset>1010</cache.server.port.offset>
+ <cache.server.management.port>11000</cache.server.management.port>
+ <cache.server.console.output>true</cache.server.console.output>
+ <keycloak.connectionsInfinispan.remoteStoreServer>localhost</keycloak.connectionsInfinispan.remoteStoreServer>
+ <keycloak.connectionsInfinispan.remoteStorePort>12232</keycloak.connectionsInfinispan.remoteStorePort>
+ <keycloak.connectionsJpa.url.crossdc>jdbc:h2:mem:test-dc-shared</keycloak.connectionsJpa.url.crossdc>
+
<adapter.test.props/>
<migration.import.properties/>
<examples.home>${project.build.directory}/examples</examples.home>
@@ -229,6 +241,22 @@
<client.key.passphrase>${client.key.passphrase}</client.key.passphrase>
<auth.server.ocsp.responder.enabled>${auth.server.ocsp.responder.enabled}</auth.server.ocsp.responder.enabled>
+
+ <!--cache server properties-->
+ <auth.server.crossdc>${auth.server.crossdc}</auth.server.crossdc>
+ <auth.server.undertow.crossdc>${auth.server.undertow.crossdc}</auth.server.undertow.crossdc>
+
+ <cache.server>${cache.server}</cache.server>
+ <cache.server.port.offset>${cache.server.port.offset}</cache.server.port.offset>
+ <cache.server.container>${cache.server.container}</cache.server.container>
+ <cache.server.home>${cache.server.home}</cache.server.home>
+ <cache.server.console.output>${cache.server.console.output}</cache.server.console.output>
+ <cache.server.management.port>${cache.server.management.port}</cache.server.management.port>
+
+ <keycloak.connectionsInfinispan.remoteStorePort>${keycloak.connectionsInfinispan.remoteStorePort}</keycloak.connectionsInfinispan.remoteStorePort>
+ <keycloak.connectionsInfinispan.remoteStoreServer>${keycloak.connectionsInfinispan.remoteStoreServer}</keycloak.connectionsInfinispan.remoteStoreServer>
+
+ <keycloak.connectionsJpa.url.crossdc>${keycloak.connectionsJpa.url.crossdc}</keycloak.connectionsJpa.url.crossdc>
</systemPropertyVariables>
<properties>
<property>
@@ -289,6 +317,7 @@
<auth.server>wildfly</auth.server>
<auth.server.jboss>true</auth.server.jboss>
<auth.server.undertow>false</auth.server.undertow>
+ <auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
<auth.server.config.property.value>standalone.xml</auth.server.config.property.value>
<auth.server.config.dir>${auth.server.home}/standalone/configuration</auth.server.config.dir>
<h2.version>1.3.173</h2.version>
@@ -307,6 +336,7 @@
<auth.server>eap</auth.server>
<auth.server.jboss>true</auth.server.jboss>
<auth.server.undertow>false</auth.server.undertow>
+ <auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
<auth.server.config.property.value>standalone.xml</auth.server.config.property.value>
<auth.server.config.dir>${auth.server.home}/standalone/configuration</auth.server.config.dir>
<h2.version>1.3.173</h2.version>
@@ -320,6 +350,154 @@
</profile>
<profile>
+ <id>cache-server-infinispan</id>
+ <properties>
+ <cache.server>infinispan</cache.server>
+ <auth.server.undertow.crossdc>true</auth.server.undertow.crossdc>
+ <auth.server.crossdc>true</auth.server.crossdc>
+ <cache.server.jboss>true</cache.server.jboss>
+ <cache.server.config.dir>${cache.server.home}/standalone/configuration</cache.server.config.dir>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly.arquillian</groupId>
+ <artifactId>wildfly-arquillian-container-managed</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <!--requireActiveProfile 'auth-server-wildfly/eap' doesn't work unless the profiles are defined in all submodule poms
+ using requireProperty instead-->
+ <requireProperty>
+ <property>cache.server</property>
+ <regex>(infinispan)|(jdg)</regex>
+ <regexMessage>Profile "cache-server-infinispan" requires activation of profile "cache-server-infinispan" or "cache-server-jdg".</regexMessage>
+ </requireProperty>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-antrun-plugin</artifactId>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack-cache-server-infinispan</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-servers-cache-server-infinispan</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ <outputDirectory>${containers.home}</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+
+ <profile>
+ <id>cache-server-jdg</id>
+ <properties>
+ <cache.server>jdg</cache.server>
+ <auth.server.undertow.crossdc>true</auth.server.undertow.crossdc>
+ <auth.server.crossdc>true</auth.server.crossdc>
+ <cache.server.jboss>true</cache.server.jboss>
+ <cache.server.config.dir>${cache.server.home}/standalone/configuration</cache.server.config.dir>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly.arquillian</groupId>
+ <artifactId>wildfly-arquillian-container-managed</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <!--requireActiveProfile 'auth-server-wildfly/eap' doesn't work unless the profiles are defined in all submodule poms
+ using requireProperty instead-->
+ <requireProperty>
+ <property>cache.server</property>
+ <regex>(infinispan)|(jdg)</regex>
+ <regexMessage>Profile "cache-server-jdg" requires activation of profile "cache-server-infinispan" or "cache-server-jdg".</regexMessage>
+ </requireProperty>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-antrun-plugin</artifactId>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack-cache-server-jdg</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-servers-cache-server-jdg</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ <outputDirectory>${containers.home}</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+
+ <profile>
<id>auth-server-profile</id>
<activation>
<property>
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 6834ac8..a2dfe0c 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
@@ -320,6 +320,9 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
}
this.checkNameAvailability = function (onSuccess) {
+ if (!$scope.resource.name || $scope.resource.name.trim().length == 0) {
+ return;
+ }
ResourceServerResource.search({
realm : $route.current.params.realm,
client : client.id,
@@ -534,6 +537,9 @@ module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $rout
}
this.checkNameAvailability = function (onSuccess) {
+ if (!$scope.scope.name || $scope.scope.name.trim().length == 0) {
+ return;
+ }
ResourceServerScope.search({
realm : $route.current.params.realm,
client : client.id,
@@ -2017,6 +2023,9 @@ module.service("PolicyController", function($http, $route, $location, ResourceSe
}
this.checkNameAvailability = function (onSuccess) {
+ if (!$scope.policy.name || $scope.policy.name.trim().length == 0) {
+ return;
+ }
ResourceServerPolicy.search({
realm: $route.current.params.realm,
client: client.id,