From aa177dd9f9f6664b9cfc32dda64ee40d976dba57 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 1 Aug 2016 19:03:07 +0200 Subject: [PATCH 01/13] Ignore KcSamlBrokerTest and KcSamlSignedBrokerTest again --- .../java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java | 2 ++ .../org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java index cdacaa7874..7a8fb9ceb3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerTest.java @@ -1,5 +1,6 @@ package org.keycloak.testsuite.broker; +import org.junit.Ignore; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; @@ -12,6 +13,7 @@ import java.util.Map; import static org.keycloak.testsuite.broker.BrokerTestConstants.*; +@Ignore public class KcSamlBrokerTest extends AbstractBrokerTest { @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java index 128d1e020e..1a2eca83d5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java @@ -1,5 +1,6 @@ package org.keycloak.testsuite.broker; +import org.junit.Ignore; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -10,6 +11,7 @@ import java.util.Map; import static org.keycloak.testsuite.broker.BrokerTestConstants.*; +@Ignore public class KcSamlSignedBrokerTest extends KcSamlBrokerTest { @Override From 059011e82f415f08499803d0c13b333e92115709 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 1 Aug 2016 14:28:34 -0400 Subject: [PATCH 02/13] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c69a2bc2e3..837a3d08e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ before_script: - export MAVEN_SKIP_RC=true install: - - mvn install -Pdistribution -DskipTests=true -B -V -q + - mvn install -Pdistribution -DskipTests=true -B -V script: - mvn test -B From 7d1bdf6e819a0d0d50aa17e07910771bc6bee5e4 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 1 Aug 2016 16:39:06 -0400 Subject: [PATCH 03/13] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 837a3d08e7..c69a2bc2e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ before_script: - export MAVEN_SKIP_RC=true install: - - mvn install -Pdistribution -DskipTests=true -B -V + - mvn install -Pdistribution -DskipTests=true -B -V -q script: - mvn test -B From ae1a7542d88c603d6d8b836bc8892b95876f9e01 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Mon, 1 Aug 2016 18:01:09 -0300 Subject: [PATCH 04/13] [KEYCLOAK-3385] - Improvements to evaluation tool UI and result --- .../authorization/AbstractPolicyEnforcer.java | 4 + .../KeycloakAdapterPolicyEnforcer.java | 38 +++++- .../authorization/ResourceRepresentation.java | 10 ++ .../admin/PolicyEvaluationService.java | 10 +- .../PolicyEvaluationResponse.java | 118 ++++++++++++++++-- .../authorization/admin/util/Models.java | 22 ++++ .../AuthorizationTokenService.java | 53 +++++++- .../entitlement/EntitlementService.java | 94 +++++++++++--- .../permission/AbstractPermissionService.java | 79 +++++++----- .../authorization/util/Permissions.java | 46 ++++--- ...AbstractDefaultAuthzConfigAdapterTest.java | 2 +- .../messages/admin-messages_en.properties | 2 +- .../resources/js/authz/authz-controller.js | 5 + ...esource-server-policy-evaluate-result.html | 9 +- 14 files changed, 399 insertions(+), 93 deletions(-) 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 0c0fc236f9..cfe5ff9a97 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 @@ -140,6 +140,10 @@ public abstract class AbstractPolicyEnforcer { return true; } } + } else { + if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) { + return true; + } } } diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java index a77676292c..f1be944353 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java @@ -26,12 +26,17 @@ import org.keycloak.authorization.client.AuthorizationDeniedException; import org.keycloak.authorization.client.AuthzClient; import org.keycloak.authorization.client.representation.AuthorizationRequest; import org.keycloak.authorization.client.representation.AuthorizationResponse; +import org.keycloak.authorization.client.representation.EntitlementRequest; import org.keycloak.authorization.client.representation.EntitlementResponse; import org.keycloak.authorization.client.representation.PermissionRequest; import org.keycloak.authorization.client.representation.PermissionResponse; import org.keycloak.representations.AccessToken; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; +import org.keycloak.representations.idm.authorization.Permission; +import org.keycloak.util.JsonSerialization; +import java.util.ArrayList; +import java.util.HashSet; import java.util.Set; /** @@ -52,7 +57,6 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { while (retry > 0) { if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) { - original.setAuthorization(accessToken.getAuthorization()); return true; } @@ -62,6 +66,21 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { return false; } + AccessToken.Authorization authorization = original.getAuthorization(); + + if (authorization == null) { + authorization = new AccessToken.Authorization(); + authorization.setPermissions(new ArrayList()); + } + + AccessToken.Authorization newAuthorization = accessToken.getAuthorization(); + + if (newAuthorization != null) { + authorization.getPermissions().addAll(newAuthorization.getPermissions()); + } + + original.setAuthorization(authorization); + retry--; } @@ -107,8 +126,21 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { return null; } else { LOGGER.debug("Obtaining entitlements for authenticated user."); - EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId()); - return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl()); + AccessToken token = httpFacade.getSecurityContext().getToken(); + + if (token.getAuthorization() == null) { + EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId()); + return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl()); + } else { + EntitlementRequest request = new EntitlementRequest(); + PermissionRequest permissionRequest = new PermissionRequest(); + permissionRequest.setResourceSetId(pathConfig.getId()); + permissionRequest.setResourceSetName(pathConfig.getName()); + permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes())); + request.addPermission(permissionRequest); + EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request); + return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl()); + } } } catch (AuthorizationDeniedException e) { return null; diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java index 2776b0a330..c89a2d6305 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.net.URI; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; @@ -49,6 +50,7 @@ public class ResourceRepresentation { @JsonInclude(JsonInclude.Include.NON_EMPTY) private List policies; + private List typedScopes; /** * Creates a new instance. @@ -169,4 +171,12 @@ public class ResourceRepresentation { T test(Predicate t) { return null; } + + public void setTypedScopes(List typedScopes) { + this.typedScopes = typedScopes; + } + + public List getTypedScopes() { + return typedScopes; + } } 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 67ae7d4c9e..9f38732d7e 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java @@ -31,6 +31,7 @@ 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.Result; +import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.util.Permissions; import org.keycloak.models.ClientModel; @@ -147,14 +148,15 @@ public class PolicyEvaluationService { StoreFactory storeFactory = authorization.getStoreFactory(); - List scopes = givenScopes.stream().map(scopeName -> storeFactory.getScopeStore().findByName(scopeName, this.resourceServer.getId())).collect(Collectors.toList()); - if (resource.getId() != null) { Resource resourceModel = storeFactory.getResourceStore().findById(resource.getId()); - return Stream.of(new ResourcePermission(resourceModel, scopes, resourceServer)); + return Permissions.createResourcePermissions(resourceModel, givenScopes, authorization).stream(); } else if (resource.getType() != null) { - return storeFactory.getResourceStore().findByType(resource.getType()).stream().map(resource1 -> new ResourcePermission(resource1, scopes, resourceServer)); + Set finalGivenScopes = givenScopes; + return storeFactory.getResourceStore().findByType(resource.getType()).stream().flatMap(resource1 -> Permissions.createResourcePermissions(resource1, finalGivenScopes, authorization).stream()); } else { + ScopeStore scopeStore = storeFactory.getScopeStore(); + List scopes = givenScopes.stream().map(scopeName -> scopeStore.findByName(scopeName, this.resourceServer.getId())).collect(Collectors.toList()); List collect = scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer)).collect(Collectors.toList()); for (Scope scope : scopes) { diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java index cccb38ff39..ee6661dfad 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java +++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java @@ -22,22 +22,25 @@ import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.Decision.Effect; import org.keycloak.authorization.admin.util.Models; import org.keycloak.authorization.common.KeycloakIdentity; +import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.policy.evaluation.Result; import org.keycloak.authorization.policy.evaluation.Result.PolicyResult; import org.keycloak.authorization.util.Permissions; -import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.representations.AccessToken; -import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Pedro Igor @@ -59,7 +62,7 @@ public class PolicyEvaluationResponse { AccessToken accessToken = identity.getAccessToken(); AccessToken.Authorization authorizationData = new AccessToken.Authorization(); - authorizationData.setPermissions(Permissions.allPermits(results)); + authorizationData.setPermissions(Permissions.allPermits(results, authorization)); accessToken.setAuthorization(authorizationData); response.rpt = accessToken; @@ -99,18 +102,82 @@ public class PolicyEvaluationResponse { policies.add(toRepresentation(policy, authorization)); } - if (rep.getResource().getId() != null) { - if (!rep.getScopes().isEmpty()) { - rep.getResource().setName(rep.getResource().getName() + " with scopes " + rep.getScopes().stream().map(ScopeRepresentation::getName).collect(Collectors.toList())); - } - } - rep.setPolicies(policies); } resultsRep.sort((o1, o2) -> o1.getResource().getName().compareTo(o2.getResource().getName())); - response.results = resultsRep; + Map groupedResults = new HashMap<>(); + + resultsRep.forEach(evaluationResultRepresentation -> { + EvaluationResultRepresentation result = groupedResults.get(evaluationResultRepresentation.getResource().getId()); + ResourceRepresentation resource = evaluationResultRepresentation.getResource(); + + if (result == null) { + groupedResults.put(resource.getId(), evaluationResultRepresentation); + result = evaluationResultRepresentation; + } + + if (result.getStatus().equals(Effect.PERMIT) || (evaluationResultRepresentation.getStatus().equals(Effect.PERMIT) && result.getStatus().equals(Effect.DENY))) { + result.setStatus(Effect.PERMIT); + } + + List scopes = result.getScopes(); + + if (scopes == null) { + scopes = new ArrayList<>(); + result.setScopes(scopes); + } + + List currentScopes = evaluationResultRepresentation.getScopes(); + + if (currentScopes != null) { + for (ScopeRepresentation scope : currentScopes) { + if (!scopes.contains(scope)) { + scopes.add(scope); + } + if (evaluationResultRepresentation.getStatus().equals(Effect.PERMIT)) { + result.getAllowedScopes().add(scope); + } + } + } + + if (resource.getId() != null) { + if (!scopes.isEmpty()) { + result.getResource().setName(evaluationResultRepresentation.getResource().getName() + " with scopes " + scopes.stream().flatMap((Function>) scopeRepresentation -> Arrays.asList(scopeRepresentation.getName()).stream()).collect(Collectors.toList())); + } else { + result.getResource().setName(evaluationResultRepresentation.getResource().getName()); + } + } else { + result.getResource().setName("Any Resource with Scopes " + scopes.stream().flatMap((Function>) scopeRepresentation -> Arrays.asList(scopeRepresentation.getName()).stream()).collect(Collectors.toList())); + } + + List policies = result.getPolicies(); + + for (PolicyResultRepresentation policy : new ArrayList<>(evaluationResultRepresentation.getPolicies())) { + if (!policies.contains(policy)) { + policies.add(policy); + } else { + policy = policies.get(policies.indexOf(policy)); + } + + if (policy.getStatus().equals(Effect.DENY)) { + Policy policyModel = authorization.getStoreFactory().getPolicyStore().findById(policy.getPolicy().getId()); + for (ScopeRepresentation scope : policyModel.getScopes().stream().map(scope -> Models.toRepresentation(scope, authorization)).collect(Collectors.toList())) { + if (!policy.getScopes().contains(scope)) { + policy.getScopes().add(scope); + } + } + for (ScopeRepresentation scope : currentScopes) { + if (!policy.getScopes().contains(scope)) { + policy.getScopes().add(scope); + } + } + } + } + }); + + response.results = groupedResults.values().stream().collect(Collectors.toList()); return response; } @@ -147,6 +214,7 @@ public class PolicyEvaluationResponse { private List scopes; private List policies; private Effect status; + private List allowedScopes = new ArrayList<>(); public void setResource(final ResourceRepresentation resource) { this.resource = resource; @@ -179,6 +247,14 @@ public class PolicyEvaluationResponse { public Effect getStatus() { return status; } + + public void setAllowedScopes(List allowedScopes) { + this.allowedScopes = allowedScopes; + } + + public List getAllowedScopes() { + return allowedScopes; + } } public static class PolicyResultRepresentation { @@ -186,6 +262,7 @@ public class PolicyEvaluationResponse { private PolicyRepresentation policy; private Effect status; private List associatedPolicies; + private List scopes = new ArrayList<>(); public PolicyRepresentation getPolicy() { return policy; @@ -210,5 +287,26 @@ public class PolicyEvaluationResponse { public void setAssociatedPolicies(final List associatedPolicies) { this.associatedPolicies = associatedPolicies; } + + @Override + public int hashCode() { + return this.policy.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final PolicyResultRepresentation policy = (PolicyResultRepresentation) o; + return this.policy.equals(policy.getPolicy()); + } + + public void setScopes(List scopes) { + this.scopes = scopes; + } + + public List getScopes() { + return scopes; + } } } diff --git a/services/src/main/java/org/keycloak/authorization/admin/util/Models.java b/services/src/main/java/org/keycloak/authorization/admin/util/Models.java index 7e9d66d9e2..f233e98575 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/util/Models.java +++ b/services/src/main/java/org/keycloak/authorization/admin/util/Models.java @@ -25,6 +25,7 @@ import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.ClientModel; @@ -48,6 +49,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -245,6 +247,26 @@ public final class Models { return scope; }).collect(Collectors.toSet())); + resource.setTypedScopes(new ArrayList<>()); + + if (resource.getType() != null) { + ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore(); + for (Resource typed : resourceStore.findByType(resource.getType())) { + if (typed.getOwner().equals(resourceServer.getClientId()) && !typed.getId().equals(resource.getId())) { + resource.setTypedScopes(typed.getScopes().stream().map(model1 -> { + ScopeRepresentation scope = new ScopeRepresentation(); + scope.setId(model1.getId()); + scope.setName(model1.getName()); + String iconUri = model1.getIconUri(); + if (iconUri != null) { + scope.setIconUri(iconUri); + } + return scope; + }).filter(scopeRepresentation -> !resource.getScopes().contains(scopeRepresentation)).collect(Collectors.toList())); + } + } + } + resource.setPolicies(new ArrayList<>()); Set policies = new HashSet<>(); 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 0fd347488d..4e7c2a9f0b 100644 --- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java +++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java @@ -25,10 +25,14 @@ import org.keycloak.authorization.authorization.representation.AuthorizationResp import org.keycloak.authorization.common.KeycloakEvaluationContext; import org.keycloak.authorization.common.KeycloakIdentity; 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; +import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.util.Permissions; import org.keycloak.authorization.util.Tokens; @@ -51,6 +55,8 @@ 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; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -58,6 +64,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -100,7 +107,7 @@ public class AuthorizationTokenService { authorization.evaluators().from(createPermissions(ticket, authorizationRequest, authorization), evaluationContext).evaluate(new DecisionResultCollector() { @Override public void onComplete(List results) { - List entitlements = Permissions.allPermits(results); + List entitlements = Permissions.allPermits(results, authorization); if (entitlements.isEmpty()) { HashMap error = new HashMap<>(); @@ -139,13 +146,36 @@ public class AuthorizationTokenService { resource = storeFactory.getResourceStore().findByName(requestedResource.getName(), ticket.getResourceServerId()); } - if (resource == null) { + if (resource == null && (requestedResource.getScopes() == null || requestedResource.getScopes().isEmpty())) { throw new ErrorResponseException("invalid_resource", "Resource with id [" + requestedResource.getId() + "] or name [" + requestedResource.getName() + "] does not exist.", Status.FORBIDDEN); } Set requestedScopes = requestedResource.getScopes(); + Set collect = requestedScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet()); - permissionsToEvaluate.put(resource.getId(), requestedScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet())); + if (resource != null) { + permissionsToEvaluate.put(resource.getId(), collect); + } else { + ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore(); + ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); + List resources = new ArrayList(); + + resources.addAll(resourceStore.findByScope(requestedScopes.stream().map(scopeRepresentation -> { + Scope scope = scopeStore.findByName(scopeRepresentation.getName(), ticket.getResourceServerId()); + + if (scope == null) { + return null; + } + + return scope.getId(); + }).filter(s -> s != null).collect(Collectors.toList()).toArray(new String[requestedScopes.size()]))); + + for (Resource resource1 : resources) { + permissionsToEvaluate.put(resource1.getId(), collect); + } + + permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", collect); + } }); String rpt = request.getRpt(); @@ -193,10 +223,23 @@ public class AuthorizationTokenService { } } + ResourceServer resourceServer = authorization.getStoreFactory().getResourceServerStore().findById(ticket.getResourceServerId()); + return permissionsToEvaluate.entrySet().stream() .flatMap((Function>, Stream>) entry -> { - Resource entryResource = storeFactory.getResourceStore().findById(entry.getKey()); - return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream(); + String key = entry.getKey(); + + if ("$KC_SCOPE_PERMISSION".equals(key)) { + ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); + List scopes = entry.getValue().stream().map(scopeName -> { + Scope byName = scopeStore.findByName(scopeName, resourceServer.getId()); + return byName; + }).collect(Collectors.toList()); + return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream(); + } else { + Resource entryResource = storeFactory.getResourceStore().findById(key); + return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream(); + } }).collect(Collectors.toList()); } 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 e5986b78ae..e28106397f 100644 --- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java +++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java @@ -27,9 +27,12 @@ import org.keycloak.authorization.entitlement.representation.EntitlementRequest; 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.store.ResourceStore; +import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.util.Permissions; import org.keycloak.authorization.util.Tokens; @@ -41,6 +44,7 @@ import org.keycloak.models.RealmModel; 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.ErrorResponseException; import org.keycloak.services.resources.Cors; @@ -56,6 +60,8 @@ 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; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -115,7 +121,7 @@ public class EntitlementService { @Override protected void onComplete(List results) { - List entitlements = Permissions.allPermits(results); + List entitlements = Permissions.allPermits(results, authorization); if (entitlements.isEmpty()) { HashMap error = new HashMap<>(); @@ -168,10 +174,17 @@ public class EntitlementService { @Override protected void onComplete(List results) { - List entitlements = Permissions.allPermits(results); + List entitlements = Permissions.allPermits(results, authorization); if (entitlements.isEmpty()) { - asyncResponse.resume(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)); + HashMap error = new HashMap<>(); + + error.put(OAuth2Constants.ERROR, "not_authorized"); + + 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)))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build()); } @@ -203,18 +216,42 @@ public class EntitlementService { resource = storeFactory.getResourceStore().findByName(requestedResource.getResourceSetName(), resourceServer.getId()); } - if (resource == null) { + if (resource == null && (requestedResource.getScopes() == null || requestedResource.getScopes().isEmpty())) { throw new ErrorResponseException("invalid_resource", "Resource with id [" + requestedResource.getResourceSetId() + "] or name [" + requestedResource.getResourceSetName() + "] does not exist.", Status.FORBIDDEN); } - permissionsToEvaluate.put(resource.getId(), requestedResource.getScopes()); + Set requestedScopes = requestedResource.getScopes().stream().map(ScopeRepresentation::new).collect(Collectors.toSet()); + Set collect = requestedScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet()); + + if (resource != null) { + permissionsToEvaluate.put(resource.getId(), collect); + } else { + ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore(); + ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); + List resources = new ArrayList(); + + resources.addAll(resourceStore.findByScope(requestedScopes.stream().map(scopeRepresentation -> { + Scope scope = scopeStore.findByName(scopeRepresentation.getName(), resourceServer.getId()); + + if (scope == null) { + return null; + } + + return scope.getId(); + }).filter(s -> s != null).collect(Collectors.toList()).toArray(new String[requestedScopes.size()]))); + + for (Resource resource1 : resources) { + permissionsToEvaluate.put(resource1.getId(), collect); + } + + permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", collect); + } }); String rpt = entitlementRequest.getRpt(); if (rpt != null && !"".equals(rpt)) { KeycloakContext context = authorization.getKeycloakSession().getContext(); - if (!Tokens.verifySignature(rpt, context.getRealm().getPublicKey())) { throw new ErrorResponseException("invalid_rpt", "RPT signature is invalid", Status.FORBIDDEN); } @@ -231,28 +268,47 @@ public class EntitlementService { AccessToken.Authorization authorizationData = requestingPartyToken.getAuthorization(); if (authorizationData != null) { - authorizationData.getPermissions().forEach(permission -> { - Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId()); + List permissions = authorizationData.getPermissions(); - if (resourcePermission != null) { - Set scopes = permissionsToEvaluate.get(resourcePermission.getId()); + if (permissions != null) { + permissions.forEach(permission -> { + Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId()); - if (scopes == null) { - scopes = new HashSet<>(); - permissionsToEvaluate.put(resourcePermission.getId(), scopes); + if (resourcePermission != null) { + Set scopes = permissionsToEvaluate.get(resourcePermission.getId()); + + if (scopes == null) { + scopes = new HashSet<>(); + permissionsToEvaluate.put(resourcePermission.getId(), scopes); + } + + Set scopePermission = permission.getScopes(); + + if (scopePermission != null) { + scopes.addAll(scopePermission); + } } - - scopes.addAll(permission.getScopes()); - } - }); + }); + } } } } return permissionsToEvaluate.entrySet().stream() .flatMap((Function>, Stream>) entry -> { - Resource entryResource = storeFactory.getResourceStore().findById(entry.getKey()); - return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream(); + String key = entry.getKey(); + + if ("$KC_SCOPE_PERMISSION".equals(key)) { + ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); + List scopes = entry.getValue().stream().map(scopeName -> { + Scope byName = scopeStore.findByName(scopeName, resourceServer.getId()); + return byName; + }).collect(Collectors.toList()); + return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream(); + } else { + Resource entryResource = storeFactory.getResourceStore().findById(key); + return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream(); + } }).collect(Collectors.toList()); } } diff --git a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java index 910cee5569..eb215f97d0 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java +++ b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java @@ -32,6 +32,7 @@ import org.keycloak.services.ErrorResponseException; import javax.ws.rs.core.Response; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -64,51 +65,69 @@ public class AbstractPermissionService { return request.stream().map(request1 -> { String resourceSetId = request1.getResourceSetId(); String resourceSetName = request1.getResourceSetName(); + boolean resourceNotProvider = resourceSetId == null && resourceSetName == null; - if (resourceSetId == null && resourceSetName == null) { - throw new ErrorResponseException("invalid_resource_set_id", "Resource id or name not provided.", Response.Status.BAD_REQUEST); - } - - Resource resource; - - if (resourceSetId != null) { - resource = storeFactory.getResourceStore().findById(resourceSetId); - } else { - resource = storeFactory.getResourceStore().findByName(resourceSetName, this.resourceServer.getId()); - } - - if (resource == null) { - if (resourceSetId != null) { - throw new ErrorResponseException("nonexistent_resource_set_id", "Resource set with id[" + resourceSetId + "] does not exists in this server.", Response.Status.BAD_REQUEST); - } else { - throw new ErrorResponseException("nonexistent_resource_set_name", "Resource set with name[" + resourceSetName + "] does not exists in this server.", Response.Status.BAD_REQUEST); + if (resourceNotProvider) { + if ((request1.getScopes() == null || request1.getScopes().isEmpty())) { + throw new ErrorResponseException("invalid_resource_set_id", "Resource id or name not provided.", Response.Status.BAD_REQUEST); } } - return new ResourceRepresentation(resource.getName(), verifyRequestedScopes(request1, resource)); + Resource resource = null; + + if (!resourceNotProvider) { + if (resourceSetId != null) { + resource = storeFactory.getResourceStore().findById(resourceSetId); + } else { + resource = storeFactory.getResourceStore().findByName(resourceSetName, this.resourceServer.getId()); + } + + if (resource == null) { + if (resourceSetId != null) { + throw new ErrorResponseException("nonexistent_resource_set_id", "Resource set with id[" + resourceSetId + "] does not exists in this server.", Response.Status.BAD_REQUEST); + } else { + throw new ErrorResponseException("nonexistent_resource_set_name", "Resource set with name[" + resourceSetName + "] does not exists in this server.", Response.Status.BAD_REQUEST); + } + } + } + + Set scopes = verifyRequestedScopes(request1, resource); + + if (resource != null) { + if (scopes.isEmpty() && !request1.getScopes().isEmpty()) { + return new ResourceRepresentation(null, request1.getScopes().stream().map(ScopeRepresentation::new).collect(Collectors.toSet())); + } + return new ResourceRepresentation(resource.getName(), scopes); + } + + return new ResourceRepresentation(null, scopes); }).collect(Collectors.toList()); } private Set verifyRequestedScopes(PermissionRequest request, Resource resource) { return request.getScopes().stream().map(scopeName -> { - for (Scope scope : resource.getScopes()) { - if (scope.getName().equals(scopeName)) { - return new ScopeRepresentation(scopeName); + if (resource != null) { + for (Scope scope : resource.getScopes()) { + if (scope.getName().equals(scopeName)) { + return new ScopeRepresentation(scopeName); + } } - } - for (Resource baseResource : authorization.getStoreFactory().getResourceStore().findByType(resource.getType())) { - if (baseResource.getOwner().equals(resource.getResourceServer().getClientId())) { - for (Scope baseScope : baseResource.getScopes()) { - if (baseScope.getName().equals(scopeName)) { - return new ScopeRepresentation(scopeName); + for (Resource baseResource : authorization.getStoreFactory().getResourceStore().findByType(resource.getType())) { + if (baseResource.getOwner().equals(resource.getResourceServer().getClientId())) { + for (Scope baseScope : baseResource.getScopes()) { + if (baseScope.getName().equals(scopeName)) { + return new ScopeRepresentation(scopeName); + } } } } - } - throw new ErrorResponseException("invalid_scope", "Scope [" + scopeName + " is not valid.", Response.Status.BAD_REQUEST); - }).collect(Collectors.toSet()); + return null; + } else { + return new ScopeRepresentation(scopeName); + } + }).filter(scopeRepresentation -> scopeRepresentation != null).collect(Collectors.toSet()); } private String createPermissionTicket(List resources) { 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 ebc57c2951..116ddd69be 100644 --- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java +++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java @@ -110,38 +110,54 @@ public final class Permissions { return permissions; } - public static List allPermits(List evaluation) { + public static List allPermits(List evaluation, AuthorizationProvider authorizationProvider) { Map permissions = new HashMap<>(); for (Result evaluationResult : evaluation) { ResourcePermission permission = evaluationResult.getPermission(); Set scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()); + if (evaluationResult.getEffect().equals(Effect.DENY)) { continue; } + + List resources = new ArrayList<>(); Resource resource = permission.getResource(); if (resource != null) { - String resourceId = resource.getId(); - String resourceName = resource.getName(); - Permission evalPermission = permissions.get(resource.getId()); + resources.add(resource); + } else { + List permissionScopes = permission.getScopes(); - if (evalPermission == null) { - evalPermission = new Permission(resourceId, resourceName, scopes); - permissions.put(resourceId, evalPermission); + if (!permissionScopes.isEmpty()) { + ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore(); + resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()).toArray(new String[permissionScopes.size()]))); } + } - if (scopes != null && !scopes.isEmpty()) { - Set finalScopes = evalPermission.getScopes(); + if (!resources.isEmpty()) { + for (Resource allowedResource : resources) { + String resourceId = allowedResource.getId(); + String resourceName = allowedResource.getName(); + Permission evalPermission = permissions.get(allowedResource.getId()); - if (finalScopes == null) { - finalScopes = new HashSet(); - evalPermission.setScopes(finalScopes); + if (evalPermission == null) { + evalPermission = new Permission(resourceId, resourceName, scopes); + permissions.put(resourceId, evalPermission); } - for (String scopeName : scopes) { - if (!finalScopes.contains(scopeName)) { - finalScopes.add(scopeName); + if (scopes != null && !scopes.isEmpty()) { + Set finalScopes = evalPermission.getScopes(); + + if (finalScopes == null) { + finalScopes = new HashSet(); + evalPermission.setScopes(finalScopes); + } + + for (String scopeName : scopes) { + if (!finalScopes.contains(scopeName)) { + finalScopes.add(scopeName); + } } } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java index 46666747ca..a6167eb0f5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractDefaultAuthzConfigAdapterTest.java @@ -60,8 +60,8 @@ public abstract class AbstractDefaultAuthzConfigAdapterTest extends AbstractExam @Test public void testDefaultAuthzConfig() throws Exception { try { - this.deployer.deploy(RESOURCE_SERVER_ID); configureAuthorizationServices(); + this.deployer.deploy(RESOURCE_SERVER_ID); login(); diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 1f39b06b66..9e023ac008 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1092,7 +1092,7 @@ authz-evaluation-any-resource-with-scopes=Any resource with scope(s) authz-evaluation-no-result=Could not obtain any result for the given authorization request. Check if the provided resource(s) or scope(s) are associated with any policy. authz-evaluation-no-policies-resource=No policies were found for this resource. authz-evaluation-result.tooltip=The overall result for this permission request. -authz-evaluation-scopes.tooltip=The requested scopes. +authz-evaluation-scopes.tooltip=The list of allowed scopes. authz-evaluation-policies.tooltip=Details about which policies were evaluated and their decisions. authz-evaluation-authorization-data=Response authz-evaluation-authorization-data.tooltip=Represents a token carrying authorization data as a result of the processing of an authorization request. This representation is basically what Keycloak issues to clients asking for permissions. Check the 'authorization' claim for the permissions that were granted based on the current authorization request. 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 d772f92f0e..e4d627389b 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 @@ -1321,6 +1321,11 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio rsrid: $scope.newResource._id }, function (data) { $scope.scopes = data.scopes; + if (data.typedScopes) { + for (i=0;i{{:: 'authz-scopes' | translate}}
- {{:: 'authz-any-scope' | translate}} + {{:: 'authz-no-scopes-available' | translate}}
    -
  • +
  • {{scope.name}}
@@ -38,15 +38,14 @@
{{:: 'authz-evaluation-no-policies-resource' | translate}} -
-
    +
  • {{policyResult.policy.name}} decision was {{policyResult.status}} {{policyResult.status}} - by {{policyResult.policy.decisionStrategy}} decision. + by {{policyResult.policy.decisionStrategy}} decision. {{policyResult.scopes.length > 0 ? 'Denied Scopes:' : ''}} {{scope.name}}{{$last ? '' : ', '}}{{policyResult.scopes.length > 0 ? '.' : ''}}
    • Date: Mon, 1 Aug 2016 22:41:25 +0200 Subject: [PATCH 05/13] Fix travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c69a2bc2e3..ee548a01b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ before_script: - export MAVEN_SKIP_RC=true install: - - mvn install -Pdistribution -DskipTests=true -B -V -q + - mvn install -Pdistribution -DskipTests=true -B -V | grep -e "Maven" -e "Java" -e "Building Keycloak" script: - mvn test -B From 9688880837695c1151278f01bdfaf2c91b49ce77 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 1 Aug 2016 14:51:17 +0200 Subject: [PATCH 06/13] Fix testsuite with mongo --- .../connections/mongo/DefaultMongoConnectionFactoryProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java index d8d87f5d5d..feabf8e742 100755 --- a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java +++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java @@ -79,6 +79,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro "org.keycloak.models.entities.RequiredActionProviderEntity", "org.keycloak.models.entities.PersistentUserSessionEntity", "org.keycloak.models.entities.PersistentClientSessionEntity", + "org.keycloak.models.entities.StorageProviderEntity", "org.keycloak.authorization.mongo.entities.PolicyEntity", "org.keycloak.authorization.mongo.entities.ResourceEntity", "org.keycloak.authorization.mongo.entities.ResourceServerEntity", From a5cbe113fd5c19ed7e7d3143a5cca86db369d521 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 1 Aug 2016 17:12:50 +0200 Subject: [PATCH 07/13] Migration fix --- .../org/keycloak/migration/migrators/MigrateTo2_1_0.java | 6 ++---- .../org/keycloak/migration/migrators/MigrationUtils.java | 8 ++++++++ .../org/keycloak/models/utils/RepresentationToModel.java | 4 ++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java index 8ff3664ad3..9e7b93130d 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java @@ -52,11 +52,9 @@ public class MigrateTo2_1_0 { private void migrateDefaultRequiredAction(RealmModel realm) { RequiredActionProviderModel otpAction = realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.CONFIGURE_TOTP.name()); - if (otpAction == null) return; - if (!otpAction.getProviderId().equals(UserModel.RequiredAction.CONFIGURE_TOTP.name())) return; - if (!otpAction.getName().equals("Configure Totp")) return; + MigrationUtils.updateOTPRequiredAction(otpAction); - otpAction.setName("Configure OTP"); + realm.updateRequiredActionProvider(otpAction); } // KEYCLOAK-3338: Changes to how role policy config is stored" diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java index 91f812e0bc..08da081b4e 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java @@ -47,4 +47,12 @@ public class MigrationUtils { } } + public static void updateOTPRequiredAction(RequiredActionProviderModel otpAction) { + if (otpAction == null) return; + if (!otpAction.getProviderId().equals(UserModel.RequiredAction.CONFIGURE_TOTP.name())) return; + if (!otpAction.getName().equals("Configure Totp")) return; + + otpAction.setName("Configure OTP"); + } + } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 3efca793af..2be6720e54 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -21,6 +21,7 @@ import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.hash.Pbkdf2PasswordHashProvider; +import org.keycloak.migration.migrators.MigrationUtils; import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.Constants; import org.keycloak.common.util.Base64; @@ -204,6 +205,9 @@ public class RepresentationToModel { if (rep.getRequiredActions() != null) { for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) { RequiredActionProviderModel model = toModel(action); + + MigrationUtils.updateOTPRequiredAction(model); + newRealm.addRequiredActionProvider(model); } } else { From 3b3368eeade283f070d51d3c40febd10cbfd686a Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 1 Aug 2016 17:37:53 +0200 Subject: [PATCH 08/13] KEYCLOAK-3237 Add scope=openid to KeycloakInstalled --- .../java/org/keycloak/adapters/installed/KeycloakInstalled.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java index fd8af9fc34..fc0e47fa50 100644 --- a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java +++ b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java @@ -119,6 +119,7 @@ public class KeycloakInstalled { .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) .queryParam(OAuth2Constants.STATE, state) + .queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID) .build().toString(); Desktop.getDesktop().browse(new URI(authUrl)); @@ -175,6 +176,7 @@ public class KeycloakInstalled { .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) + .queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID) .build().toString(); printer.println("Open the following URL in a browser. After login copy/paste the code back and press "); From 09693eb10830af6eb2e383635138259f7223bf77 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 1 Aug 2016 11:18:58 -0400 Subject: [PATCH 09/13] component model --- .../idm/ComponentRepresentation.java | 101 ++++++ .../idm/ComponentTypeRepresentation.java | 57 ++++ .../idm/StorageProviderRepresentation.java | 51 ++- .../info/ServerInfoRepresentation.java | 9 + examples/providers/pom.xml | 1 + .../user/ExampleUserStorageProvider.java | 118 +++++++ .../ExampleUserStorageProviderFactory.java | 60 ++++ .../examples/storage/user/UserAdapter.java | 135 ++++++++ .../examples/storage/user/UserEntity.java | 82 +++++ .../main/resources/META-INF/persistence.xml | 18 ++ ...eycloak.storage.UserStorageProviderFactory | 1 + .../mappers/TxAwareLDAPUserModelDelegate.java | 2 +- .../UserAttributeLDAPFederationMapper.java | 2 +- .../InfinispanStoreFactoryProvider.java | 2 +- .../models/cache/infinispan/RealmAdapter.java | 91 +++--- .../cache/infinispan/RealmCacheSession.java | 4 +- .../cache/infinispan/UserCacheSession.java | 9 +- .../infinispan/entities/CachedRealm.java | 26 +- .../InfinispanUserSessionProvider.java | 2 +- .../DefaultJpaConnectionProviderFactory.java | 2 +- .../jpa/JndiEntityManagerLookup.java | 25 +- .../keycloak/models/jpa/JpaRealmProvider.java | 8 +- .../keycloak/models/jpa/JpaUserProvider.java | 4 +- .../org/keycloak/models/jpa/RealmAdapter.java | 305 ++++++++---------- .../jpa/entities/ComponentConfigEntity.java | 110 +++++++ ...oviderEntity.java => ComponentEntity.java} | 107 +++--- .../models/jpa/entities/RealmEntity.java | 11 - .../jpa/JpaUserFederatedStorageProvider.java | 8 +- .../META-INF/jpa-changelog-2.1.0.xml | 40 +-- .../main/resources/META-INF/persistence.xml | 3 +- ...DefaultMongoConnectionFactoryProvider.java | 3 +- .../keycloak/adapters/MongoUserProvider.java | 4 +- .../mongo/keycloak/adapters/RealmAdapter.java | 276 +++++++--------- .../ComponentFactory.java} | 39 +-- .../keycloak/component/ComponentModel.java | 101 ++++++ .../ComponentValidationException.java} | 33 +- .../ConfiguredComponent.java} | 12 +- .../org/keycloak/models/KeycloakSession.java | 58 +++- .../java/org/keycloak/models/RealmModel.java | 29 +- .../models/UserFederationManager.java | 12 +- .../org/keycloak/models/UserProvider.java | 16 +- .../models/entities/ComponentEntity.java | 75 +++++ .../keycloak/models/entities/RealmEntity.java | 10 +- .../models/utils/KeycloakModelUtils.java | 40 +-- .../models/utils/ModelToRepresentation.java | 29 +- .../models/utils/RepresentationToModel.java | 11 + .../java/org/keycloak/storage/StorageId.java | 34 +- .../storage/StorageProviderModel.java | 97 ------ .../keycloak/storage/UserStorageManager.java | 58 ++-- .../keycloak/storage/UserStorageProvider.java | 9 +- .../storage/UserStorageProviderFactory.java | 85 +++++ .../storage/UserStorageProviderModel.java | 62 ++++ ...erSpi.java => UserStorageProviderSpi.java} | 6 +- .../storage/adapter/AbstractUserAdapter.java | 6 +- .../AbstractUserAdapterFederatedStorage.java | 7 +- .../storage/changeset/UserDataQuery.java | 46 --- .../storage/changeset/UserDataStore.java | 32 -- .../UserFederatedStorageProvider.java | 6 +- .../UserFederatedStorageProviderSpi.java | 2 - .../user/UserRegistrationProvider.java | 1 - .../services/org.keycloak.provider.Spi | 2 +- .../partialimport/PartialImportManager.java | 8 +- .../services/DefaultKeycloakSession.java | 18 +- .../filters/KeycloakSessionServletFilter.java | 6 +- .../managers/DefaultBruteForceProtector.java | 6 +- .../resources/IdentityBrokerService.java | 10 +- .../resources/KeycloakApplication.java | 32 +- .../AuthenticationManagementResource.java | 10 +- .../admin/ClientInitialAccessResource.java | 4 +- .../resources/admin/ComponentResource.java | 152 +++++++++ .../resources/admin/RealmAdminResource.java | 2 +- .../resources/admin/UsersResource.java | 18 +- .../admin/info/ServerInfoAdminResource.java | 28 +- .../ClusterAwareScheduledTaskRunner.java | 4 +- .../scheduled/ScheduledTaskRunner.java | 6 +- .../keycloak/testsuite/KeycloakServer.java | 9 +- .../adapter/AdapterTestStrategy.java | 14 +- .../adapter/CookieTokenStoreAdapterTest.java | 4 +- .../AbstractAuthorizationTest.java | 4 +- .../federation/storage/UserMapStorage.java | 29 +- .../storage/UserMapStorageFactory.java | 16 +- .../storage/UserPropertyFileStorage.java | 18 +- .../UserPropertyFileStorageFactory.java | 20 +- ...nStorageTest.java => UserStorageTest.java} | 64 ++-- .../testsuite/model/AbstractModelTest.java | 8 +- .../keycloak/testsuite/model/AdapterTest.java | 2 +- .../keycloak/testsuite/model/CacheTest.java | 8 +- .../testsuite/model/TransactionsTest.java | 14 +- .../testsuite/model/UserModelTest.java | 4 +- .../testsuite/rule/AbstractKeycloakRule.java | 24 +- .../keycloak/testsuite/rule/KeycloakRule.java | 4 +- ...ycloak.storage.UserStorageProviderFactory} | 0 92 files changed, 2097 insertions(+), 1044 deletions(-) create mode 100755 core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java create mode 100644 core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java rename server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java => core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java (62%) create mode 100644 examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java create mode 100644 examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.java create mode 100644 examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java create mode 100644 examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java create mode 100644 examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml create mode 100644 examples/providers/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory rename server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java => model/jpa/src/main/java/org/keycloak/connections/jpa/JndiEntityManagerLookup.java (51%) create mode 100755 model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java rename model/jpa/src/main/java/org/keycloak/models/jpa/entities/{StorageProviderEntity.java => ComponentEntity.java} (52%) rename server-spi/src/main/java/org/keycloak/{storage/StorageProviderFactory.java => component/ComponentFactory.java} (53%) mode change 100755 => 100644 create mode 100755 server-spi/src/main/java/org/keycloak/component/ComponentModel.java rename server-spi/src/main/java/org/keycloak/{storage/StorageProvider.java => component/ComponentValidationException.java} (54%) rename server-spi/src/main/java/org/keycloak/{storage/changeset/UserDataLookup.java => component/ConfiguredComponent.java} (66%) create mode 100755 server-spi/src/main/java/org/keycloak/models/entities/ComponentEntity.java delete mode 100755 server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java rename server-spi/src/main/java/org/keycloak/storage/{StorageProviderSpi.java => UserStorageProviderSpi.java} (89%) delete mode 100644 server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java delete mode 100644 server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java rename testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/{UserFederationStorageTest.java => UserStorageTest.java} (80%) rename testsuite/integration/src/test/resources/META-INF/services/{org.keycloak.storage.StorageProviderFactory => org.keycloak.storage.UserStorageProviderFactory} (100%) diff --git a/core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java new file mode 100755 index 0000000000..bbc4a42b97 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java @@ -0,0 +1,101 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.representations.idm; + +import org.keycloak.common.util.MultivaluedHashMap; + +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class ComponentRepresentation { + + private String id; + private String name; + private String providerId; + private String providerType; + private String parentId; + private MultivaluedHashMap config; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public String getProviderType() { + return providerType; + } + + public void setProviderType(String providerType) { + this.providerType = providerType; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + public MultivaluedHashMap getConfig() { + return config; + } + + public void setConfig(MultivaluedHashMap config) { + this.config = config; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ComponentRepresentation that = (ComponentRepresentation) o; + + if (!id.equals(that.id)) return false; + + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java new file mode 100644 index 0000000000..8ba0da6e41 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.representations.idm; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class ComponentTypeRepresentation { + protected String id; + protected String helpText; + protected List properties; + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getHelpText() { + return helpText; + } + + public void setHelpText(String helpText) { + this.helpText = helpText; + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } +} diff --git a/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java b/core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java similarity index 62% rename from server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java rename to core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java index 3845ba0d61..04bf174636 100755 --- a/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java +++ b/core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java @@ -15,20 +15,36 @@ * limitations under the License. */ -package org.keycloak.models.entities; +package org.keycloak.representations.idm; import java.util.Map; /** - * @author Bill Burke - * @version $Revision: 1 $ + * @author Marek Posolda */ -public class StorageProviderEntity extends AbstractIdentifiableEntity { - protected String providerName; - protected Map config; - protected int priority; - protected String displayName; +public class StorageProviderRepresentation { + private String id; + private String displayName; + private String providerName; + private Map config; + private int priority; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } public String getProviderName() { return providerName; @@ -38,6 +54,7 @@ public class StorageProviderEntity extends AbstractIdentifiableEntity { this.providerName = providerName; } + public Map getConfig() { return config; } @@ -54,12 +71,20 @@ public class StorageProviderEntity extends AbstractIdentifiableEntity { this.priority = priority; } - public String getDisplayName() { - return displayName; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StorageProviderRepresentation that = (StorageProviderRepresentation) o; + + if (!id.equals(that.id)) return false; + + return true; } - public void setDisplayName(String displayName) { - this.displayName = displayName; + @Override + public int hashCode() { + return id.hashCode(); } - } diff --git a/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java index c3aeeeb328..59d400e485 100755 --- a/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java @@ -17,6 +17,7 @@ package org.keycloak.representations.info; +import org.keycloak.representations.idm.ComponentTypeRepresentation; import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation; @@ -43,6 +44,7 @@ public class ServerInfoRepresentation { private Map> protocolMapperTypes; private Map> builtinProtocolMappers; private Map> clientInstallations; + private Map> componentTypes; private List passwordPolicies; @@ -144,4 +146,11 @@ public class ServerInfoRepresentation { this.passwordPolicies = passwordPolicies; } + public Map> getComponentTypes() { + return componentTypes; + } + + public void setComponentTypes(Map> componentTypes) { + this.componentTypes = componentTypes; + } } diff --git a/examples/providers/pom.xml b/examples/providers/pom.xml index 20b4eaa7e9..fcde8b08e3 100755 --- a/examples/providers/pom.xml +++ b/examples/providers/pom.xml @@ -37,5 +37,6 @@ authenticator rest domain-extension + user-storage-jpa diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java new file mode 100644 index 0000000000..07053339d2 --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java @@ -0,0 +1,118 @@ +/* + * 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.examples.storage.user; + +import org.keycloak.component.ComponentModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.user.UserLookupProvider; +import org.keycloak.storage.user.UserRegistrationProvider; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ExampleUserStorageProvider implements UserStorageProvider, UserLookupProvider, UserRegistrationProvider { + protected EntityManager em; + protected ComponentModel model; + protected KeycloakSession session; + + public ExampleUserStorageProvider(EntityManager em, ComponentModel model, KeycloakSession session) { + this.em = em; + this.model = model; + this.session = session; + } + + @Override + public void preRemove(RealmModel realm) { + + } + + @Override + public void preRemove(RealmModel realm, GroupModel group) { + + } + + @Override + public void preRemove(RealmModel realm, RoleModel role) { + + } + + @Override + public void close() { + em.close(); + } + + @Override + public UserModel getUserById(String id, RealmModel realm) { + String persistenceId = StorageId.externalId(id); + UserEntity entity = em.find(UserEntity.class, persistenceId); + if (entity == null) return null; + return new UserAdapter(session, realm, model, entity); + } + + @Override + public UserModel getUserByUsername(String username, RealmModel realm) { + TypedQuery query = em.createNamedQuery("getUserByUsername", UserEntity.class); + query.setParameter("username", username); + List result = query.getResultList(); + if (result.isEmpty()) return null; + return new UserAdapter(session, realm, model, result.get(0)); + } + + @Override + public UserModel getUserByEmail(String email, RealmModel realm) { + TypedQuery query = em.createNamedQuery("getUserByEmail", UserEntity.class); + query.setParameter("email", email); + List result = query.getResultList(); + if (result.isEmpty()) return null; + return new UserAdapter(session, realm, model, result.get(0)); + } + + @Override + public UserModel addUser(RealmModel realm, String username) { + UserEntity entity = new UserEntity(); + entity.setId(KeycloakModelUtils.generateId()); + entity.setUsername(username); + em.persist(entity); + return new UserAdapter(session, realm, model, entity); + } + + @Override + public boolean removeUser(RealmModel realm, UserModel user) { + String persistenceId = StorageId.externalId(user.getId()); + UserEntity entity = em.find(UserEntity.class, persistenceId); + if (entity == null) return false; + em.remove(entity); + return true; + } + + @Override + public void grantToAllUsers(RealmModel realm, RoleModel role) { + + } +} diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.java new file mode 100644 index 0000000000..963a558227 --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.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.examples.storage.user; + +import org.keycloak.Config; +import org.keycloak.component.ComponentModel; +import org.keycloak.connections.jpa.JndiEntityManagerLookup; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.storage.UserStorageProviderFactory; + +import javax.persistence.EntityManager; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ExampleUserStorageProviderFactory implements UserStorageProviderFactory { + protected String jndiName = "java:jboss/ExampleUserEntityManagerFactory"; + + @Override + public ExampleUserStorageProvider create(KeycloakSession session, ComponentModel model) { + EntityManager em = JndiEntityManagerLookup.getSessionEntityManager(session, jndiName); + return new ExampleUserStorageProvider(em, model, session); + } + + @Override + public String getId() { + return "example-user-storage"; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } +} diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java new file mode 100644 index 0000000000..f851904d52 --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java @@ -0,0 +1,135 @@ +/* + * 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.examples.storage.user; + +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserAdapter extends AbstractUserAdapterFederatedStorage { + protected UserEntity entity; + protected String keycloakId; + + public UserAdapter(KeycloakSession session, RealmModel realm, ComponentModel model, UserEntity entity) { + super(session, realm, model); + this.entity = entity; + keycloakId = StorageId.keycloakId(model, entity.getId()); + } + + @Override + public String getUsername() { + return entity.getUsername(); + } + + @Override + public void setUsername(String username) { + entity.setUsername(username); + + } + + @Override + public void setEmail(String email) { + entity.setEmail(email); + } + + @Override + public String getEmail() { + return entity.getEmail(); + } + + @Override + public String getId() { + return keycloakId; + } + + @Override + public void updateCredential(UserCredentialModel cred) { + if (cred.getType().equals(UserCredentialModel.PASSWORD)) { + entity.setPassword(cred.getValue()); + } else { + super.updateCredential(cred); + } + } + + @Override + public void setSingleAttribute(String name, String value) { + if (name.equals("phone")) { + entity.setPhone(value); + } else { + super.setSingleAttribute(name, value); + } + } + + @Override + public void removeAttribute(String name) { + if (name.equals("phone")) { + entity.setPhone(null); + } else { + super.removeAttribute(name); + } + } + + @Override + public void setAttribute(String name, List values) { + if (name.equals("phone")) { + entity.setPhone(values.get(0)); + } else { + super.setAttribute(name, values); + } + } + + @Override + public String getFirstAttribute(String name) { + if (name.equals("phone")) { + return entity.getPhone(); + } else { + return super.getFirstAttribute(name); + } + } + + @Override + public Map> getAttributes() { + Map> attrs = super.getAttributes(); + MultivaluedHashMap all = new MultivaluedHashMap<>(); + all.putAll(attrs); + all.add("phone", entity.getPhone()); + return all; + } + + @Override + public List getAttribute(String name) { + if (name.equals("phone")) { + List phone = new LinkedList<>(); + phone.add(entity.getPhone()); + return phone; + } else { + return super.getAttribute(name); + } + } +} diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java new file mode 100644 index 0000000000..c9227d2368 --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java @@ -0,0 +1,82 @@ +/* + * 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.examples.storage.user; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="getUserByUsername", query="select u from UserEntity u where u.username = :username"), + @NamedQuery(name="getUserByEmail", query="select u from UserEntity u where u.email = :email"), +}) +@Entity +public class UserEntity { + @Id + private String id; + + + private String username; + private String email; + private String password; + private String phone; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } +} diff --git a/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml b/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..5af362d198 --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,18 @@ + + + + java:jboss/datasources/ExampleUserDS + + org.keycloak.examples.storage.user.UserEntity + + + + + + + + diff --git a/examples/providers/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/examples/providers/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory new file mode 100644 index 0000000000..1634d11e1b --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory @@ -0,0 +1 @@ +org.keycloak.examples.storage.user.ExampleUserStorageProviderFactory \ No newline at end of file diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java index 622ebb683b..9126a668b8 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java @@ -70,7 +70,7 @@ public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate { logger.trace("Starting and enlisting transaction for object " + ldapUser.getDn().toString()); } - this.provider.getSession().getTransaction().enlistAfterCompletion(transaction); + this.provider.getSession().getTransactionManager().enlistAfterCompletion(transaction); } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java index af910a7c2c..b577326a0f 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java @@ -163,7 +163,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap UserModel that = session.userStorage().getUserByEmail(email, realm); if (that != null && !that.getId().equals(user.getId())) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); String exceptionMessage = String.format("Can't import user '%s' from LDAP because email '%s' already exists in Keycloak. Existing user with this email is '%s'", user.getUsername(), email, that.getUsername()); throw new ModelDuplicateException(exceptionMessage, UserModel.EMAIL); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java index cea4388fb3..56a385fe12 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java @@ -40,7 +40,7 @@ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvide InfinispanStoreFactoryProvider(KeycloakSession delegate) { this.session = delegate; this.transaction = new CacheTransaction(); - this.session.getTransaction().enlistAfterCompletion(transaction); + this.session.getTransactionManager().enlistAfterCompletion(transaction); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index 69f0510dec..ffaf46911f 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -19,11 +19,10 @@ package org.keycloak.models.cache.infinispan; import org.keycloak.Config; import org.keycloak.common.enums.SslRequired; +import org.keycloak.component.ComponentModel; import org.keycloak.models.*; -import org.keycloak.models.cache.CacheRealmProvider; import org.keycloak.models.cache.infinispan.entities.CachedRealm; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.StorageProviderModel; import java.security.Key; import java.security.PrivateKey; @@ -746,48 +745,6 @@ public class RealmAdapter implements RealmModel { } - @Override - public StorageProviderModel addStorageProvider(StorageProviderModel provider) { - getDelegateForUpdate(); - return updated.addStorageProvider(provider); - } - - @Override - public void updateStorageProvider(StorageProviderModel provider) { - getDelegateForUpdate(); - updated.updateStorageProvider(provider); - - } - - @Override - public void removeStorageProvider(StorageProviderModel provider) { - getDelegateForUpdate(); - updated.removeStorageProvider(provider); - - } - - @Override - public void setStorageProviders(List providers) { - getDelegateForUpdate(); - updated.setStorageProviders(providers); - - } - - @Override - public List getStorageProviders() { - if (isUpdated()) return updated.getStorageProviders(); - return cached.getStorageProviders(); - } - - @Override - public StorageProviderModel getStorageProvider(String id) { - if (isUpdated()) return updated.getStorageProvider(id); - for (StorageProviderModel model : cached.getStorageProviders()) { - if (model.getId().equals(id)) return model; - } - return null; - } - @Override public String getLoginTheme() { if (isUpdated()) return updated.getLoginTheme(); @@ -1451,6 +1408,52 @@ public class RealmAdapter implements RealmModel { return cacheSession.getClientTemplateById(id, this); } + @Override + public ComponentModel addComponentModel(ComponentModel model) { + getDelegateForUpdate(); + return updated.addComponentModel(model); + } + @Override + public void updateComponent(ComponentModel component) { + getDelegateForUpdate(); + updated.updateComponent(component); + } + + @Override + public void removeComponent(ComponentModel component) { + getDelegateForUpdate(); + updated.removeComponent(component); + + } + + @Override + public void removeComponents(String parentId) { + getDelegateForUpdate(); + updated.removeComponents(parentId); + + } + + @Override + public List getComponents(String parentId, String providerType) { + if (isUpdated()) return updated.getComponents(parentId, providerType); + List components = cached.getComponentsByParent().getList(parentId + providerType); + if (components == null) return Collections.EMPTY_LIST; + return Collections.unmodifiableList(components); + } + + @Override + public List getComponents() { + if (isUpdated()) return updated.getComponents(); + List results = new LinkedList<>(); + results.addAll(cached.getComponents().values()); + return Collections.unmodifiableList(results); + } + + @Override + public ComponentModel getComponent(String id) { + if (isUpdated()) return updated.getComponent(id); + return cached.getComponents().get(id); + } } 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 2a3880d67b..7a72964c63 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 @@ -132,8 +132,8 @@ public class RealmCacheSession implements CacheRealmProvider { this.cache = cache; this.session = session; this.startupRevision = cache.getCurrentCounter(); - session.getTransaction().enlistPrepare(getPrepareTransaction()); - session.getTransaction().enlistAfterCompletion(getAfterTransaction()); + session.getTransactionManager().enlistPrepare(getPrepareTransaction()); + session.getTransactionManager().enlistAfterCompletion(getAfterTransaction()); } public long getStartupRevision() { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java index c5e4c302b1..3016ae8435 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java @@ -19,6 +19,7 @@ package org.keycloak.models.cache.infinispan; import org.jboss.logging.Logger; import org.keycloak.common.constants.ServiceAccountConstants; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; @@ -39,7 +40,6 @@ import org.keycloak.models.cache.infinispan.entities.CachedUser; import org.keycloak.models.cache.infinispan.entities.CachedUserConsent; import org.keycloak.models.cache.infinispan.entities.CachedUserConsents; import org.keycloak.models.cache.infinispan.entities.UserListQuery; -import org.keycloak.storage.StorageProviderModel; import java.util.*; @@ -65,7 +65,7 @@ public class UserCacheSession implements CacheUserProvider { this.cache = cache; this.session = session; this.startupRevision = cache.getCurrentCounter(); - session.getTransaction().enlistAfterCompletion(getTransaction()); + session.getTransactionManager().enlistAfterCompletion(getTransaction()); } @Override @@ -665,7 +665,8 @@ public class UserCacheSession implements CacheUserProvider { } @Override - public void preRemove(RealmModel realm, StorageProviderModel provider) { - getDelegate().preRemove(realm, provider); + public void preRemove(RealmModel realm, ComponentModel component) { + getDelegate().preRemove(realm, component); + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java index 6dcc0d9670..80997c3e77 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java @@ -18,6 +18,7 @@ package org.keycloak.models.cache.infinispan.entities; import org.keycloak.common.enums.SslRequired; +import org.keycloak.component.ComponentModel; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticatorConfigModel; @@ -29,20 +30,14 @@ import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.OTPPolicy; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; -import org.keycloak.models.RealmProvider; import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.models.cache.infinispan.RealmCache; import org.keycloak.common.util.MultivaluedHashMap; -import org.keycloak.storage.StorageProviderModel; -import java.io.Serializable; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; @@ -111,7 +106,8 @@ public class CachedRealm extends AbstractRevisioned { protected List requiredCredentials; protected List userFederationProviders; - protected List storageProviders; + protected MultivaluedHashMap componentsByParent = new MultivaluedHashMap<>(); + protected Map components = new HashMap<>(); protected MultivaluedHashMap userFederationMappers = new MultivaluedHashMap(); protected Set userFederationMapperSet; protected List identityProviders; @@ -208,7 +204,6 @@ public class CachedRealm extends AbstractRevisioned { requiredCredentials = model.getRequiredCredentials(); userFederationProviders = model.getUserFederationProviders(); - storageProviders = model.getStorageProviders(); userFederationMapperSet = model.getUserFederationMappers(); for (UserFederationMapperModel mapper : userFederationMapperSet) { this.userFederationMappers.add(mapper.getFederationProviderId(), mapper); @@ -279,6 +274,13 @@ public class CachedRealm extends AbstractRevisioned { resetCredentialsFlow = model.getResetCredentialsFlow(); clientAuthenticationFlow = model.getClientAuthenticationFlow(); + for (ComponentModel component : model.getComponents()) { + componentsByParent.add(component.getParentId() + component.getProviderType(), component); + } + for (ComponentModel component : model.getComponents()) { + components.put(component.getId(), component); + } + } protected void cacheClientTemplates(RealmModel model) { @@ -602,7 +604,11 @@ public class CachedRealm extends AbstractRevisioned { return requiredActionProviderList; } - public List getStorageProviders() { - return storageProviders; + public MultivaluedHashMap getComponentsByParent() { + return componentsByParent; + } + + public Map getComponents() { + return components; } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index 960ea250b0..614fa9fa9d 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java @@ -81,7 +81,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { this.loginFailureCache = loginFailureCache; this.tx = new InfinispanKeycloakTransaction(); - session.getTransaction().enlistAfterCompletion(tx); + session.getTransactionManager().enlistAfterCompletion(tx); } protected Cache getCache(boolean offline) { diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java index 431a638f09..4272fb8869 100755 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java @@ -63,7 +63,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide EntityManager em = emf.createEntityManager(); em = PersistenceExceptionConverter.create(em); - session.getTransaction().enlist(new JpaKeycloakTransaction(em)); + session.getTransactionManager().enlist(new JpaKeycloakTransaction(em)); return new DefaultJpaConnectionProvider(em); } diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/JndiEntityManagerLookup.java similarity index 51% rename from server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java rename to model/jpa/src/main/java/org/keycloak/connections/jpa/JndiEntityManagerLookup.java index 741f3b7546..5291793edf 100644 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/JndiEntityManagerLookup.java @@ -14,20 +14,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.storage.changeset; +package org.keycloak.connections.jpa; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; -import java.util.List; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface UserDataCredentialValidator { - boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input); - boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input); +public class JndiEntityManagerLookup { + public static EntityManager getSessionEntityManager(KeycloakSession session, String entityManagerFactoryJndiName) { + EntityManagerFactory factory = null; + try { + factory = (EntityManagerFactory)new InitialContext().lookup(entityManagerFactoryJndiName); + } catch (NamingException e) { + throw new RuntimeException(e); + } + EntityManager em = factory.createEntityManager(); + session.getTransactionManager().enlist(new JpaKeycloakTransaction(em)); + return em; + } } 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 2cf74ad270..f5d266602d 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 @@ -34,7 +34,6 @@ import org.keycloak.models.jpa.entities.GroupEntity; import org.keycloak.models.jpa.entities.RealmEntity; import org.keycloak.models.jpa.entities.RoleEntity; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.StorageProviderModel; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -128,9 +127,6 @@ public class JpaRealmProvider implements RealmProvider { em.refresh(realm); final RealmAdapter adapter = new RealmAdapter(session, em, realm); session.users().preRemove(adapter); - for (StorageProviderModel provider : adapter.getStorageProviders()) { - adapter.removeStorageProvider(provider); - } realm.getDefaultGroups().clear(); em.flush(); @@ -141,6 +137,10 @@ public class JpaRealmProvider implements RealmProvider { .setParameter("realm", realm).executeUpdate(); num = em.createNamedQuery("deleteGroupsByRealm") .setParameter("realm", realm).executeUpdate(); + num = em.createNamedQuery("deleteComponentConfigByRealm") + .setParameter("realm", realm).executeUpdate(); + num = em.createNamedQuery("deleteComponentByRealm") + .setParameter("realm", realm).executeUpdate(); TypedQuery query = em.createNamedQuery("getClientIdsByRealm", String.class); query.setParameter("realm", realm.getId()); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index 02403ee3aa..a928e58feb 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -17,6 +17,7 @@ package org.keycloak.models.jpa; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; @@ -42,7 +43,6 @@ import org.keycloak.models.jpa.entities.UserEntity; import org.keycloak.models.utils.CredentialValidation; import org.keycloak.models.utils.DefaultRoles; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.StorageProviderModel; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -712,7 +712,7 @@ public class JpaUserProvider implements UserProvider { } @Override - public void preRemove(RealmModel realm, StorageProviderModel link) { + public void preRemove(RealmModel realm, ComponentModel component) { } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index d6837aa20e..093ebf342d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -18,7 +18,8 @@ package org.keycloak.models.jpa; import org.jboss.logging.Logger; -import org.keycloak.connections.jpa.util.JpaUtils; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.common.enums.SslRequired; import org.keycloak.jose.jwk.JWKBuilder; import org.keycloak.models.AuthenticationExecutionModel; @@ -43,8 +44,6 @@ import org.keycloak.models.UserFederationProviderCreationEventImpl; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.jpa.entities.*; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderModel; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -1003,179 +1002,6 @@ public class RealmAdapter implements RealmModel, JpaModel { return null; } - @Override - public StorageProviderModel getStorageProvider(String id) { - StorageProviderEntity entity = em.find(StorageProviderEntity.class, id); - if (entity == null) return null; - return toModel(entity); - } - - @Override - public List getStorageProviders() { - List entities = realm.getStorageProviders(); - if (entities.isEmpty()) return Collections.EMPTY_LIST; - List copy = new LinkedList<>(); - for (StorageProviderEntity entity : entities) { - copy.add(entity); - - } - List result = new LinkedList<>(); - for (StorageProviderEntity entity : copy) { - result.add(toModel(entity)); - } - Collections.sort(result, StorageProviderModel.comparator); - - return Collections.unmodifiableList(result); - } - - protected StorageProviderModel toModel(StorageProviderEntity entity) { - StorageProviderModel model = new StorageProviderModel(); - model.setId(entity.getId()); - model.setProviderName(entity.getProviderName()); - model.getConfig().putAll(entity.getConfig()); - model.setPriority(entity.getPriority()); - model.setDisplayName(entity.getDisplayName()); - return model; - } - - @Override - public StorageProviderModel addStorageProvider(StorageProviderModel model) { - KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), null, getStorageProviders()); - - String id = KeycloakModelUtils.generateId(); - StorageProviderEntity entity = new StorageProviderEntity(); - entity.setId(id); - entity.setRealm(realm); - entity.setProviderName(model.getProviderName()); - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - String displayName = model.getDisplayName(); - if (model.getDisplayName() == null) { - displayName = id; - } - entity.setDisplayName(displayName); - em.persist(entity); - realm.getStorageProviders().add(entity); - em.flush(); - StorageProviderModel providerModel = toModel(entity); - - return providerModel; - } - - @Override - public void removeStorageProvider(StorageProviderModel provider) { - Iterator it = realm.getStorageProviders().iterator(); - while (it.hasNext()) { - StorageProviderEntity entity = it.next(); - if (entity.getId().equals(provider.getId())) { - - session.users().preRemove(this, provider); - - it.remove(); - em.remove(entity); - return; - } - } - } - @Override - public void updateStorageProvider(StorageProviderModel model) { - KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getStorageProviders()); - - Iterator it = realm.getStorageProviders().iterator(); - while (it.hasNext()) { - StorageProviderEntity entity = it.next(); - if (entity.getId().equals(model.getId())) { - String displayName = model.getDisplayName(); - if (displayName != null) { - entity.setDisplayName(model.getDisplayName()); - } - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - entity.setProviderName(model.getProviderName()); - entity.setPriority(model.getPriority()); - break; - } - } - } - - @Override - public void setStorageProviders(List providers) { - for (StorageProviderModel currentProvider : providers) { - KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers); - } - - Iterator it = realm.getStorageProviders().iterator(); - while (it.hasNext()) { - StorageProviderEntity entity = it.next(); - boolean found = false; - for (StorageProviderModel model : providers) { - if (entity.getId().equals(model.getId())) { - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - entity.setProviderName(model.getProviderName()); - String displayName = model.getDisplayName(); - if (displayName != null) { - entity.setDisplayName(displayName); - } - found = true; - break; - } - - } - if (found) continue; - session.users().preRemove(this, toModel(entity)); - removeFederationMappersForProvider(entity.getId()); - - it.remove(); - em.remove(entity); - } - - List add = new LinkedList<>(); - for (StorageProviderModel model : providers) { - boolean found = false; - for (StorageProviderEntity entity : realm.getStorageProviders()) { - if (entity.getId().equals(model.getId())) { - found = true; - break; - } - } - if (!found) add.add(model); - } - - for (StorageProviderModel model : add) { - StorageProviderEntity entity = new StorageProviderEntity(); - if (model.getId() != null) { - entity.setId(model.getId()); - } else { - String id = KeycloakModelUtils.generateId(); - entity.setId(id); - model.setId(id); - } - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - entity.setProviderName(model.getProviderName()); - entity.setPriority(model.getPriority()); - String displayName = model.getDisplayName(); - if (displayName == null) { - displayName = entity.getId(); - } - entity.setDisplayName(displayName); - entity.setRealm(realm); - em.persist(entity); - realm.getStorageProviders().add(entity); - - } - } - - protected StorageProviderEntity getStorageProviderEntityById(String id) { - for (StorageProviderEntity entity : realm.getStorageProviders()) { - if (entity.getId().equals(id)) { - return entity; - } - } - return null; - } - @Override public RoleModel getRole(String name) { return session.realms().getRealmRole(this, name); @@ -2280,4 +2106,131 @@ public class RealmAdapter implements RealmModel, JpaModel { return session.realms().getClientTemplateById(id, this); } + @Override + public ComponentModel addComponentModel(ComponentModel model) { + ComponentEntity c = new ComponentEntity(); + if (model.getId() == null) { + c.setId(KeycloakModelUtils.generateId()); + } else { + c.setId(model.getId()); + } + c.setName(model.getName()); + c.setParentId(model.getParentId()); + c.setProviderType(model.getProviderType()); + c.setProviderId(model.getProviderId()); + c.setRealm(realm); + em.persist(c); + setConfig(model, c); + model.setId(c.getId()); + return model; + } + + protected void setConfig(ComponentModel model, ComponentEntity c) { + for (String key : model.getConfig().keySet()) { + List vals = model.getConfig().get(key); + for (String val : vals) { + ComponentConfigEntity config = new ComponentConfigEntity(); + config.setId(KeycloakModelUtils.generateId()); + config.setName(key); + config.setValue(val); + config.setComponent(c); + em.persist(config); + } + } + } + + @Override + public void updateComponent(ComponentModel component) { + ComponentEntity c = em.find(ComponentEntity.class, component.getId()); + if (c == null) return; + c.setName(component.getName()); + c.setProviderId(component.getProviderId()); + c.setProviderType(component.getProviderType()); + c.setParentId(component.getParentId()); + em.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate(); + em.flush(); + setConfig(component, c); + + + } + + @Override + public void removeComponent(ComponentModel component) { + ComponentEntity c = em.find(ComponentEntity.class, component.getId()); + if (c == null) return; + session.users().preRemove(this, component); + em.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate(); + em.remove(c); + } + + @Override + public void removeComponents(String parentId) { + TypedQuery query = em.createNamedQuery("getComponentIdsByParent", String.class) + .setParameter("realm", realm) + .setParameter("parentId", parentId); + List results = query.getResultList(); + if (results.isEmpty()) return; + for (String id : results) { + session.users().preRemove(this, getComponent(id)); + } + em.createNamedQuery("deleteComponentConfigByParent").setParameter("parentId", parentId).executeUpdate(); + em.createNamedQuery("deleteComponentByParent").setParameter("parentId", parentId).executeUpdate(); + + } + + @Override + public List getComponents(String parentId, String providerType) { + if (parentId == null) parentId = getId(); + TypedQuery query = em.createNamedQuery("getComponentsByParentAndType", ComponentEntity.class) + .setParameter("realm", realm) + .setParameter("parentId", parentId) + .setParameter("providerType", providerType); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (ComponentEntity c : results) { + ComponentModel model = entityToModel(c); + rtn.add(model); + + } + return rtn; + } + + protected ComponentModel entityToModel(ComponentEntity c) { + ComponentModel model = new ComponentModel(); + model.setId(c.getId()); + model.setName(c.getName()); + model.setProviderType(c.getProviderType()); + model.setProviderId(c.getProviderId()); + model.setParentId(c.getParentId()); + MultivaluedHashMap config = new MultivaluedHashMap<>(); + TypedQuery configQuery = em.createNamedQuery("getComponentConfig", ComponentConfigEntity.class) + .setParameter("component", c); + List configResults = configQuery.getResultList(); + for (ComponentConfigEntity configEntity : configResults) { + config.add(configEntity.getName(), configEntity.getValue()); + } + model.setConfig(config); + return model; + } + + @Override + public List getComponents() { + TypedQuery query = em.createNamedQuery("getComponents", ComponentEntity.class) + .setParameter("realm", realm); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (ComponentEntity c : results) { + ComponentModel model = entityToModel(c); + rtn.add(model); + + } + return rtn; + } + + @Override + public ComponentModel getComponent(String id) { + ComponentEntity c = em.find(ComponentEntity.class, id); + if (c == null) return null; + return entityToModel(c); + } } \ No newline at end of file diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java new file mode 100755 index 0000000000..bf141a8f94 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.models.jpa.entities; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="getComponentConfig", query="select attr from ComponentConfigEntity attr where attr.component = :component"), + @NamedQuery(name="deleteComponentConfigByComponent", query="delete from ComponentConfigEntity attr where attr.component = :component"), + @NamedQuery(name="deleteComponentConfigByRealm", query="delete from ComponentConfigEntity attr where attr.component IN (select u from ComponentEntity u where u.realm=:realm)"), + @NamedQuery(name="deleteComponentConfigByParent", query="delete from ComponentConfigEntity attr where attr.component IN (select u from ComponentEntity u where u.parentId=:parentId)"), +}) +@Table(name="COMPONENT_CONFIG") +@Entity +public class ComponentConfigEntity { + + @Id + @Column(name="ID", length = 36) + @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL + protected String id; + + @ManyToOne(fetch= FetchType.LAZY) + @JoinColumn(name = "COMPONENT_ID") + protected ComponentEntity component; + + @Column(name = "NAME") + protected String name; + @Column(name = "VALUE") + protected String value; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public ComponentEntity getComponent() { + return component; + } + + public void setComponent(ComponentEntity component) { + this.component = component; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof ComponentConfigEntity)) return false; + + ComponentConfigEntity that = (ComponentConfigEntity) o; + + if (!id.equals(that.getId())) return false; + + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java similarity index 52% rename from model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java rename to model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java index f663795007..69dad0da7d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java @@ -19,6 +19,7 @@ package org.keycloak.models.jpa.entities; import javax.persistence.Access; import javax.persistence.AccessType; +import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; @@ -28,16 +29,28 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.MapKeyColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; import java.util.Map; /** - * @author Marek Posolda * @author Bill Burke */ +@NamedQueries({ + @NamedQuery(name="getComponents", query="select attr from ComponentEntity attr where attr.realm = :realm"), + @NamedQuery(name="getComponentsByParentAndType", query="select attr from ComponentEntity attr where attr.realm = :realm and attr.providerType = :providerType and attr.parentId = :parentId"), + @NamedQuery(name="getComponentIdsByParent", query="select attr.id from ComponentEntity attr where attr.realm = :realm and attr.parentId = :parentId"), + @NamedQuery(name="deleteComponentByRealm", query="delete from ComponentEntity c where c.realm = :realm"), + @NamedQuery(name="deleteComponentByParent", query="delete from ComponentEntity c where c.parentId = :parentId") +}) @Entity -@Table(name="STORAGE_PROVIDER") -public class StorageProviderEntity { +@Table(name="COMPONENT") +public class ComponentEntity { @Id @Column(name="ID", length = 36) @@ -48,19 +61,17 @@ public class StorageProviderEntity { @JoinColumn(name = "REALM_ID") protected RealmEntity realm; - @Column(name="PROVIDER_NAME") - private String providerName; - @Column(name="PRIORITY") - private int priority; + @Column(name="NAME") + protected String name; - @ElementCollection - @MapKeyColumn(name="name") - @Column(name="VALUE") - @CollectionTable(name="STORAGE_PROVIDER_CONFIG", joinColumns={ @JoinColumn(name="STORAGE_PROVIDER_ID") }) - private Map config; + @Column(name="PROVIDER_TYPE") + protected String providerType; - @Column(name="DISPLAY_NAME") - private String displayName; + @Column(name="PROVIDER_ID") + protected String providerId; + + @Column(name="PARENT_ID") + protected String parentId; public String getId() { return id; @@ -70,6 +81,38 @@ public class StorageProviderEntity { this.id = id; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getProviderType() { + return providerType; + } + + public void setProviderType(String providerType) { + this.providerType = providerType; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + public RealmEntity getRealm() { return realm; } @@ -78,45 +121,13 @@ public class StorageProviderEntity { this.realm = realm; } - public String getProviderName() { - return providerName; - } - - public void setProviderName(String providerName) { - this.providerName = providerName; - } - - public int getPriority() { - return priority; - } - - public void setPriority(int priority) { - this.priority = priority; - } - - public Map getConfig() { - return config; - } - - public void setConfig(Map config) { - this.config = config; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; - if (!(o instanceof StorageProviderEntity)) return false; + if (!(o instanceof ComponentEntity)) return false; - StorageProviderEntity that = (StorageProviderEntity) o; + ComponentEntity that = (ComponentEntity) o; if (!id.equals(that.getId())) return false; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java index 3c9ae5fd22..e45d819800 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java @@ -144,9 +144,6 @@ public class RealmEntity { @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") List userFederationProviders = new ArrayList<>(); - @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") - List storageProviders = new ArrayList<>(); - @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") Collection userFederationMappers = new ArrayList(); @@ -554,14 +551,6 @@ public class RealmEntity { this.userFederationProviders = userFederationProviders; } - public List getStorageProviders() { - return storageProviders; - } - - public void setStorageProviders(List storageProviders) { - this.storageProviders = storageProviders; - } - public Collection getUserFederationMappers() { return userFederationMappers; } diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java index d64a3ead7e..f6710dd3d5 100644 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java @@ -17,6 +17,7 @@ package org.keycloak.storage.jpa; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.GroupModel; @@ -31,11 +32,10 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; -import org.keycloak.models.jpa.entities.CredentialEntity; import org.keycloak.models.utils.FederatedCredentials; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.storage.StorageId; -import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.federated.UserAttributeFederatedStorage; import org.keycloak.storage.federated.UserBrokerLinkFederatedStorage; import org.keycloak.storage.federated.UserConsentFederatedStorage; @@ -719,7 +719,9 @@ public class JpaUserFederatedStorageProvider implements } @Override - public void preRemove(RealmModel realm, StorageProviderModel model) { + public void preRemove(RealmModel realm, ComponentModel model) { + if (!model.getProviderType().equals(UserStorageProvider.class.getName())) return; + em.createNamedQuery("deleteBrokerLinkByStorageProvider") .setParameter("storageProviderId", model.getId()) .executeUpdate(); diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml index 988662d1e2..9b01aceeef 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml @@ -149,22 +149,26 @@ - - - - - - - - - - + - - - + + + + + + + + + + + + + + + + @@ -185,10 +189,10 @@ - - - + + + + + \ No newline at end of file diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml index d912033b8a..0b2ff231ed 100755 --- a/model/jpa/src/main/resources/META-INF/persistence.xml +++ b/model/jpa/src/main/resources/META-INF/persistence.xml @@ -25,7 +25,8 @@ org.keycloak.models.jpa.entities.RealmEntity org.keycloak.models.jpa.entities.RealmAttributeEntity org.keycloak.models.jpa.entities.RequiredCredentialEntity - org.keycloak.models.jpa.entities.StorageProviderEntity + org.keycloak.models.jpa.entities.ComponentConfigEntity + org.keycloak.models.jpa.entities.ComponentEntity org.keycloak.models.jpa.entities.UserFederationProviderEntity org.keycloak.models.jpa.entities.UserFederationMapperEntity org.keycloak.models.jpa.entities.RoleEntity diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java index feabf8e742..440886f18d 100755 --- a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java +++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java @@ -22,7 +22,6 @@ import java.net.UnknownHostException; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; import javax.net.ssl.SSLSocketFactory; @@ -142,7 +141,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro lazyInit(session); TransactionMongoStoreInvocationContext invocationContext = new TransactionMongoStoreInvocationContext(mongoStore); - session.getTransaction().enlist(new MongoKeycloakTransaction(invocationContext)); + session.getTransactionManager().enlist(new MongoKeycloakTransaction(invocationContext)); return new DefaultMongoConnectionProvider(db, mongoStore, invocationContext); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java index ad811d4e6a..96d1f91ea4 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java @@ -21,6 +21,7 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; +import org.keycloak.component.ComponentModel; import org.keycloak.connections.mongo.api.MongoStore; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.ClientModel; @@ -44,7 +45,6 @@ import org.keycloak.models.entities.UserConsentEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; import org.keycloak.models.utils.CredentialValidation; -import org.keycloak.storage.StorageProviderModel; import java.util.ArrayList; import java.util.Collections; @@ -632,7 +632,7 @@ public class MongoUserProvider implements UserProvider { } @Override - public void preRemove(RealmModel realm, StorageProviderModel link) { + public void preRemove(RealmModel realm, ComponentModel component) { } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index cb6bba7c67..8b945effd7 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -20,6 +20,8 @@ package org.keycloak.models.mongo.keycloak.adapters; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.common.enums.SslRequired; import org.keycloak.jose.jwk.JWKBuilder; @@ -47,20 +49,18 @@ import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.entities.AuthenticationExecutionEntity; import org.keycloak.models.entities.AuthenticationFlowEntity; import org.keycloak.models.entities.AuthenticatorConfigEntity; +import org.keycloak.models.entities.ComponentEntity; import org.keycloak.models.entities.IdentityProviderEntity; import org.keycloak.models.entities.IdentityProviderMapperEntity; import org.keycloak.models.entities.RequiredActionProviderEntity; import org.keycloak.models.entities.RequiredCredentialEntity; -import org.keycloak.models.entities.StorageProviderEntity; import org.keycloak.models.entities.UserFederationMapperEntity; import org.keycloak.models.entities.UserFederationProviderEntity; import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity; import org.keycloak.models.mongo.keycloak.entities.MongoClientTemplateEntity; -import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity; import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity; import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.StorageProviderModel; import java.security.Key; import java.security.PrivateKey; @@ -1170,166 +1170,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme updateRealm(); } - @Override - public StorageProviderModel addStorageProvider(StorageProviderModel model) { - KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), null, getStorageProviders()); - - StorageProviderEntity entity = new StorageProviderEntity(); - entity.setId(KeycloakModelUtils.generateId()); - entity.setPriority(model.getPriority()); - entity.setProviderName(model.getProviderName()); - entity.setConfig(model.getConfig()); - String displayName = model.getDisplayName(); - if (displayName == null) { - displayName = entity.getId(); - } - entity.setDisplayName(displayName); - realm.getStorageProviders().add(entity); - updateRealm(); - - StorageProviderModel providerModel = new StorageProviderModel(entity.getId(), model.getProviderName(), - model.getConfig(), model.getPriority(), displayName); - - - return providerModel; - } - - @Override - public void updateStorageProvider(StorageProviderModel provider) { - KeycloakModelUtils.ensureUniqueDisplayName(provider.getDisplayName(), provider, getStorageProviders()); - - Iterator it = realm.getStorageProviders().iterator(); - while (it.hasNext()) { - StorageProviderEntity entity = it.next(); - if (entity.getId().equals(provider.getId())) { - entity.setProviderName(provider.getProviderName()); - entity.setConfig(provider.getConfig()); - entity.setPriority(provider.getPriority()); - String displayName = provider.getDisplayName(); - if (displayName != null) { - entity.setDisplayName(provider.getDisplayName()); - } - } - } - updateRealm(); - - } - - @Override - public void removeStorageProvider(StorageProviderModel provider) { - Iterator it = realm.getStorageProviders().iterator(); - while (it.hasNext()) { - StorageProviderEntity entity = it.next(); - if (entity.getId().equals(provider.getId())) { - session.users().preRemove(this, new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName() - )); - - it.remove(); - } - } - updateRealm(); - - } - - @Override - public void setStorageProviders(List providers) { - for (StorageProviderModel currentProvider : providers) { - KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers); - } - - List existingProviders = realm.getStorageProviders(); - List toRemove = new LinkedList<>(); - for (StorageProviderEntity entity : existingProviders) { - boolean found = false; - for (StorageProviderModel model : providers) { - if (entity.getId().equals(model.getId())) { - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - entity.setProviderName(model.getProviderName()); - String displayName = model.getDisplayName(); - if (displayName != null) { - entity.setDisplayName(displayName); - } - found = true; - break; - } - - } - if (found) continue; - session.users().preRemove(this, new StorageProviderModel(entity.getId(), entity.getProviderName(), - entity.getConfig(), entity.getPriority(), entity.getDisplayName())); - toRemove.add(entity); - } - - for (StorageProviderEntity entity : toRemove) { - realm.getStorageProviders().remove(entity); - } - - List add = new LinkedList<>(); - for (StorageProviderModel model : providers) { - boolean found = false; - for (StorageProviderEntity entity : realm.getStorageProviders()) { - if (entity.getId().equals(model.getId())) { - found = true; - break; - } - } - if (!found) add.add(model); - } - - for (StorageProviderModel model : add) { - StorageProviderEntity entity = new StorageProviderEntity(); - if (model.getId() != null) { - entity.setId(model.getId()); - } else { - String id = KeycloakModelUtils.generateId(); - entity.setId(id); - model.setId(id); - } - entity.setProviderName(model.getProviderName()); - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - String displayName = model.getDisplayName(); - if (displayName == null) { - displayName = entity.getId(); - } - entity.setDisplayName(displayName); - realm.getStorageProviders().add(entity); - - } - - updateRealm(); - - } - - @Override - public List getStorageProviders() { - List entities = realm.getStorageProviders(); - if (entities.isEmpty()) return Collections.EMPTY_LIST; - List copy = new LinkedList<>(); - for (StorageProviderEntity entity : entities) { - copy.add(entity); - - } - List result = new LinkedList<>(); - for (StorageProviderEntity entity : copy) { - result.add(new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName() - )); - } - - Collections.sort(result, StorageProviderModel.comparator); - return Collections.unmodifiableList(result); - } - - @Override - public StorageProviderModel getStorageProvider(String id) { - for (StorageProviderEntity entity : realm.getStorageProviders()) { - if (entity.getId().equals(id)) return new StorageProviderModel(entity.getId(), entity.getProviderName(), - entity.getConfig(), entity.getPriority(), entity.getDisplayName()); - } - return null; - } - @Override public boolean isEventsEnabled() { return realm.isEventsEnabled(); @@ -2217,5 +2057,115 @@ public class RealmAdapter extends AbstractMongoAdapter impleme return model.getClientTemplateById(id, this); } + @Override + public ComponentModel addComponentModel(ComponentModel model) { + ComponentEntity entity = new ComponentEntity(); + if (model.getId() == null) { + entity.setId(KeycloakModelUtils.generateId()); + } else { + entity.setId(model.getId()); + } + entity.setConfig(model.getConfig()); + entity.setId(model.getId()); + entity.setParentId(model.getParentId()); + entity.setProviderType(model.getProviderType()); + entity.setProviderId(model.getProviderId()); + entity.setName(model.getName()); + model.setId(entity.getId()); + realm.getComponentEntities().add(entity); + updateRealm(); + return model; + } + + @Override + public void updateComponent(ComponentModel model) { + for (ComponentEntity entity : realm.getComponentEntities()) { + if (entity.getId().equals(model.getId())) { + entity.setConfig(model.getConfig()); + entity.setId(model.getId()); + entity.setParentId(model.getParentId()); + entity.setProviderType(model.getProviderType()); + entity.setProviderId(model.getProviderId()); + entity.setName(model.getName()); + + } + } + updateRealm(); + + } + + @Override + public void removeComponent(ComponentModel component) { + Iterator it = realm.getComponentEntities().iterator(); + while(it.hasNext()) { + if (it.next().getId().equals(component.getId())) { + session.users().preRemove(this, component); + it.remove(); + break; + } + } + updateRealm(); + + } + + @Override + public void removeComponents(String parentId) { + Iterator it = realm.getComponentEntities().iterator(); + while(it.hasNext()) { + ComponentEntity next = it.next(); + if (next.getParentId().equals(parentId)) { + session.users().preRemove(this, entityToModel(next)); + it.remove(); + } + } + updateRealm(); + + } + + @Override + public List getComponents(String parentId, String providerType) { + List results = new LinkedList<>(); + for (ComponentEntity entity : realm.getComponentEntities()) { + if (entity.getParentId().equals(parentId) && entity.getProviderType().equals(providerType)) { + ComponentModel model = entityToModel(entity); + results.add(model); + } + + } + return results; + } + + protected ComponentModel entityToModel(ComponentEntity entity) { + ComponentModel model = new ComponentModel(); + model.setId(entity.getId()); + model.setName(entity.getName()); + model.setParentId(entity.getParentId()); + model.setProviderId(entity.getProviderId()); + model.setProviderType(entity.getProviderType()); + MultivaluedHashMap map = new MultivaluedHashMap<>(); + map.putAll(entity.getConfig()); + model.setConfig(map); + return model; + } + + @Override + public List getComponents() { + List results = new LinkedList<>(); + for (ComponentEntity entity : realm.getComponentEntities()) { + ComponentModel model = entityToModel(entity); + results.add(model); + } + return results; + } + + @Override + public ComponentModel getComponent(String id) { + for (ComponentEntity entity : realm.getComponentEntities()) { + if (entity.getId() == entity.getId()) { + return entityToModel(entity); + } + } + return null; + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java old mode 100755 new mode 100644 similarity index 53% rename from server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java rename to server-spi/src/main/java/org/keycloak/component/ComponentFactory.java index e4f27171b0..b107d5a59a --- a/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java +++ b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java @@ -14,41 +14,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.keycloak.storage; +package org.keycloak.component; import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.ConfiguredProvider; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderFactory; +import org.keycloak.storage.UserStorageProviderModel; -import java.util.Set; +import java.util.Collections; +import java.util.List; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface StorageProviderFactory extends ProviderFactory { - /** - * called per Keycloak transaction. - * - * @param session - * @param model - * @return - */ - T getInstance(KeycloakSession session, StorageProviderModel model); +public interface ComponentFactory extends ProviderFactory, ConfiguredProvider { + CreatedType create(KeycloakSession session, ComponentModel model); - /** - * This is the name of the provider and will be showed in the admin console as an option. - * - * @return - */ @Override - String getId(); + default ProviderType create(KeycloakSession session) { + return null; + } + + void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException; - /** - * This method is never called and is only an artifact of ProviderFactory. Returning null with no implementation is recommended. - * @param session - * @return - */ - @Override - StorageProvider create(KeycloakSession session); } diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentModel.java b/server-spi/src/main/java/org/keycloak/component/ComponentModel.java new file mode 100755 index 0000000000..338bc4b8af --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/component/ComponentModel.java @@ -0,0 +1,101 @@ +/* + * 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.component; + +import org.keycloak.common.util.MultivaluedHashMap; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Stored configuration of a User Storage provider instance. + * + * @author Marek Posolda + * @author Bill Burke + */ +public class ComponentModel implements Serializable { + + private String id; + private String name; + private String providerId; + private String providerType; + private String parentId; + private MultivaluedHashMap config = new MultivaluedHashMap<>(); + + public ComponentModel() {} + + public ComponentModel(ComponentModel copy) { + this.id = copy.id; + this.name = copy.name; + this.providerId = copy.providerId; + this.providerType = copy.providerType; + this.config = copy.config; + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public MultivaluedHashMap getConfig() { + return config; + } + + public void setConfig(MultivaluedHashMap config) { + this.config = config; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public String getProviderType() { + return providerType; + } + + public void setProviderType(String providerType) { + this.providerType = providerType; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } +} diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java b/server-spi/src/main/java/org/keycloak/component/ComponentValidationException.java similarity index 54% rename from server-spi/src/main/java/org/keycloak/storage/StorageProvider.java rename to server-spi/src/main/java/org/keycloak/component/ComponentValidationException.java index 56de3ab82c..d1d707c778 100644 --- a/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java +++ b/server-spi/src/main/java/org/keycloak/component/ComponentValidationException.java @@ -14,26 +14,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.storage; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.provider.Provider; +package org.keycloak.component; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface StorageProvider extends Provider { +public class ComponentValidationException extends RuntimeException { + public ComponentValidationException() { + } - void preRemove(RealmModel realm); - void preRemove(RealmModel realm, GroupModel group); - void preRemove(RealmModel realm, RoleModel role); - void preRemove(RealmModel realm, StorageProviderModel model); + public ComponentValidationException(String message) { + super(message); + } + public ComponentValidationException(String message, Throwable cause) { + super(message, cause); + } + + public ComponentValidationException(Throwable cause) { + super(cause); + } + + public ComponentValidationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } } - diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java b/server-spi/src/main/java/org/keycloak/component/ConfiguredComponent.java similarity index 66% rename from server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java rename to server-spi/src/main/java/org/keycloak/component/ConfiguredComponent.java index 5e80d5d51c..835cd3986a 100644 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java +++ b/server-spi/src/main/java/org/keycloak/component/ConfiguredComponent.java @@ -14,18 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.storage.changeset; +package org.keycloak.component; -import org.keycloak.models.RealmModel; -import org.keycloak.models.entities.UserEntity; -import org.keycloak.storage.StorageId; +import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.ConfiguredProvider; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface UserDataLookup { - UserData getUserById(RealmModel realm, StorageId id); - UserData getUserByUsername(RealmModel realm, String username); - UserData getUserByEmail(RealmModel realm, String email); +public interface ConfiguredComponent extends ConfiguredProvider { } diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java index 1d71b1f63b..8b6dcd31d0 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java @@ -32,16 +32,48 @@ public interface KeycloakSession { KeycloakContext getContext(); - KeycloakTransactionManager getTransaction(); + KeycloakTransactionManager getTransactionManager(); + /** + * Get dedicated provider instance of provider type clazz that was created for this session. If one hasn't been created yet, + * find the factory and allocate by calling ProviderFactory.create(KeycloakSession). The provider to use is determined + * by the "provider" config entry in keycloak-server boot configuration. (keycloak-server.json) + * + * + * + * @param clazz + * @param + * @return + */ T getProvider(Class clazz); + /** + * Get dedicated provider instance for a specific provider factory of id of provider type clazz that was created for this session. + * If one hasn't been created yet, + * find the factory and allocate by calling ProviderFactory.create(KeycloakSession). + + * @param clazz + * @param id + * @param + * @return + */ T getProvider(Class clazz, String id); + /** + * Get all provider factories that manage provider instances of class. + * + * @param clazz + * @param + * @return + */ Set listProviderIds(Class clazz); Set getAllProviders(Class clazz); + Object getAttribute(String attribute); + Object removeAttribute(String attribute); + void setAttribute(String name, Object value); + void enlistForClose(Provider provider); KeycloakSessionFactory getKeycloakSessionFactory(); @@ -69,22 +101,40 @@ public interface KeycloakSession { void close(); /** - * Possibly both cached and federated view of users depending on configuration. + * A cached view of all users in system. * * @return */ UserFederationManager users(); + + /** + * Un-cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI. + * + * @return + */ UserProvider userStorageManager(); /** - * Keycloak user storage. Non-federated, but possibly cache (if it is on) view of users. + * A cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI. */ UserProvider userStorage(); - UserFederatedStorageProvider userFederatedStorage(); + /** + * Keycloak specific local storage for users. No cache in front, this api talks directly to database. + * + * @return + */ UserProvider userLocalStorage(); + /** + * Hybrid storage for UserStorageProviders that can't store a specific piece of keycloak data in their external storage. + * + * @return + */ + UserFederatedStorageProvider userFederatedStorage(); + + /** * Keycloak scripting support. */ diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index af4d6d746d..47692ac652 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -18,13 +18,17 @@ package org.keycloak.models; import org.keycloak.common.enums.SslRequired; +import org.keycloak.component.ComponentModel; import org.keycloak.provider.ProviderEvent; -import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -282,12 +286,23 @@ public interface RealmModel extends RoleContainerModel { public IdentityProviderMapperModel getIdentityProviderMapperByName(String brokerAlias, String name); - StorageProviderModel addStorageProvider(StorageProviderModel model); - void updateStorageProvider(StorageProviderModel provider); - void removeStorageProvider(StorageProviderModel provider); - void setStorageProviders(List providers); - List getStorageProviders(); - StorageProviderModel getStorageProvider(String id); + ComponentModel addComponentModel(ComponentModel model); + void updateComponent(ComponentModel component); + void removeComponent(ComponentModel component); + void removeComponents(String parentId); + List getComponents(String parentId, String providerType); + List getComponents(); + ComponentModel getComponent(String id); + + default + List getUserStorageProviders() { + List list = new LinkedList<>(); + for (ComponentModel component : getComponents(getId(), UserStorageProvider.class.getName())) { + list.add(new UserStorageProviderModel(component)); + } + Collections.sort(list, UserStorageProviderModel.comparator); + return list; + } // Should return list sorted by UserFederationProviderModel.priority List getUserFederationProviders(); diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java index 19db2e178f..c48ae4d75f 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java @@ -18,11 +18,11 @@ package org.keycloak.models; import org.jboss.logging.Logger; +import org.keycloak.component.ComponentModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.policy.PasswordPolicyManagerProvider; import org.keycloak.policy.PolicyError; import org.keycloak.services.managers.UserManager; -import org.keycloak.storage.StorageProviderModel; import java.util.ArrayList; import java.util.Arrays; @@ -487,11 +487,6 @@ public class UserFederationManager implements UserProvider { session.userStorage().preRemove(protocolMapper); } - @Override - public void preRemove(RealmModel realm, StorageProviderModel link) { - - } - public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) { if (credential.getType().equals(UserCredentialModel.PASSWORD)) { if (realm.getPasswordPolicy() != null) { @@ -604,6 +599,11 @@ public class UserFederationManager implements UserProvider { return (result != null) ? result : CredentialValidationOutput.failed(); } + @Override + public void preRemove(RealmModel realm, ComponentModel component) { + + } + @Override public void close() { } diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java index 24b61066ee..d6ef2cd360 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java @@ -17,8 +17,8 @@ package org.keycloak.models; +import org.keycloak.component.ComponentModel; import org.keycloak.provider.Provider; -import org.keycloak.storage.StorageProviderModel; import org.keycloak.storage.user.UserCredentialValidatorProvider; import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserQueryProvider; @@ -56,10 +56,20 @@ public interface UserProvider extends Provider, List getUsers(RealmModel realm, boolean includeServiceAccounts); List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts); + /** + * only used for local storage + * + * @param realm + * @param id + * @param username + * @param addDefaultRoles + * @param addDefaultRequiredActions + * @return + */ + UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions); void preRemove(RealmModel realm); void preRemove(RealmModel realm, UserFederationProviderModel link); - void preRemove(RealmModel realm, StorageProviderModel link); void preRemove(RealmModel realm, RoleModel role); void preRemove(RealmModel realm, GroupModel group); @@ -73,4 +83,6 @@ public interface UserProvider extends Provider, void close(); + + void preRemove(RealmModel realm, ComponentModel component); } diff --git a/server-spi/src/main/java/org/keycloak/models/entities/ComponentEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/ComponentEntity.java new file mode 100755 index 0000000000..f873761596 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/entities/ComponentEntity.java @@ -0,0 +1,75 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.models.entities; + +import org.keycloak.common.util.MultivaluedHashMap; + +import java.util.List; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ComponentEntity extends AbstractIdentifiableEntity { + protected String name; + protected String providerType; + protected String providerId; + protected String parentId; + protected Map> config = new MultivaluedHashMap<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getProviderType() { + return providerType; + } + + public void setProviderType(String providerType) { + this.providerType = providerType; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + public Map> getConfig() { + return config; + } + + public void setConfig(Map> config) { + this.config = config; + } +} diff --git a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java index 6780498abb..c5a6ecf8fe 100755 --- a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java +++ b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -85,7 +85,7 @@ public class RealmEntity extends AbstractIdentifiableEntity { private List defaultGroups = new LinkedList(); private List requiredCredentials = new LinkedList<>(); - private List storageProviders = new LinkedList<>(); + private List componentEntities = new LinkedList<>(); private List userFederationProviders = new LinkedList(); private List userFederationMappers = new LinkedList(); private List identityProviders = new LinkedList(); @@ -685,12 +685,12 @@ public class RealmEntity extends AbstractIdentifiableEntity { this.defaultGroups = defaultGroups; } - public List getStorageProviders() { - return storageProviders; + public List getComponentEntities() { + return componentEntities; } - public void setStorageProviders(List storageProviders) { - this.storageProviders = storageProviders; + public void setComponentEntities(List componentEntities) { + this.componentEntities = componentEntities; } } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index 55a7c02d16..bc20d49042 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -44,7 +44,6 @@ import org.keycloak.models.UserModel; import org.keycloak.representations.idm.CertificateRepresentation; import org.keycloak.common.util.CertificateUtils; import org.keycloak.common.util.PemUtils; -import org.keycloak.storage.StorageProviderModel; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; @@ -282,7 +281,7 @@ public final class KeycloakModelUtils { */ public static void runJobInTransaction(KeycloakSessionFactory factory, KeycloakSessionTask task) { KeycloakSession session = factory.create(); - KeycloakTransaction tx = session.getTransaction(); + KeycloakTransaction tx = session.getTransactionManager(); try { tx.begin(); task.run(session); @@ -342,43 +341,6 @@ public final class KeycloakModelUtils { } // USER FEDERATION RELATED STUFF - /** - * Ensure that displayName of myProvider (if not null) is unique and there is no other provider with same displayName in the list. - * - * @param displayName to check for duplications - * @param myProvider provider, which is excluded from the list (if present) - * @param federationProviders - * @throws ModelDuplicateException if there is other provider with same displayName - */ - public static void ensureUniqueDisplayName(String displayName, StorageProviderModel myProvider, List federationProviders) throws ModelDuplicateException { - if (displayName != null) { - - for (StorageProviderModel federationProvider : federationProviders) { - if (myProvider != null && (myProvider.equals(federationProvider) || (myProvider.getId() != null && myProvider.getId().equals(federationProvider.getId())))) { - continue; - } - - if (displayName.equals(federationProvider.getDisplayName())) { - throw new ModelDuplicateException("There is already existing federation provider with display name: " + displayName); - } - } - } - } - - - public static StorageProviderModel findStorageProviderByDisplayName(String displayName, RealmModel realm) { - if (displayName == null) { - return null; - } - - for (StorageProviderModel provider : realm.getStorageProviders()) { - if (displayName.equals(provider.getDisplayName())) { - return provider; - } - } - return null; - } - /** * Ensure that displayName of myProvider (if not null) is unique and there is no other provider with same displayName in the list. * diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 203736f893..f0c75b7fa9 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -17,6 +17,7 @@ package org.keycloak.models.utils; +import org.keycloak.component.ComponentModel; import org.keycloak.events.Event; import org.keycloak.events.admin.AdminEvent; import org.keycloak.events.admin.AuthDetails; @@ -44,6 +45,7 @@ import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.AuthDetailsRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; @@ -51,6 +53,8 @@ import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientTemplateRepresentation; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.ConfigPropertyRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; @@ -565,7 +569,7 @@ public class ModelToRepresentation { return rep; } - public static UserFederationMapperRepresentation toRepresentation(RealmModel realm, UserFederationMapperModel model) { + public static UserFederationMapperRepresentation toRepresentation(RealmModel realm, UserFederationMapperModel model) { UserFederationMapperRepresentation rep = new UserFederationMapperRepresentation(); rep.setId(model.getId()); rep.setName(model.getName()); @@ -736,4 +740,27 @@ public class ModelToRepresentation { return rep; } + public static List toRepresentation(List configProperties) { + List propertiesRep = new LinkedList<>(); + for (ProviderConfigProperty prop : configProperties) { + ConfigPropertyRepresentation propRep = new ConfigPropertyRepresentation(); + propRep.setName(prop.getName()); + propRep.setLabel(prop.getLabel()); + propRep.setType(prop.getType()); + propRep.setDefaultValue(prop.getDefaultValue()); + propRep.setHelpText(prop.getHelpText()); + propertiesRep.add(propRep); + } + return propertiesRep; + } + + public static ComponentRepresentation toRepresentation(ComponentModel component) { + ComponentRepresentation rep = new ComponentRepresentation(); + rep.setId(component.getId()); + rep.setName(component.getName()); + rep.setProviderId(component.getProviderId()); + rep.setProviderType(component.getProviderType()); + rep.setConfig(component.getConfig()); + return rep; + } } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 2be6720e54..a20adcf4f2 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -20,6 +20,7 @@ package org.keycloak.models.utils; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.store.ResourceServerStore; +import org.keycloak.component.ComponentModel; import org.keycloak.hash.Pbkdf2PasswordHashProvider; import org.keycloak.migration.migrators.MigrationUtils; import org.keycloak.models.ClientTemplateModel; @@ -62,6 +63,7 @@ import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; import org.keycloak.representations.idm.ClaimRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientTemplateRepresentation; +import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; import org.keycloak.representations.idm.GroupRepresentation; @@ -1618,4 +1620,13 @@ public class RepresentationToModel { } + public static ComponentModel toModel(ComponentRepresentation rep) { + ComponentModel model = new ComponentModel(); + model.setParentId(rep.getParentId()); + model.setProviderType(rep.getProviderType()); + model.setProviderId(rep.getProviderId()); + model.setConfig(rep.getConfig()); + model.setName(rep.getName()); + return model; + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageId.java b/server-spi/src/main/java/org/keycloak/storage/StorageId.java index 56207403e3..ab44e47fed 100644 --- a/server-spi/src/main/java/org/keycloak/storage/StorageId.java +++ b/server-spi/src/main/java/org/keycloak/storage/StorageId.java @@ -16,6 +16,7 @@ */ package org.keycloak.storage; +import org.keycloak.component.ComponentModel; import org.keycloak.models.UserModel; import java.io.Serializable; @@ -27,27 +28,44 @@ import java.io.Serializable; public class StorageId implements Serializable { private String id; private String providerId; - private String storageId; + private String externalId; public StorageId(String id) { this.id = id; if (!id.startsWith("f:")) { - storageId = id; + externalId = id; return; } int providerIndex = id.indexOf(':', 2); providerId = id.substring(2, providerIndex); - storageId = id.substring(providerIndex + 1); + externalId = id.substring(providerIndex + 1); } - public StorageId(String providerId, String storageId) { - this.id = "f:" + providerId + ":" + storageId; + public StorageId(String providerId, String externalId) { + this.id = "f:" + providerId + ":" + externalId; this.providerId = providerId; - this.storageId = storageId; + this.externalId = externalId; } + /** + * generate the id string that should be returned by UserModel.getId() + * + * @param model + * @param externalId id used to resolve user in external storage + * @return + */ + public static String keycloakId(ComponentModel model, String externalId) { + return new StorageId(model.getId(), externalId).getId(); + } + + public static String externalId(String keycloakId) { + return new StorageId(keycloakId).getExternalId(); + } + + + public static String resolveProviderId(UserModel user) { return new StorageId(user.getId()).getProviderId(); } @@ -63,8 +81,8 @@ public class StorageId implements Serializable { return providerId; } - public String getStorageId() { - return storageId; + public String getExternalId() { + return externalId; } diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java deleted file mode 100755 index 823a500abd..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.keycloak.storage; - -import java.io.Serializable; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; - -/** - * Stored configuration of a User Storage provider instance. - * - * @author Marek Posolda - * @author Bill Burke - */ -public class StorageProviderModel implements Serializable { - - public static Comparator comparator = new Comparator() { - @Override - public int compare(StorageProviderModel o1, StorageProviderModel o2) { - return o1.priority - o2.priority; - } - }; - - private String id; - private String providerName; - private Map config = new HashMap(); - private int priority; - private String displayName; - - public StorageProviderModel() {} - - public StorageProviderModel(String id, String providerName, Map config, int priority, String displayName) { - this.id = id; - this.providerName = providerName; - if (config != null) { - this.config.putAll(config); - } - this.priority = priority; - this.displayName = displayName; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getProviderName() { - return providerName; - } - - public void setProviderName(String providerName) { - this.providerName = providerName; - } - - public Map getConfig() { - return config; - } - - public void setConfig(Map config) { - this.config = config; - } - - public int getPriority() { - return priority; - } - - public void setPriority(int priority) { - this.priority = priority; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } -} diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java index b6e6028cd9..72906b01f1 100755 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -19,6 +19,7 @@ package org.keycloak.storage; import org.jboss.logging.Logger; import org.keycloak.common.util.reflections.Types; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; @@ -62,10 +63,6 @@ public class UserStorageManager implements UserProvider { protected KeycloakSession session; - // Set of already validated/proxied federation users during this session. Key is user ID - private Map managedUsers = new HashMap<>(); - private UserProvider localStorage = null; - public UserStorageManager(KeycloakSession session) { this.session = session; } @@ -74,28 +71,37 @@ public class UserStorageManager implements UserProvider { return session.userLocalStorage(); } - protected List getStorageProviders(RealmModel realm) { - return realm.getStorageProviders(); + protected List getStorageProviders(RealmModel realm) { + return realm.getUserStorageProviders(); } protected T getFirstStorageProvider(RealmModel realm, Class type) { - for (StorageProviderModel model : getStorageProviders(realm)) { - StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); + for (UserStorageProviderModel model : getStorageProviders(realm)) { + UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId()); - if (Types.supports(type, factory, StorageProviderFactory.class)) { - return type.cast(factory.getInstance(session, model)); + if (Types.supports(type, factory, UserStorageProviderFactory.class)) { + return type.cast(getStorageProviderInstance(model, factory)); } } return null; } + private UserStorageProvider getStorageProviderInstance(UserStorageProviderModel model, UserStorageProviderFactory factory) { + UserStorageProvider instance = (UserStorageProvider)session.getAttribute(model.getId()); + if (instance != null) return instance; + instance = factory.create(session, model); + session.enlistForClose(instance); + session.setAttribute(model.getId(), instance); + return instance; + } + protected List getStorageProviders(RealmModel realm, Class type) { List list = new LinkedList<>(); - for (StorageProviderModel model : getStorageProviders(realm)) { - StorageProviderFactory factory = (StorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); - if (Types.supports(type, factory, StorageProviderFactory.class)) { - list.add(type.cast(factory.getInstance(session, model))); + for (UserStorageProviderModel model : getStorageProviders(realm)) { + UserStorageProviderFactory factory = (UserStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId()); + if (Types.supports(type, factory, UserStorageProviderFactory.class)) { + list.add(type.cast(getStorageProviderInstance(model, factory))); } @@ -106,10 +112,6 @@ public class UserStorageManager implements UserProvider { @Override public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { - UserRegistrationProvider registry = getFirstStorageProvider(realm, UserRegistrationProvider.class); - if (registry != null) { - return registry.addUser(realm, id, username, addDefaultRoles, addDefaultRequiredActions); - } return localStorage().addUser(realm, id, username.toLowerCase(), addDefaultRoles, addDefaultRequiredActions); } @@ -122,14 +124,14 @@ public class UserStorageManager implements UserProvider { return localStorage().addUser(realm, username.toLowerCase()); } - public StorageProvider getStorageProvider(RealmModel realm, String providerId) { - StorageProviderModel model = realm.getStorageProvider(providerId); + public UserStorageProvider getStorageProvider(RealmModel realm, String componentId) { + ComponentModel model = realm.getComponent(componentId); if (model == null) return null; - StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); + UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId()); if (factory == null) { - throw new ModelException("Could not find StorageProviderFactory for: " + model.getProviderName()); + throw new ModelException("Could not find UserStorageProviderFactory for: " + model.getProviderId()); } - return factory.getInstance(session, model); + return getStorageProviderInstance(new UserStorageProviderModel(model), factory); } @Override @@ -481,7 +483,7 @@ public class UserStorageManager implements UserProvider { public void preRemove(RealmModel realm) { localStorage().preRemove(realm); getFederatedStorage().preRemove(realm); - for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) { + for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) { provider.preRemove(realm); } } @@ -496,7 +498,7 @@ public class UserStorageManager implements UserProvider { public void preRemove(RealmModel realm, GroupModel group) { localStorage().preRemove(realm, group); getFederatedStorage().preRemove(realm, group); - for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) { + for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) { provider.preRemove(realm, group); } } @@ -505,7 +507,7 @@ public class UserStorageManager implements UserProvider { public void preRemove(RealmModel realm, RoleModel role) { localStorage().preRemove(realm, role); getFederatedStorage().preRemove(realm, role); - for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) { + for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) { provider.preRemove(realm, role); } } @@ -557,7 +559,7 @@ public class UserStorageManager implements UserProvider { if (toValidate.isEmpty()) return true; - StorageProvider provider = getStorageProvider(realm, StorageId.resolveProviderId(user)); + UserStorageProvider provider = getStorageProvider(realm, StorageId.resolveProviderId(user)); if (!(provider instanceof UserCredentialValidatorProvider)) { return false; } @@ -601,7 +603,7 @@ public class UserStorageManager implements UserProvider { } @Override - public void preRemove(RealmModel realm, StorageProviderModel link) { + public void preRemove(RealmModel realm, ComponentModel component) { } diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java index 58695c306b..dbb4b3c91e 100644 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java @@ -16,13 +16,18 @@ */ package org.keycloak.storage; +import org.keycloak.models.GroupModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.provider.Provider; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface UserStorageProvider { +public interface UserStorageProvider extends Provider { void preRemove(RealmModel realm); - + void preRemove(RealmModel realm, GroupModel group); + void preRemove(RealmModel realm, RoleModel role); } + diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java new file mode 100755 index 0000000000..5102355982 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java @@ -0,0 +1,85 @@ +/* + * 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.storage; + +import org.keycloak.Config; +import org.keycloak.component.ComponentFactory; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; + +import java.util.Collections; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserStorageProviderFactory extends ComponentFactory { + + + /** + * called per Keycloak transaction. + * + * @param session + * @param model + * @return + */ + T create(KeycloakSession session, ComponentModel model); + + /** + * This is the name of the provider and will be showed in the admin console as an option. + * + * @return + */ + @Override + String getId(); + + @Override + default void init(Config.Scope config) { + + } + + @Override + default void postInit(KeycloakSessionFactory factory) { + + } + + @Override + default void close() { + + } + + @Override + default String getHelpText() { + return ""; + } + + @Override + default List getConfigProperties() { + return Collections.EMPTY_LIST; + } + + @Override + default void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException { + + } + +} diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java new file mode 100755 index 0000000000..351107f94f --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java @@ -0,0 +1,62 @@ +/* + * 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.storage; + +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.RealmModel; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +/** + * Stored configuration of a User Storage provider instance. + * + * @author Marek Posolda + * @author Bill Burke + */ +public class UserStorageProviderModel extends ComponentModel { + + public static Comparator comparator = new Comparator() { + @Override + public int compare(UserStorageProviderModel o1, UserStorageProviderModel o2) { + return o1.getPriority() - o2.getPriority(); + } + }; + + public UserStorageProviderModel() { + setProviderType(UserStorageProvider.class.getName()); + } + + public UserStorageProviderModel(ComponentModel copy) { + super(copy); + } + + public int getPriority() { + String priority = getConfig().getFirst("priority"); + if (priority == null) return 0; + return Integer.valueOf(priority); + + } + + public void setPriority(int priority) { + getConfig().putSingle("priority", Integer.toString(priority)); + } +} diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java similarity index 89% rename from server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java rename to server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java index 5d6d0da687..344e0a0680 100755 --- a/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java @@ -24,7 +24,7 @@ import org.keycloak.provider.Spi; /** * @author Stian Thorgersen */ -public class StorageProviderSpi implements Spi { +public class UserStorageProviderSpi implements Spi { @Override public boolean isInternal() { @@ -38,12 +38,12 @@ public class StorageProviderSpi implements Spi { @Override public Class getProviderClass() { - return StorageProvider.class; + return UserStorageProvider.class; } @Override public Class getProviderFactoryClass() { - return StorageProviderFactory.class; + return UserStorageProviderFactory.class; } } diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java index 45c616891d..6a4e7d2be8 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java @@ -17,6 +17,7 @@ package org.keycloak.storage.adapter; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -29,7 +30,6 @@ import org.keycloak.models.UserModel; import org.keycloak.models.utils.DefaultRoles; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.storage.StorageId; -import org.keycloak.storage.StorageProviderModel; import java.util.Collections; import java.util.HashSet; @@ -58,9 +58,9 @@ public abstract class AbstractUserAdapter implements UserModel { } protected KeycloakSession session; protected RealmModel realm; - protected StorageProviderModel storageProviderModel; + protected ComponentModel storageProviderModel; - public AbstractUserAdapter(KeycloakSession session, RealmModel realm, StorageProviderModel storageProviderModel) { + public AbstractUserAdapter(KeycloakSession session, RealmModel realm, ComponentModel storageProviderModel) { this.session = session; this.realm = realm; this.storageProviderModel = storageProviderModel; diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java index b09ceae6ca..bfe0b95f6e 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java @@ -16,6 +16,7 @@ */ package org.keycloak.storage.adapter; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -28,11 +29,9 @@ import org.keycloak.models.UserModel; import org.keycloak.models.utils.DefaultRoles; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.storage.StorageId; -import org.keycloak.storage.StorageProviderModel; import org.keycloak.storage.federated.UserFederatedStorageProvider; import java.util.Collections; -import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -60,9 +59,9 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel { protected KeycloakSession session; protected RealmModel realm; - protected StorageProviderModel storageProviderModel; + protected ComponentModel storageProviderModel; - public AbstractUserAdapterFederatedStorage(KeycloakSession session, RealmModel realm, StorageProviderModel storageProviderModel) { + public AbstractUserAdapterFederatedStorage(KeycloakSession session, RealmModel realm, ComponentModel storageProviderModel) { this.session = session; this.realm = realm; this.storageProviderModel = storageProviderModel; diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java deleted file mode 100644 index e0ef387e9e..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.storage.changeset; - -import org.keycloak.models.RealmModel; -import org.keycloak.models.entities.UserEntity; - -import java.util.List; -import java.util.Map; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public interface UserDataQuery { - - // Service account is included for counts - int getUsersCount(RealmModel realm); - - List getUsers(RealmModel realm); - List searchForUser(String search, RealmModel realm); - List searchForUserByAttributes(Map attributes, RealmModel realm); - - List getUsers(RealmModel realm, int firstResult, int maxResults); - List searchForUser(String search, RealmModel realm, int firstResult, int maxResults); - List searchForUserByAttributes(Map attributes, RealmModel realm, int firstResult, int maxResults); - - - - // Searching by UserModel.attribute (not property) - List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm); -} diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java deleted file mode 100644 index e896b00e9b..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.storage.changeset; - -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.entities.UserEntity; -import org.keycloak.storage.StorageId; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public interface UserDataStore { - void updateUser(RealmModel realm, UserData user); - void addUser(RealmModel realm, UserData user); - boolean removeUser(RealmModel realm, StorageId store); -} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java index 46ae9db649..b60499199c 100755 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java @@ -17,6 +17,7 @@ package org.keycloak.storage.federated; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; import org.keycloak.models.ProtocolMapperModel; @@ -25,7 +26,6 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.provider.Provider; -import org.keycloak.storage.StorageProviderModel; /** * @author Bill Burke @@ -44,7 +44,7 @@ public interface UserFederatedStorageProvider extends Provider, void preRemove(RealmModel realm, UserFederationProviderModel link); - public void preRemove(RealmModel realm, GroupModel group); + void preRemove(RealmModel realm, GroupModel group); void preRemove(RealmModel realm, RoleModel role); @@ -54,5 +54,5 @@ public interface UserFederatedStorageProvider extends Provider, void preRemove(RealmModel realm, UserModel user); - void preRemove(RealmModel realm, StorageProviderModel model); + void preRemove(RealmModel realm, ComponentModel model); } diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java index 5475422d00..21b1ad0396 100755 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java @@ -20,8 +20,6 @@ package org.keycloak.storage.federated; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderFactory; /** * @author Stian Thorgersen diff --git a/server-spi/src/main/java/org/keycloak/storage/user/UserRegistrationProvider.java b/server-spi/src/main/java/org/keycloak/storage/user/UserRegistrationProvider.java index 7b6922d6e6..7697042f87 100644 --- a/server-spi/src/main/java/org/keycloak/storage/user/UserRegistrationProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/user/UserRegistrationProvider.java @@ -25,7 +25,6 @@ import org.keycloak.models.UserModel; * @version $Revision: 1 $ */ public interface UserRegistrationProvider { - UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions); UserModel addUser(RealmModel realm, String username); diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 696bb4d2e3..d2431d84b0 100755 --- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -16,7 +16,7 @@ # org.keycloak.models.UserFederationSpi -org.keycloak.storage.StorageProviderSpi +org.keycloak.storage.UserStorageProviderSpi org.keycloak.storage.federated.UserFederatedStorageProviderSpi org.keycloak.mappers.UserFederationMapperSpi org.keycloak.models.RealmSpi diff --git a/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java b/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java index 59d6217d15..705d23048f 100644 --- a/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java +++ b/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java @@ -62,7 +62,7 @@ public class PartialImportManager { try { partialImport.prepare(rep, realm, session); } catch (ErrorResponseException error) { - if (session.getTransaction().isActive()) session.getTransaction().setRollbackOnly(); + if (session.getTransactionManager().isActive()) session.getTransactionManager().setRollbackOnly(); return error.getResponse(); } } @@ -72,7 +72,7 @@ public class PartialImportManager { partialImport.removeOverwrites(realm, session); results.addAllResults(partialImport.doImport(rep, realm, session)); } catch (ErrorResponseException error) { - if (session.getTransaction().isActive()) session.getTransaction().setRollbackOnly(); + if (session.getTransactionManager().isActive()) session.getTransactionManager().setRollbackOnly(); return error.getResponse(); } } @@ -84,8 +84,8 @@ public class PartialImportManager { } } - if (session.getTransaction().isActive()) { - session.getTransaction().commit(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().commit(); } return Response.ok(results).build(); diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 8eb2a56b6b..1fae787117 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -36,6 +36,7 @@ public class DefaultKeycloakSession implements KeycloakSession { private final Map providers = new HashMap<>(); private final List closable = new LinkedList(); private final DefaultKeycloakTransactionManager transactionManager; + private final Map attributes = new HashMap<>(); private RealmProvider model; private UserProvider userModel; private UserStorageManager userStorageManager; @@ -81,7 +82,22 @@ public class DefaultKeycloakSession implements KeycloakSession { } @Override - public KeycloakTransactionManager getTransaction() { + public Object getAttribute(String attribute) { + return attributes.get(attribute); + } + + @Override + public Object removeAttribute(String attribute) { + return attributes.remove(attribute); + } + + @Override + public void setAttribute(String name, Object value) { + attributes.put(name, value); + } + + @Override + public KeycloakTransactionManager getTransactionManager() { return transactionManager; } diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java index 40c1ac6d2c..41876455c8 100755 --- a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java +++ b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java @@ -82,7 +82,7 @@ public class KeycloakSessionServletFilter implements Filter { session.getContext().setConnection(connection); ResteasyProviderFactory.pushContext(ClientConnection.class, connection); - KeycloakTransaction tx = session.getTransaction(); + KeycloakTransaction tx = session.getTransactionManager(); ResteasyProviderFactory.pushContext(KeycloakTransaction.class, tx); tx.begin(); @@ -123,8 +123,8 @@ public class KeycloakSessionServletFilter implements Filter { private void closeSession(KeycloakSession session) { // KeycloakTransactionCommitter is responsible for committing the transaction, but if an exception is thrown it's not invoked and transaction // should be rolled back - if (session.getTransaction() != null && session.getTransaction().isActive()) { - session.getTransaction().rollback(); + if (session.getTransactionManager() != null && session.getTransactionManager().isActive()) { + session.getTransactionManager().rollback(); } session.close(); diff --git a/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java b/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java index fa3b1d5d6b..28fc29d308 100644 --- a/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java +++ b/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java @@ -177,7 +177,7 @@ public class DefaultBruteForceProtector implements Runnable, BruteForceProtector queue.drainTo(events, TRANSACTION_SIZE); Collections.sort(events); // we sort to avoid deadlock due to ordered updates. Maybe I'm overthinking this. KeycloakSession session = factory.create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { for (LoginEvent event : events) { if (event instanceof FailedLogin) { @@ -186,9 +186,9 @@ public class DefaultBruteForceProtector implements Runnable, BruteForceProtector run = false; } } - session.getTransaction().commit(); + session.getTransactionManager().commit(); } catch (Exception e) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); throw e; } finally { for (LoginEvent event : events) { diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index c7cbc62709..91818176c2 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -835,17 +835,17 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal private void fireErrorEvent(String message, Throwable throwable) { if (!this.event.getEvent().getType().toString().endsWith("_ERROR")) { - boolean newTransaction = !this.session.getTransaction().isActive(); + boolean newTransaction = !this.session.getTransactionManager().isActive(); try { if (newTransaction) { - this.session.getTransaction().begin(); + this.session.getTransactionManager().begin(); } this.event.error(message); if (newTransaction) { - this.session.getTransaction().commit(); + this.session.getTransactionManager().commit(); } } catch (Exception e) { logger.couldNotFireEvent(e); @@ -869,8 +869,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } private void rollback() { - if (this.session.getTransaction().isActive()) { - this.session.getTransaction().rollback(); + if (this.session.getTransactionManager().isActive()) { + this.session.getTransactionManager().rollback(); } } 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 038528156e..5e1cd9f876 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -119,10 +119,10 @@ public class KeycloakApplication extends Application { boolean bootstrapAdminUser = false; KeycloakSession session = sessionFactory.create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); bootstrapAdminUser = new ApplianceBootstrap(session).isNoMasterUser(); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } @@ -142,7 +142,7 @@ public class KeycloakApplication extends Application { KeycloakSession session = sessionFactory.create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); exportImportManager = new ExportImportManager(session); @@ -155,10 +155,10 @@ public class KeycloakApplication extends Application { if (createMasterRealm) { applianceBootstrap.createMasterRealm(contextPath); } - session.getTransaction().commit(); + session.getTransactionManager().commit(); } catch (RuntimeException re) { - if (session.getTransaction().isActive()) { - session.getTransaction().rollback(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().rollback(); } throw re; } finally { @@ -180,11 +180,11 @@ public class KeycloakApplication extends Application { protected void migrateModel() { KeycloakSession session = sessionFactory.create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); MigrationModelManager.migrate(session); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } catch (Exception e) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); logger.migrationFailure(e); throw e; } finally { @@ -294,7 +294,7 @@ public class KeycloakApplication extends Application { KeycloakSession session = sessionFactory.create(); boolean exists = false; try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); @@ -313,9 +313,9 @@ public class KeycloakApplication extends Application { RealmModel realm = manager.importRealm(rep); logger.importedRealm(realm.getName(), from); } - session.getTransaction().commit(); + session.getTransactionManager().commit(); } catch (Throwable t) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); if (!exists) { logger.unableToImportRealm(t, rep.getRealm(), from); } @@ -345,7 +345,7 @@ public class KeycloakApplication extends Application { for (UserRepresentation userRep : realmRep.getUsers()) { KeycloakSession session = sessionFactory.create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); RealmModel realm = session.realms().getRealmByName(realmRep.getRealm()); if (realm == null) { @@ -357,13 +357,13 @@ public class KeycloakApplication extends Application { RepresentationToModel.createRoleMappings(userRep, user, realm); } - session.getTransaction().commit(); + session.getTransactionManager().commit(); logger.addUserSuccess(userRep.getUsername(), realmRep.getRealm()); } catch (ModelDuplicateException e) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); logger.addUserFailedUserExists(userRep.getUsername(), realmRep.getRealm()); } catch (Throwable t) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); logger.addUserFailed(t, userRep.getUsername(), realmRep.getRealm()); } finally { session.close(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 94203d7893..82e65fb623 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -536,7 +536,7 @@ public class AuthenticationManagementResource { AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(rep.getId()); if (model == null) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); throw new NotFoundException("Illegal execution"); } @@ -596,7 +596,7 @@ public class AuthenticationManagementResource { AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); throw new NotFoundException("Illegal execution"); } @@ -642,7 +642,7 @@ public class AuthenticationManagementResource { AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); throw new NotFoundException("Illegal execution"); } @@ -682,7 +682,7 @@ public class AuthenticationManagementResource { AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); throw new NotFoundException("Illegal execution"); } @@ -718,7 +718,7 @@ public class AuthenticationManagementResource { AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); throw new NotFoundException("Illegal execution"); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java index a2c35d8e60..de4d49b8dc 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java @@ -73,8 +73,8 @@ public class ClientInitialAccessResource { adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientInitialAccessModel.getId()).representation(config).success(); - if (session.getTransaction().isActive()) { - session.getTransaction().commit(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().commit(); } ClientInitialAccessPresentation rep = wrap(clientInitialAccessModel); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java new file mode 100644 index 0000000000..0097b191ff --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java @@ -0,0 +1,152 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.services.resources.admin; + +import org.jboss.resteasy.spi.NotFoundException; +import org.keycloak.common.ClientConnection; +import org.keycloak.component.ComponentModel; +import org.keycloak.events.admin.OperationType; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.protocol.oidc.TokenManager; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.services.ServicesLogger; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ComponentResource { + protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + + protected RealmModel realm; + + private RealmAuth auth; + + private AdminEventBuilder adminEvent; + + @Context + protected ClientConnection clientConnection; + + @Context + protected UriInfo uriInfo; + + @Context + protected KeycloakSession session; + + @Context + protected HttpHeaders headers; + + public ComponentResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + this.auth = auth; + this.realm = realm; + this.adminEvent = adminEvent; + + auth.init(RealmAuth.Resource.USER); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public List getComponents(@QueryParam("parent") String parent, @QueryParam("type") String type) { + auth.requireManage(); + if (parent == null) parent = realm.getId(); + List components = realm.getComponents(parent, type); + List reps = new LinkedList<>(); + for (ComponentModel component : components) { + ComponentRepresentation rep = ModelToRepresentation.toRepresentation(component); + reps.add(rep); + } + return reps; + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + public Response create(ComponentRepresentation rep) { + auth.requireManage(); + ComponentModel model = RepresentationToModel.toModel(rep); + if (model.getParentId() == null) model.setParentId(realm.getId()); + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success(); + + + + model = realm.addComponentModel(model); + return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build(); + } + + @GET + @Path("{id}") + public ComponentRepresentation getComponent(@PathParam("id") String id) { + auth.requireManage(); + ComponentModel model = realm.getComponent(id); + if (model == null) { + throw new NotFoundException("Could not find component"); + } + return ModelToRepresentation.toRepresentation(model); + + + } + + @PUT + @Path("{id}") + @Consumes(MediaType.APPLICATION_JSON) + public void updateComponent(@PathParam("id") String id, ComponentRepresentation rep) { + auth.requireManage(); + ComponentModel model = realm.getComponent(id); + if (model == null) { + throw new NotFoundException("Could not find component"); + } + model = RepresentationToModel.toModel(rep); + model.setId(id); + adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo, model.getId()).representation(rep).success(); + realm.updateComponent(model); + + } + @DELETE + @Path("{id}") + public void removeComponent(@PathParam("id") String id) { + auth.requireManage(); + ComponentModel model = realm.getComponent(id); + if (model == null) { + throw new NotFoundException("Could not find component"); + } + adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo, model.getId()).success(); + realm.removeComponent(model); + + } + + + +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 4594bdd87e..8270ed4c86 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -321,7 +321,7 @@ public class RealmAdminResource { */ @Path("users") public UsersResource users() { - UsersResource users = new UsersResource(realm, auth, tokenManager, adminEvent); + UsersResource users = new UsersResource(realm, auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(users); //resourceContext.initResource(users); return users; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 5788b42e7e..aa6aa688e7 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -127,7 +127,7 @@ public class UsersResource { @Context protected HttpHeaders headers; - public UsersResource(RealmModel realm, RealmAuth auth, TokenManager tokenManager, AdminEventBuilder adminEvent) { + public UsersResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { this.auth = auth; this.realm = realm; this.adminEvent = adminEvent; @@ -172,8 +172,8 @@ public class UsersResource { updateUserFromRep(user, rep, attrsToRemove, realm, session, true); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); - if (session.getTransaction().isActive()) { - session.getTransaction().commit(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().commit(); } return Response.noContent().build(); } catch (ModelDuplicateException e) { @@ -214,19 +214,19 @@ public class UsersResource { adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success(); - if (session.getTransaction().isActive()) { - session.getTransaction().commit(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().commit(); } return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getId()).build()).build(); } catch (ModelDuplicateException e) { - if (session.getTransaction().isActive()) { - session.getTransaction().setRollbackOnly(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().setRollbackOnly(); } return ErrorResponse.exists("User exists with same username or email"); } catch (ModelException me){ - if (session.getTransaction().isActive()) { - session.getTransaction().setRollbackOnly(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().setRollbackOnly(); } return ErrorResponse.exists("Could not create user"); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java index 1a67fb0bf7..d7ee1085c9 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java @@ -25,7 +25,6 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.ServiceLoader; import javax.ws.rs.GET; import javax.ws.rs.WebApplicationException; @@ -33,12 +32,13 @@ import javax.ws.rs.core.Context; import org.keycloak.broker.provider.IdentityProvider; import org.keycloak.broker.provider.IdentityProviderFactory; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.events.EventType; import org.keycloak.events.admin.OperationType; -import org.keycloak.models.PasswordPolicy; import org.keycloak.policy.PasswordPolicyProvider; import org.keycloak.policy.PasswordPolicyProviderFactory; import org.keycloak.provider.*; +import org.keycloak.representations.idm.ComponentTypeRepresentation; import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation; import org.keycloak.theme.Theme; import org.keycloak.theme.ThemeProvider; @@ -115,12 +115,26 @@ public class ServerInfoAdminResource { Map providers = new HashMap<>(); if (providerIds != null) { + info.setComponentTypes(new HashMap<>()); for (String name : providerIds) { ProviderRepresentation provider = new ProviderRepresentation(); ProviderFactory pi = session.getKeycloakSessionFactory().getProviderFactory(spi.getProviderClass(), name); if (ServerInfoAwareProviderFactory.class.isAssignableFrom(pi.getClass())) { provider.setOperationalInfo(((ServerInfoAwareProviderFactory) pi).getOperationalInfo()); } + if (pi instanceof ConfiguredProvider) { + ComponentTypeRepresentation rep = new ComponentTypeRepresentation(); + rep.setId(pi.getId()); + ConfiguredProvider configured = (ConfiguredProvider)pi; + rep.setHelpText(configured.getHelpText()); + rep.setProperties(ModelToRepresentation.toRepresentation(configured.getConfigProperties())); + List reps = info.getComponentTypes().get(spi.getProviderClass().getName()); + if (reps == null) { + reps = new LinkedList<>(); + info.getComponentTypes().put(spi.getProviderClass().getName(), reps); + } + reps.add(rep); + } providers.put(name, provider); } } @@ -225,15 +239,7 @@ public class ServerInfoAdminResource { rep.setCategory(mapper.getDisplayCategory()); rep.setProperties(new LinkedList()); List configProperties = mapper.getConfigProperties(); - for (ProviderConfigProperty prop : configProperties) { - ConfigPropertyRepresentation propRep = new ConfigPropertyRepresentation(); - propRep.setName(prop.getName()); - propRep.setLabel(prop.getLabel()); - propRep.setType(prop.getType()); - propRep.setDefaultValue(prop.getDefaultValue()); - propRep.setHelpText(prop.getHelpText()); - rep.getProperties().add(propRep); - } + rep.setProperties(ModelToRepresentation.toRepresentation(configProperties)); types.add(rep); } } diff --git a/services/src/main/java/org/keycloak/services/scheduled/ClusterAwareScheduledTaskRunner.java b/services/src/main/java/org/keycloak/services/scheduled/ClusterAwareScheduledTaskRunner.java index 94db9c971e..b2a8bf7d15 100644 --- a/services/src/main/java/org/keycloak/services/scheduled/ClusterAwareScheduledTaskRunner.java +++ b/services/src/main/java/org/keycloak/services/scheduled/ClusterAwareScheduledTaskRunner.java @@ -41,7 +41,7 @@ public class ClusterAwareScheduledTaskRunner extends ScheduledTaskRunner { @Override protected void runTask(final KeycloakSession session) { - session.getTransaction().begin(); + session.getTransactionManager().begin(); ClusterProvider clusterProvider = session.getProvider(ClusterProvider.class); String taskKey = task.getClass().getSimpleName(); @@ -56,7 +56,7 @@ public class ClusterAwareScheduledTaskRunner extends ScheduledTaskRunner { }); - session.getTransaction().commit(); + session.getTransactionManager().commit(); if (result.isExecuted()) { logger.debugf("Executed scheduled task %s", taskKey); diff --git a/services/src/main/java/org/keycloak/services/scheduled/ScheduledTaskRunner.java b/services/src/main/java/org/keycloak/services/scheduled/ScheduledTaskRunner.java index b49300f8a1..81ff5f6ae2 100644 --- a/services/src/main/java/org/keycloak/services/scheduled/ScheduledTaskRunner.java +++ b/services/src/main/java/org/keycloak/services/scheduled/ScheduledTaskRunner.java @@ -45,7 +45,7 @@ public class ScheduledTaskRunner implements Runnable { } catch (Throwable t) { logger.failedToRunScheduledTask(t, task.getClass().getSimpleName()); - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); } finally { try { session.close(); @@ -56,9 +56,9 @@ public class ScheduledTaskRunner implements Runnable { } protected void runTask(KeycloakSession session) { - session.getTransaction().begin(); + session.getTransactionManager().begin(); task.run(session); - session.getTransaction().commit(); + session.getTransactionManager().commit(); logger.debug("Executed scheduled task " + task.getClass().getSimpleName()); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java index 7791c9139c..3b8d68b9b2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java @@ -39,7 +39,6 @@ import org.keycloak.testsuite.util.cli.TestsuiteCLI; import org.keycloak.util.JsonSerialization; import javax.servlet.DispatcherType; -import javax.ws.rs.core.Application; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -249,7 +248,7 @@ public class KeycloakServer { public void importRealm(RealmRepresentation rep) { KeycloakSession session = sessionFactory.create();; - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); @@ -268,7 +267,7 @@ public class KeycloakServer { info("Imported realm " + realm.getName()); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } @@ -278,11 +277,11 @@ public class KeycloakServer { if (System.getProperty("keycloak.createAdminUser", "true").equals("true")) { KeycloakSession session = sessionFactory.create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); if (new ApplianceBootstrap(session).isNoMasterUser()) { new ApplianceBootstrap(session).createMasterRealmUser("admin", "admin"); } - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index 507c0fee58..adea44e0b3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -265,7 +265,7 @@ public class AdapterTestStrategy extends ExternalResource { RealmModel realm = session.realms().getRealmByName("demo"); int originalIdle = realm.getSsoSessionIdleTimeout(); realm.setSsoSessionIdleTimeout(1); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(2); @@ -278,7 +278,7 @@ public class AdapterTestStrategy extends ExternalResource { session = keycloakRule.startSession(); realm = session.realms().getRealmByName("demo"); realm.setSsoSessionIdleTimeout(originalIdle); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(0); @@ -300,7 +300,7 @@ public class AdapterTestStrategy extends ExternalResource { RealmModel realm = session.realms().getRealmByName("demo"); int originalIdle = realm.getSsoSessionIdleTimeout(); realm.setSsoSessionIdleTimeout(1); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(2); @@ -308,7 +308,7 @@ public class AdapterTestStrategy extends ExternalResource { session = keycloakRule.startSession(); realm = session.realms().getRealmByName("demo"); session.sessions().removeExpired(realm); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); // test SSO @@ -321,7 +321,7 @@ public class AdapterTestStrategy extends ExternalResource { UserModel user = session.users().getUserByUsername("bburke@redhat.com", realm); new ResourceAdminManager(session).logoutUser(null, realm, user, session); realm.setSsoSessionIdleTimeout(originalIdle); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(0); @@ -343,7 +343,7 @@ public class AdapterTestStrategy extends ExternalResource { RealmModel realm = session.realms().getRealmByName("demo"); int original = realm.getSsoSessionMaxLifespan(); realm.setSsoSessionMaxLifespan(1); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(2); @@ -356,7 +356,7 @@ public class AdapterTestStrategy extends ExternalResource { session = keycloakRule.startSession(); realm = session.realms().getRealmByName("demo"); realm.setSsoSessionMaxLifespan(original); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(0); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java index 936dfe921a..02e34a7bcc 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java @@ -125,7 +125,7 @@ public class CookieTokenStoreAdapterTest { RealmModel realm = session.realms().getRealmByName("demo"); int originalTokenTimeout = realm.getAccessTokenLifespan(); realm.setAccessTokenLifespan(3); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); // login to customer-cookie-portal @@ -164,7 +164,7 @@ public class CookieTokenStoreAdapterTest { session = keycloakRule.startSession(); realm = session.realms().getRealmByName("demo"); realm.setAccessTokenLifespan(originalTokenTimeout); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); } finally { Time.setOffset(0); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractAuthorizationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractAuthorizationTest.java index 301adcae3a..fbf602ec4b 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractAuthorizationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractAuthorizationTest.java @@ -58,7 +58,7 @@ public abstract class AbstractAuthorizationTest { protected R onAuthorizationSession(Function function) { KeycloakSession keycloakSession = startKeycloakSession(); - KeycloakTransactionManager transaction = keycloakSession.getTransaction(); + KeycloakTransactionManager transaction = keycloakSession.getTransactionManager(); try { AuthorizationProvider authorizationProvider = keycloakSession.getProvider(AuthorizationProvider.class); @@ -80,7 +80,7 @@ public abstract class AbstractAuthorizationTest { protected void onAuthorizationSession(Consumer consumer) { KeycloakSession keycloakSession = startKeycloakSession(); - KeycloakTransactionManager transaction = keycloakSession.getTransaction(); + KeycloakTransactionManager transaction = keycloakSession.getTransactionManager(); try { AuthorizationProvider authorizationProvider = keycloakSession.getProvider(AuthorizationProvider.class); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java index daed4adac9..a53479c15b 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java @@ -16,6 +16,7 @@ */ package org.keycloak.testsuite.federation.storage; +import org.keycloak.component.ComponentModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -23,8 +24,7 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.storage.StorageId; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; import org.keycloak.storage.user.UserCredentialValidatorProvider; import org.keycloak.storage.user.UserLookupProvider; @@ -32,28 +32,33 @@ import org.keycloak.storage.user.UserRegistrationProvider; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class UserMapStorage implements UserLookupProvider, StorageProvider, UserCredentialValidatorProvider, UserRegistrationProvider { +public class UserMapStorage implements UserLookupProvider, UserStorageProvider, UserCredentialValidatorProvider, UserRegistrationProvider { protected Map userPasswords; - protected StorageProviderModel model; + protected ComponentModel model; protected KeycloakSession session; - public UserMapStorage(KeycloakSession session, StorageProviderModel model, Map userPasswords) { + public static final AtomicInteger allocations = new AtomicInteger(0); + public static final AtomicInteger closings = new AtomicInteger(0); + + public UserMapStorage(KeycloakSession session, ComponentModel model, Map userPasswords) { this.session = session; this.model = model; this.userPasswords = userPasswords; + allocations.incrementAndGet(); } @Override public UserModel getUserById(String id, RealmModel realm) { StorageId storageId = new StorageId(id); - final String username = storageId.getStorageId(); + final String username = storageId.getExternalId(); if (!userPasswords.containsKey(username)) return null; return createUser(realm, username); @@ -94,12 +99,6 @@ public class UserMapStorage implements UserLookupProvider, StorageProvider, User return null; } - @Override - public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { - userPasswords.put(username, ""); - return createUser(realm, username); - } - @Override public UserModel addUser(RealmModel realm, String username) { userPasswords.put(username, ""); @@ -131,11 +130,6 @@ public class UserMapStorage implements UserLookupProvider, StorageProvider, User } - @Override - public void preRemove(RealmModel realm, StorageProviderModel model) { - - } - @Override public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { for (UserCredentialModel cred : input) { @@ -151,6 +145,7 @@ public class UserMapStorage implements UserLookupProvider, StorageProvider, User @Override public void close() { + closings.incrementAndGet(); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorageFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorageFactory.java index f80a11dea7..79e9261384 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorageFactory.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorageFactory.java @@ -17,22 +17,19 @@ package org.keycloak.testsuite.federation.storage; import org.keycloak.Config; +import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderFactory; -import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.UserStorageProviderFactory; -import java.io.IOException; import java.util.Hashtable; import java.util.Map; -import java.util.Properties; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class UserMapStorageFactory implements StorageProviderFactory { +public class UserMapStorageFactory implements UserStorageProviderFactory { public static final String PROVIDER_ID = "user-password-map"; @@ -40,7 +37,7 @@ public class UserMapStorageFactory implements StorageProviderFactory userPasswords = new Hashtable<>(); @Override - public UserMapStorage getInstance(KeycloakSession session, StorageProviderModel model) { + public UserMapStorage create(KeycloakSession session, ComponentModel model) { return new UserMapStorage(session, model, userPasswords); } @@ -49,11 +46,6 @@ public class UserMapStorageFactory implements StorageProviderFactoryBill Burke * @version $Revision: 1 $ */ -public class UserPropertyFileStorage implements UserLookupProvider, StorageProvider, UserCredentialValidatorProvider, UserQueryProvider { +public class UserPropertyFileStorage implements UserLookupProvider, UserStorageProvider, UserCredentialValidatorProvider, UserQueryProvider { protected Properties userPasswords; - protected StorageProviderModel model; + protected ComponentModel model; protected KeycloakSession session; protected boolean federatedStorageEnabled; - public UserPropertyFileStorage(KeycloakSession session, StorageProviderModel model, Properties userPasswords) { + public UserPropertyFileStorage(KeycloakSession session, ComponentModel model, Properties userPasswords) { this.session = session; this.model = model; this.userPasswords = userPasswords; @@ -60,7 +59,7 @@ public class UserPropertyFileStorage implements UserLookupProvider, StorageProvi @Override public UserModel getUserById(String id, RealmModel realm) { StorageId storageId = new StorageId(id); - final String username = storageId.getStorageId(); + final String username = storageId.getExternalId(); if (!userPasswords.containsKey(username)) return null; return createUser(realm, username); @@ -116,11 +115,6 @@ public class UserPropertyFileStorage implements UserLookupProvider, StorageProvi } - @Override - public void preRemove(RealmModel realm, StorageProviderModel model) { - - } - @Override public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { for (UserCredentialModel cred : input) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java index 82d91cfd4f..0dec2bb400 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java @@ -17,33 +17,28 @@ package org.keycloak.testsuite.federation.storage; import org.keycloak.Config; +import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderFactory; -import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.UserStorageProviderFactory; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import java.util.Properties; -import java.util.Set; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class UserPropertyFileStorageFactory implements StorageProviderFactory { +public class UserPropertyFileStorageFactory implements UserStorageProviderFactory { public static final String PROVIDER_ID = "user-password-props"; @Override - public UserPropertyFileStorage getInstance(KeycloakSession session, StorageProviderModel model) { + public UserPropertyFileStorage create(KeycloakSession session, ComponentModel model) { Properties props = new Properties(); try { - props.load(getClass().getResourceAsStream(model.getConfig().get("property.file"))); + props.load(getClass().getResourceAsStream(model.getConfig().getFirst("property.file"))); } catch (IOException e) { throw new RuntimeException(e); } @@ -55,11 +50,6 @@ public class UserPropertyFileStorageFactory implements StorageProviderFactoryBill Burke * @version $Revision: 1 $ */ -public class UserFederationStorageTest { - public static StorageProviderModel memoryProvider = null; +public class UserStorageTest { + public static ComponentModel memoryProvider = null; @ClassRule public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { @Override public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - StorageProviderModel model = new StorageProviderModel(); - model.setDisplayName("memory"); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setName("memory"); model.setPriority(0); - model.setProviderName(UserMapStorageFactory.PROVIDER_ID); - memoryProvider = appRealm.addStorageProvider(model); - model = new StorageProviderModel(); - model.setDisplayName("read-only-user-props"); + model.setProviderId(UserMapStorageFactory.PROVIDER_ID); + model.setParentId(appRealm.getId()); + memoryProvider = appRealm.addComponentModel(model); + + model = new UserStorageProviderModel(); + model.setName("read-only-user-props"); model.setPriority(1); - model.setProviderName(UserPropertyFileStorageFactory.PROVIDER_ID); - model.getConfig().put("property.file", "/storage-test/read-only-user-password.properties"); - appRealm.addStorageProvider(model); - model = new StorageProviderModel(); - model.setDisplayName("user-props"); + model.setProviderId(UserPropertyFileStorageFactory.PROVIDER_ID); + model.setParentId(appRealm.getId()); + model.getConfig().putSingle("property.file", "/storage-test/read-only-user-password.properties"); + appRealm.addComponentModel(model); + model = new UserStorageProviderModel(); + model.setName("user-props"); model.setPriority(2); - model.setProviderName(UserPropertyFileStorageFactory.PROVIDER_ID); - model.getConfig().put("property.file", "/storage-test/user-password.properties"); - model.getConfig().put("USER_FEDERATED_STORAGE", "true"); - appRealm.addStorageProvider(model); + model.setParentId(appRealm.getId()); + model.setProviderId(UserPropertyFileStorageFactory.PROVIDER_ID); + model.getConfig().putSingle("property.file", "/storage-test/user-password.properties"); + model.getConfig().putSingle("USER_FEDERATED_STORAGE", "true"); + appRealm.addComponentModel(model); } }); @Rule @@ -265,4 +270,27 @@ public class UserFederationStorageTest { } + @Test + public void testLifecycle() { + UserMapStorage.allocations.set(0); + UserMapStorage.closings.set(0); + KeycloakSession session = keycloakRule.startSession(); + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().addUser(realm, "memuser"); + Assert.assertNotNull(user); + user = session.users().getUserByUsername("nonexistent", realm); + Assert.assertNull(user); + keycloakRule.stopSession(session, true); + Assert.assertEquals(1, UserMapStorage.allocations.get()); + Assert.assertEquals(1, UserMapStorage.closings.get()); + + session = keycloakRule.startSession(); + realm = session.realms().getRealmByName("test"); + user = session.users().getUserByUsername("memuser", realm); + session.users().removeUser(realm, user); + Assert.assertNull(session.users().getUserByUsername("memuser", realm)); + keycloakRule.stopSession(session, true); + + } + } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java index 0befa7ca16..f1409f42b4 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java @@ -81,16 +81,16 @@ public class AbstractModelTest { protected void commit(boolean rollback) { if (rollback) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); } else { - session.getTransaction().commit(); + session.getTransactionManager().commit(); } resetSession(); } protected void resetSession() { - if (session.getTransaction().isActive()) { - session.getTransaction().rollback(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().rollback(); } kc.stopSession(session, false); session = kc.startSession(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java index a28af56688..eecf8c9034 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java @@ -74,7 +74,7 @@ public class AdapterTest extends AbstractModelTest { realmModel.setAccessTokenLifespan(1000); realmModel.addDefaultRole("foo"); - session.getTransaction().commit(); + session.getTransactionManager().commit(); resetSession(); realmModel = realmManager.getRealm(realmModel.getId()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java index 0d7c9b08c6..c478375e21 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java @@ -105,7 +105,7 @@ public class CacheTest { assertNotNull(user2.getLastName()); } finally { - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); } } @@ -121,7 +121,7 @@ public class CacheTest { RoleModel fooRole = client.addRole("foo-role"); user.grantRole(fooRole); } finally { - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); } @@ -136,7 +136,7 @@ public class CacheTest { ClientModel client = realm.getClientByClientId("foo"); realm.removeClient(client.getId()); } finally { - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); } @@ -152,7 +152,7 @@ public class CacheTest { Assert.assertEquals(roles.size(), grantedRolesCount - 1); } finally { - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/TransactionsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/TransactionsTest.java index 0d7e9e27c2..2edcfaa31e 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/TransactionsTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/TransactionsTest.java @@ -35,14 +35,14 @@ public class TransactionsTest { public void testTransactionActive() { KeycloakSession session = kc.startSession(); - Assert.assertTrue(session.getTransaction().isActive()); - session.getTransaction().commit(); - Assert.assertFalse(session.getTransaction().isActive()); + Assert.assertTrue(session.getTransactionManager().isActive()); + session.getTransactionManager().commit(); + Assert.assertFalse(session.getTransactionManager().isActive()); - session.getTransaction().begin(); - Assert.assertTrue(session.getTransaction().isActive()); - session.getTransaction().rollback(); - Assert.assertFalse(session.getTransaction().isActive()); + session.getTransactionManager().begin(); + Assert.assertTrue(session.getTransactionManager().isActive()); + session.getTransactionManager().rollback(); + Assert.assertFalse(session.getTransactionManager().isActive()); session.close(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java index 83ea5adb99..01f99fb3c3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java @@ -217,7 +217,7 @@ public class UserModelTest extends AbstractModelTest { UserModel user1 = session.users().addUser(realm, "user1"); commit(); - + realm = session.realms().getRealmByName("original"); List users = session.users().searchForUser("user", realm, 0, 7); Assert.assertTrue(users.contains(user1)); } @@ -238,6 +238,7 @@ public class UserModelTest extends AbstractModelTest { user3.setSingleAttribute("key2", "value21"); commit(); + realm = session.realms().getRealmByName("original"); List users = session.users().searchForUserByUserAttribute("key1", "value1", realm); Assert.assertEquals(2, users.size()); @@ -284,6 +285,7 @@ public class UserModelTest extends AbstractModelTest { // Search and assert service account user not found realm = realmManager.getRealmByName("original"); + client = realm.getClientByClientId("foo"); UserModel searched = session.users().getServiceAccount(client); Assert.assertEquals(searched, user1); users = session.users().searchForUser("John Doe", realm); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java index 172c51796e..8315331918 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java @@ -85,12 +85,12 @@ public abstract class AbstractKeycloakRule extends ExternalResource { public UserRepresentation getUser(String realm, String name) { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmModel realmByName = session.realms().getRealmByName(realm); UserModel user = session.users().getUserByUsername(name, realmByName); UserRepresentation userRep = user != null ? ModelToRepresentation.toRepresentation(user) : null; - session.getTransaction().commit(); + session.getTransactionManager().commit(); return userRep; } finally { session.close(); @@ -99,11 +99,11 @@ public abstract class AbstractKeycloakRule extends ExternalResource { public UserRepresentation getUserById(String realm, String id) { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmModel realmByName = session.realms().getRealmByName(realm); UserRepresentation userRep = ModelToRepresentation.toRepresentation(session.users().getUserById(id, realmByName)); - session.getTransaction().commit(); + session.getTransactionManager().commit(); return userRep; } finally { session.close(); @@ -112,7 +112,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { protected void setupKeycloak() { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); @@ -121,7 +121,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { configure(session, manager, adminstrationRealm); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } @@ -129,7 +129,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { public void update(KeycloakRule.KeycloakSetup configurer, String realmId) { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); @@ -141,7 +141,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { configurer.session = session; configurer.config(manager, adminstrationRealm, appRealm); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } @@ -221,7 +221,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { protected void removeTestRealms() { KeycloakSession session = server.getSessionFactory().create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); RealmManager realmManager = new RealmManager(session); for (String realmName : getTestRealms()) { RealmModel realm = realmManager.getRealmByName(realmName); @@ -229,7 +229,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { realmManager.removeRealm(realm); } } - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } @@ -248,12 +248,12 @@ public abstract class AbstractKeycloakRule extends ExternalResource { public KeycloakSession startSession() { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); return session; } public void stopSession(KeycloakSession session, boolean commit) { - KeycloakTransaction transaction = session.getTransaction(); + KeycloakTransaction transaction = session.getTransactionManager(); if (commit && !transaction.getRollbackOnly()) { transaction.commit(); } else { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java index 8e481778d6..050bcf3ece 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java @@ -58,7 +58,7 @@ public class KeycloakRule extends AbstractKeycloakRule { public void configure(KeycloakSetup configurer) { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); @@ -70,7 +70,7 @@ public class KeycloakRule extends AbstractKeycloakRule { configurer.session = session; configurer.config(manager, adminstrationRealm, appRealm); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.storage.StorageProviderFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory similarity index 100% rename from testsuite/integration/src/test/resources/META-INF/services/org.keycloak.storage.StorageProviderFactory rename to testsuite/integration/src/test/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory From 1d695237b71494a05d247db4c2b25a27e51327a1 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 1 Aug 2016 12:07:02 -0400 Subject: [PATCH 10/13] fix --- .../resources/admin/info/ServerInfoAdminResource.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java index d7ee1085c9..1a1d2bfffe 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java @@ -127,7 +127,9 @@ public class ServerInfoAdminResource { rep.setId(pi.getId()); ConfiguredProvider configured = (ConfiguredProvider)pi; rep.setHelpText(configured.getHelpText()); - rep.setProperties(ModelToRepresentation.toRepresentation(configured.getConfigProperties())); + List configProperties = configured.getConfigProperties(); + if (configProperties == null) configProperties = Collections.EMPTY_LIST; + rep.setProperties(ModelToRepresentation.toRepresentation(configProperties)); List reps = info.getComponentTypes().get(spi.getProviderClass().getName()); if (reps == null) { reps = new LinkedList<>(); From 3a5d4a3723e570aed6b58d70732cacc193bdf596 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 1 Aug 2016 14:55:06 -0400 Subject: [PATCH 11/13] fix --- examples/providers/user-storage-jpa/pom.xml | 79 +++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100755 examples/providers/user-storage-jpa/pom.xml diff --git a/examples/providers/user-storage-jpa/pom.xml b/examples/providers/user-storage-jpa/pom.xml new file mode 100755 index 0000000000..b025900bf1 --- /dev/null +++ b/examples/providers/user-storage-jpa/pom.xml @@ -0,0 +1,79 @@ + + + + + keycloak-examples-providers-parent + org.keycloak + 2.1.0-SNAPSHOT + + + Properties Authentication Provider Example + + 4.0.0 + + user-storage-jpa-example + jar + + + + org.keycloak + keycloak-core + provided + + + org.keycloak + keycloak-server-spi + provided + + + org.keycloak + keycloak-model-jpa + provided + + + org.jboss.logging + jboss-logging + provided + + + org.hibernate.javax.persistence + hibernate-jpa-2.1-api + provided + + + org.hibernate + hibernate-entitymanager + provided + + + + + user-storage-jpa-example + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + From 62b96143a145ead9300219af906ad73a8d30f921 Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 2 Aug 2016 09:20:33 +0200 Subject: [PATCH 12/13] Travis workaround - another attempt --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ee548a01b1..4f4d2977ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ before_script: - export MAVEN_SKIP_RC=true install: - - mvn install -Pdistribution -DskipTests=true -B -V | grep -e "Maven" -e "Java" -e "Building Keycloak" + - ( mvn install -Pdistribution -DskipTests=true -B -V -q ) & ( MYPID=$!; while [ $(ps -ef | grep $MYPID | grep -v grep | wc -l) -gt 0 ]; do sleep 10; date; done; ) script: - mvn test -B From b3a55df1e7ef6dc63d4b153f897399a2effe0133 Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 2 Aug 2016 08:13:15 +0200 Subject: [PATCH 13/13] Fixing mongo --- ...DefaultMongoConnectionFactoryProvider.java | 2 +- .../mongo/keycloak/adapters/RealmAdapter.java | 3 +- .../keycloak/testsuite/model/AdapterTest.java | 59 +++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java index 440886f18d..b899e0d450 100755 --- a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java +++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java @@ -78,7 +78,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro "org.keycloak.models.entities.RequiredActionProviderEntity", "org.keycloak.models.entities.PersistentUserSessionEntity", "org.keycloak.models.entities.PersistentClientSessionEntity", - "org.keycloak.models.entities.StorageProviderEntity", + "org.keycloak.models.entities.ComponentEntity", "org.keycloak.authorization.mongo.entities.PolicyEntity", "org.keycloak.authorization.mongo.entities.ResourceEntity", "org.keycloak.authorization.mongo.entities.ResourceServerEntity", diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 8b945effd7..551f6fdc76 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1841,6 +1841,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme RequiredActionProviderEntity entity = getRequiredActionProviderEntity(model.getId()); if (entity == null) return; entity.setAlias(model.getAlias()); + entity.setName(model.getName()); entity.setProviderId(model.getProviderId()); entity.setEnabled(model.isEnabled()); entity.setDefaultAction(model.isDefaultAction()); @@ -2066,7 +2067,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme entity.setId(model.getId()); } entity.setConfig(model.getConfig()); - entity.setId(model.getId()); entity.setParentId(model.getParentId()); entity.setProviderType(model.getProviderType()); entity.setProviderId(model.getProviderId()); @@ -2083,7 +2083,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme for (ComponentEntity entity : realm.getComponentEntities()) { if (entity.getId().equals(model.getId())) { entity.setConfig(model.getConfig()); - entity.setId(model.getId()); entity.setParentId(model.getParentId()); entity.setProviderType(model.getProviderType()); entity.setProviderId(model.getProviderId()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java index eecf8c9034..42d190c980 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java @@ -22,6 +22,7 @@ import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; import org.keycloak.Config; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.ModelDuplicateException; @@ -36,6 +37,9 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.RealmManager; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.testsuite.federation.storage.UserMapStorageFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -831,6 +835,61 @@ public class AdapterTest extends AbstractModelTest { realmManager.removeRealm(realmModel); } + @Test + public void testComponentModelCRUD() { + // Add + realmModel = realmManager.createRealm("foo-realm"); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setName("memory"); + model.setPriority(0); + model.setProviderId(UserMapStorageFactory.PROVIDER_ID); + model.setParentId(realmModel.getId()); + ComponentModel createdModel = realmModel.addComponentModel(model); + String id = createdModel.getId(); + Assert.assertNotNull(id); + + commit(); + + realmModel = realmManager.getRealmByName("foo-realm"); + ComponentModel foundModel = realmModel.getComponent(id); + assertComponentModel(foundModel, id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); + + List components = realmModel.getComponents(); + Assert.assertEquals(components.size(), 1); + assertComponentModel(components.get(0), id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); + + components = realmModel.getComponents(realmModel.getId(), UserStorageProvider.class.getName()); + Assert.assertEquals(components.size(), 1); + assertComponentModel(components.get(0), id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); + + // Update + foundModel.getConfig().putSingle("foo", "bar"); + realmModel.updateComponent(foundModel); + + commit(); + + realmModel = realmManager.getRealmByName("foo-realm"); + foundModel = realmModel.getComponent(id); + assertComponentModel(foundModel, id, UserMapStorageFactory.PROVIDER_ID, realmModel.getId(), "memory"); + Assert.assertEquals("bar", foundModel.getConfig().getFirst("foo")); + + // Remove + realmModel.removeComponent(foundModel); + + commit(); + + realmModel = realmManager.getRealmByName("foo-realm"); + foundModel = realmModel.getComponent(id); + Assert.assertNull(foundModel); + } + + private void assertComponentModel(ComponentModel componentModel, String expectedId, String expectedProviderId, String expectedParentId, String expectedName) { + Assert.assertEquals(expectedId, componentModel.getId()); + Assert.assertEquals(expectedProviderId, componentModel.getProviderId()); + Assert.assertEquals(expectedParentId, componentModel.getParentId()); + Assert.assertEquals(expectedName, componentModel.getName()); + } + private KeyPair generateKeypair() throws NoSuchAlgorithmException { return KeyPairGenerator.getInstance("RSA").generateKeyPair(); }