From b29810c92328be014023e87f73325095df221c2a Mon Sep 17 00:00:00 2001 From: mposolda Date: Wed, 18 Mar 2020 09:31:31 +0100 Subject: [PATCH] KEYCLOAK-13306 Model fixes for check realm when lookup by ID (cherry picked from commit e40a62de31f6f5d326234314a9e285010665f707) --- .../InfinispanUserSessionProvider.java | 18 +- .../keycloak/models/jpa/JpaRealmProvider.java | 20 +- .../org/keycloak/models/jpa/RealmAdapter.java | 74 ++- .../testsuite/model/OwnerReplacementTest.java | 547 ++++++++++++++++++ 4 files changed, 627 insertions(+), 32 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/OwnerReplacementTest.java 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 5b4fdd2c77..674e854aa9 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 @@ -250,14 +250,17 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { } protected UserSessionAdapter getUserSession(RealmModel realm, String id, boolean offline) { - UserSessionEntity entity = getUserSessionEntity(id, offline); + UserSessionEntity entity = getUserSessionEntity(realm, id, offline); return wrap(realm, entity, offline); } - private UserSessionEntity getUserSessionEntity(String id, boolean offline) { + private UserSessionEntity getUserSessionEntity(RealmModel realm, String id, boolean offline) { InfinispanChangelogBasedTransaction tx = getTransaction(offline); SessionEntityWrapper entityWrapper = tx.get(id); - return entityWrapper==null ? null : entityWrapper.getEntity(); + if (entityWrapper==null) return null; + UserSessionEntity entity = entityWrapper.getEntity(); + if (!entity.getRealmId().equals(realm.getId())) return null; + return entity; } @@ -455,7 +458,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { @Override public void removeUserSession(RealmModel realm, UserSessionModel session) { - UserSessionEntity entity = getUserSessionEntity(session, false); + UserSessionEntity entity = getUserSessionEntity(realm, session, false); if (entity != null) { removeUserSession(entity, false); } @@ -801,11 +804,12 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { return entity != null ? new UserLoginFailureAdapter(this, key, entity) : null; } - UserSessionEntity getUserSessionEntity(UserSessionModel userSession, boolean offline) { + UserSessionEntity getUserSessionEntity(RealmModel realm, UserSessionModel userSession, boolean offline) { if (userSession instanceof UserSessionAdapter) { + if (!userSession.getRealm().equals(realm)) return null; return ((UserSessionAdapter) userSession).getEntity(); } else { - return getUserSessionEntity(userSession.getId(), offline); + return getUserSessionEntity(realm, userSession.getId(), offline); } } @@ -829,7 +833,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { @Override public void removeOfflineUserSession(RealmModel realm, UserSessionModel userSession) { - UserSessionEntity userSessionEntity = getUserSessionEntity(userSession, true); + UserSessionEntity userSessionEntity = getUserSessionEntity(realm, userSession, true); if (userSessionEntity != null) { removeUserSession(userSessionEntity, true); } 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 e32995be05..b9faa5ca46 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 @@ -364,6 +364,10 @@ public class JpaRealmProvider implements RealmProvider { container.removeDefaultRoles(role.getName()); } RoleEntity roleEntity = em.getReference(RoleEntity.class, role.getId()); + if (roleEntity == null || !roleEntity.getRealmId().equals(realm.getId())) { + // Throw model exception to ensure transaction rollback and revert previous operations (removing default roles) as well + throw new ModelException("Role not found or trying to remove role from incorrect realm"); + } String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em); em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate(); realm.getClients().forEach(c -> c.deleteScopeMapping(role)); @@ -786,20 +790,18 @@ public class JpaRealmProvider implements RealmProvider { @Override public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) { ClientInitialAccessEntity entity = em.find(ClientInitialAccessEntity.class, id); - if (entity == null) { - return null; - } else { - return entityToModel(entity); - } + if (entity == null) return null; + if (!entity.getRealm().getId().equals(realm.getId())) return null; + return entityToModel(entity); } @Override public void removeClientInitialAccessModel(RealmModel realm, String id) { ClientInitialAccessEntity entity = em.find(ClientInitialAccessEntity.class, id, LockModeType.PESSIMISTIC_WRITE); - if (entity != null) { - em.remove(entity); - em.flush(); - } + if (entity == null) return; + if (!entity.getRealm().getId().equals(realm.getId())) return; + em.remove(entity); + em.flush(); } @Override 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 c54ed5a193..2c54002e4e 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 @@ -1651,7 +1651,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public AuthenticationFlowModel getAuthenticationFlowById(String id) { - AuthenticationFlowEntity entity = em.find(AuthenticationFlowEntity.class, id); + AuthenticationFlowEntity entity = getAuthenticationFlowEntity(id, false); if (entity == null) return null; return entityToModel(entity); } @@ -1661,15 +1661,15 @@ public class RealmAdapter implements RealmModel, JpaModel { if (KeycloakModelUtils.isFlowUsed(this, model)) { throw new ModelException("Cannot remove authentication flow, it is currently in use"); } - AuthenticationFlowEntity entity = em.find(AuthenticationFlowEntity.class, model.getId(), LockModeType.PESSIMISTIC_WRITE); - + AuthenticationFlowEntity entity = getAuthenticationFlowEntity(model.getId(), true); + if (entity == null) return; em.remove(entity); em.flush(); } @Override public void updateAuthenticationFlow(AuthenticationFlowModel model) { - AuthenticationFlowEntity entity = em.find(AuthenticationFlowEntity.class, model.getId()); + AuthenticationFlowEntity entity = getAuthenticationFlowEntity(model.getId(), false); if (entity == null) return; entity.setAlias(model.getAlias()); entity.setDescription(model.getDescription()); @@ -1679,6 +1679,15 @@ public class RealmAdapter implements RealmModel, JpaModel { } + private AuthenticationFlowEntity getAuthenticationFlowEntity(String id, boolean readForRemove) { + AuthenticationFlowEntity entity = readForRemove + ? em.find(AuthenticationFlowEntity.class, id, LockModeType.PESSIMISTIC_WRITE) + : em.find(AuthenticationFlowEntity.class, id); + if (entity == null) return null; + if (!entity.getRealm().equals(getEntity())) return null; + return entity; + } + @Override public AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model) { AuthenticationFlowEntity entity = new AuthenticationFlowEntity(); @@ -1723,7 +1732,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public AuthenticationExecutionModel getAuthenticationExecutionById(String id) { - AuthenticationExecutionEntity entity = em.find(AuthenticationExecutionEntity.class, id); + AuthenticationExecutionEntity entity = getAuthenticationExecution(id, false); if (entity == null) return null; return entityToModel(entity); } @@ -1761,7 +1770,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public void updateAuthenticatorExecution(AuthenticationExecutionModel model) { - AuthenticationExecutionEntity entity = em.find(AuthenticationExecutionEntity.class, model.getId()); + AuthenticationExecutionEntity entity = getAuthenticationExecution(model.getId(), false); if (entity == null) return; entity.setAutheticatorFlow(model.isAuthenticatorFlow()); entity.setAuthenticator(model.getAuthenticator()); @@ -1778,13 +1787,22 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public void removeAuthenticatorExecution(AuthenticationExecutionModel model) { - AuthenticationExecutionEntity entity = em.find(AuthenticationExecutionEntity.class, model.getId(), LockModeType.PESSIMISTIC_WRITE); + AuthenticationExecutionEntity entity = getAuthenticationExecution(model.getId(), true); if (entity == null) return; em.remove(entity); em.flush(); } + private AuthenticationExecutionEntity getAuthenticationExecution(String id, boolean readForRemove) { + AuthenticationExecutionEntity entity = readForRemove + ? em.find(AuthenticationExecutionEntity.class, id, LockModeType.PESSIMISTIC_WRITE) + : em.find(AuthenticationExecutionEntity.class, id); + if (entity == null) return null; + if (!entity.getRealm().equals(getEntity())) return null; + return entity; + } + @Override public AuthenticatorConfigModel addAuthenticatorConfig(AuthenticatorConfigModel model) { AuthenticatorConfigEntity auth = new AuthenticatorConfigEntity(); @@ -1801,7 +1819,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public void removeAuthenticatorConfig(AuthenticatorConfigModel model) { - AuthenticatorConfigEntity entity = em.find(AuthenticatorConfigEntity.class, model.getId(), LockModeType.PESSIMISTIC_WRITE); + AuthenticatorConfigEntity entity = getAuthenticatorConfigEntity(model.getId(), true); if (entity == null) return; em.remove(entity); em.flush(); @@ -1810,7 +1828,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public AuthenticatorConfigModel getAuthenticatorConfigById(String id) { - AuthenticatorConfigEntity entity = em.find(AuthenticatorConfigEntity.class, id); + AuthenticatorConfigEntity entity = getAuthenticatorConfigEntity(id, false); if (entity == null) return null; return entityToModel(entity); } @@ -1827,7 +1845,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public void updateAuthenticatorConfig(AuthenticatorConfigModel model) { - AuthenticatorConfigEntity entity = em.find(AuthenticatorConfigEntity.class, model.getId()); + AuthenticatorConfigEntity entity = getAuthenticatorConfigEntity(model.getId(), false); if (entity == null) return; entity.setAlias(model.getAlias()); if (entity.getConfig() == null) { @@ -1842,6 +1860,15 @@ public class RealmAdapter implements RealmModel, JpaModel { } + private AuthenticatorConfigEntity getAuthenticatorConfigEntity(String id, boolean readForRemove) { + AuthenticatorConfigEntity entity = readForRemove + ? em.find(AuthenticatorConfigEntity.class, id, LockModeType.PESSIMISTIC_WRITE) + : em.find(AuthenticatorConfigEntity.class, id); + if (entity == null) return null; + if (!entity.getRealm().equals(getEntity())) return null; + return entity; + } + @Override public List getAuthenticatorConfigs() { Collection entities = realm.getAuthenticatorConfigs(); @@ -1875,7 +1902,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public void removeRequiredActionProvider(RequiredActionProviderModel model) { - RequiredActionProviderEntity entity = em.find(RequiredActionProviderEntity.class, model.getId(), LockModeType.PESSIMISTIC_WRITE); + RequiredActionProviderEntity entity = getRequiredProviderEntity(model.getId(), true); if (entity == null) return; em.remove(entity); em.flush(); @@ -1884,7 +1911,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public RequiredActionProviderModel getRequiredActionProviderById(String id) { - RequiredActionProviderEntity entity = em.find(RequiredActionProviderEntity.class, id); + RequiredActionProviderEntity entity = getRequiredProviderEntity(id, false); if (entity == null) return null; return entityToModel(entity); } @@ -1906,7 +1933,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public void updateRequiredActionProvider(RequiredActionProviderModel model) { - RequiredActionProviderEntity entity = em.find(RequiredActionProviderEntity.class, model.getId()); + RequiredActionProviderEntity entity = getRequiredProviderEntity(model.getId(), false); if (entity == null) return; entity.setAlias(model.getAlias()); entity.setProviderId(model.getProviderId()); @@ -1938,6 +1965,15 @@ public class RealmAdapter implements RealmModel, JpaModel { return Collections.unmodifiableList(actions); } + private RequiredActionProviderEntity getRequiredProviderEntity(String id, boolean readForRemove) { + RequiredActionProviderEntity entity = readForRemove + ? em.find(RequiredActionProviderEntity.class, id, LockModeType.PESSIMISTIC_WRITE) + : em.find(RequiredActionProviderEntity.class, id); + if (entity == null) return null; + if (!entity.getRealm().equals(getEntity())) return null; + return entity; + } + @Override public RequiredActionProviderModel getRequiredActionProviderByAlias(String alias) { for (RequiredActionProviderModel action : getRequiredActionProviders()) { @@ -2178,7 +2214,7 @@ public class RealmAdapter implements RealmModel, JpaModel { public void updateComponent(ComponentModel component) { ComponentUtil.getComponentFactory(session, component).validateConfiguration(session, this, component); - ComponentEntity c = em.find(ComponentEntity.class, component.getId()); + ComponentEntity c = getComponentEntity(component.getId()); if (c == null) return; ComponentModel old = entityToModel(c); c.setName(component.getName()); @@ -2194,7 +2230,7 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public void removeComponent(ComponentModel component) { - ComponentEntity c = em.find(ComponentEntity.class, component.getId()); + ComponentEntity c = getComponentEntity(component.getId()); if (c == null) return; session.users().preRemove(this, component); ComponentUtil.notifyPreRemove(session, this, component); @@ -2260,8 +2296,14 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public ComponentModel getComponent(String id) { + ComponentEntity c = getComponentEntity(id); + return c==null ? null : entityToModel(c); + } + + private ComponentEntity getComponentEntity(String id) { ComponentEntity c = em.find(ComponentEntity.class, id); if (c == null) return null; - return entityToModel(c); + if (!c.getRealm().equals(getEntity())) return null; + return c; } } \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/OwnerReplacementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/OwnerReplacementTest.java new file mode 100644 index 0000000000..e89274c596 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/OwnerReplacementTest.java @@ -0,0 +1,547 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.keycloak.testsuite.model; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.AuthenticationFlowModel; +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.ClientInitialAccessModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelException; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RealmProvider; +import org.keycloak.models.RequiredActionProviderModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; +import org.keycloak.testsuite.arquillian.annotation.ModelTest; +import org.keycloak.testsuite.util.RealmBuilder; +import org.keycloak.testsuite.util.UserBuilder; + +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; + +/** + * Test for the CRUD scenarios when the operation is called on the object, which is owned by different realm + * + * @author Marek Posolda + */ +@AuthServerContainerExclude(AuthServerContainerExclude.AuthServer.REMOTE) +public class OwnerReplacementTest extends AbstractKeycloakTest { + + + @Override + public void addTestRealms(List testRealms) { + log.debug("Adding test realm for import from testrealm.json"); + RealmRepresentation testRealm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + testRealms.add(testRealm); + + UserRepresentation user = UserBuilder.create() + .username("foo@user") + .email("foo@user.com") + .password("password") + .build(); + + RealmRepresentation realm2 = RealmBuilder.create() + .name("foo") + .user(user) + .build(); + realm2.setId("foo"); + testRealms.add(realm2); + } + + + @Test + @ModelTest + public void componentsTest(KeycloakSession session1) { + doTest(session1, + // Get ID of some component from realm1 + ((session, realm1) -> { + + List components = realm1.getComponents(); + return components.get(0).getId(); + + }), + // Test lookup realm1 component in realm2 should not work + ((session, realm2, realm1ComponentId) -> { + + ComponentModel component = realm2.getComponent(realm1ComponentId); + Assert.assertNull(component); + + }), + // Try to update some component in realm1 through the realm2 + ((session, realm1, realm2, realm1ComponentId) -> { + + ComponentModel component = realm1.getComponent(realm1ComponentId); + component.put("key1", "Val1"); + realm2.updateComponent(component); + + }), + // Test update from above was not successful + ((session, realm1, realm1ComponentId) -> { + + ComponentModel component = realm1.getComponent(realm1ComponentId); + Assert.assertNull(component.get("key1")); + + }), + // Try remove component from realm1 in the context of realm2 + ((session, realm1, realm2, realm1ComponentId) -> { + + ComponentModel component = realm1.getComponent(realm1ComponentId); + realm2.removeComponent(component); + + }), + // Test remove from above was not successful + ((session, realm1, realm1ComponentId) -> { + + ComponentModel component = realm1.getComponent(realm1ComponentId); + Assert.assertNotNull(component); + + }) + ); + } + + @Test + @ModelTest + public void requiredActionProvidersTest(KeycloakSession session1) { + doTest(session1, + // Get ID of some object from realm1 + ((session, realm1) -> { + + List reqActions = realm1.getRequiredActionProviders(); + return reqActions.get(0).getId(); + + }), + // Test lookup realm1 object in realm2 should not work + ((session, realm2, realm1ReqActionId) -> { + + RequiredActionProviderModel reqAction = realm2.getRequiredActionProviderById(realm1ReqActionId); + Assert.assertNull(reqAction); + + }), + // Try to update some object in realm1 through the realm2 + ((session, realm1, realm2, realm1ReqActionId) -> { + + RequiredActionProviderModel reqAction = realm1.getRequiredActionProviderById(realm1ReqActionId); + reqAction.getConfig().put("key1", "Val1"); + realm2.updateRequiredActionProvider(reqAction); + + }), + // Test update from above was not successful + ((session, realm1, realm1ReqActionId) -> { + + RequiredActionProviderModel reqAction = realm1.getRequiredActionProviderById(realm1ReqActionId); + Assert.assertNull(reqAction.getConfig().get("key1")); + + }), + // Try remove object from realm1 in the context of realm2 + ((session, realm1, realm2, realm1ReqActionId) -> { + + RequiredActionProviderModel reqAction = realm1.getRequiredActionProviderById(realm1ReqActionId); + realm2.removeRequiredActionProvider(reqAction); + + }), + // Test remove from above was not successful + ((session, realm1, realm1ReqActionId) -> { + + RequiredActionProviderModel reqAction = realm1.getRequiredActionProviderById(realm1ReqActionId); + Assert.assertNotNull(reqAction); + + }) + ); + } + + + @Test + @ModelTest + public void authenticationFlowsTest(KeycloakSession session1) { + doTest(session1, + // Get ID of some object from realm1 + ((session, realm1) -> { + + AuthenticationFlowModel flow = realm1.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); + return flow.getId(); + + }), + // Test lookup realm1 object in realm2 should not work + ((session, realm2, realm1FlowId) -> { + + AuthenticationFlowModel flow = realm2.getAuthenticationFlowById(realm1FlowId); + Assert.assertNull(flow); + + }), + // Try to update some object in realm1 through the realm2 + ((session, realm1, realm2, realm1FlowId) -> { + + AuthenticationFlowModel flow = realm1.getAuthenticationFlowById(realm1FlowId); + flow.setDescription("foo"); + realm2.updateAuthenticationFlow(flow); + + }), + // Test update from above was not successful + ((session, realm1, realm1FlowId) -> { + + AuthenticationFlowModel flow = realm1.getAuthenticationFlowById(realm1FlowId); + Assert.assertNotEquals("foo", flow.getDescription()); + + }), + // Try remove object from realm1 in the context of realm2 + ((session, realm1, realm2, realm1FlowId) -> { + + AuthenticationFlowModel flow = realm1.getAuthenticationFlowById(realm1FlowId); + realm2.removeAuthenticationFlow(flow); + + }), + // Test remove from above was not successful + ((session, realm1, realm1FlowId) -> { + + AuthenticationFlowModel flow = realm1.getAuthenticationFlowById(realm1FlowId); + Assert.assertNotNull(flow); + + }) + ); + } + + + @Test + @ModelTest + public void authenticationExecutionsTest(KeycloakSession session1) { + doTest(session1, + // Get ID of some object from realm1 + ((session, realm1) -> { + + AuthenticationFlowModel flow = realm1.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); + List executions = realm1.getAuthenticationExecutions(flow.getId()); + return executions.get(0).getId(); + + }), + // Test lookup realm1 object in realm2 should not work + ((session, realm2, realm1ExecutionId) -> { + + AuthenticationExecutionModel execution = realm2.getAuthenticationExecutionById(realm1ExecutionId); + Assert.assertNull(execution); + + }), + // Try to update some object in realm1 through the realm2 + ((session, realm1, realm2, realm1ExecutionId) -> { + + AuthenticationExecutionModel execution = realm1.getAuthenticationExecutionById(realm1ExecutionId); + execution.setPriority(1234); + realm2.updateAuthenticatorExecution(execution); + + }), + // Test update from above was not successful + ((session, realm1, realm1ExecutionId) -> { + + AuthenticationExecutionModel execution = realm1.getAuthenticationExecutionById(realm1ExecutionId); + Assert.assertNotEquals(1234, execution.getPriority()); + + }), + // Try remove object from realm1 in the context of realm2 + ((session, realm1, realm2, realm1ExecutionId) -> { + + AuthenticationExecutionModel execution = realm1.getAuthenticationExecutionById(realm1ExecutionId); + realm2.removeAuthenticatorExecution(execution); + + }), + // Test remove from above was not successful + ((session,realm1, realm1ExecutionId) -> { + + AuthenticationExecutionModel execution = realm1.getAuthenticationExecutionById(realm1ExecutionId); + Assert.assertNotNull(execution); + + }) + ); + } + + + @Test + @ModelTest + public void authenticationConfigsTest(KeycloakSession session1) { + doTest(session1, + // Get ID of some object from realm1 + ((session, realm1) -> { + + List configs = realm1.getAuthenticatorConfigs(); + return configs.get(0).getId(); + + }), + // Test lookup realm1 object in realm2 should not work + ((session, realm2, realm1AuthConfigId) -> { + + AuthenticatorConfigModel config = realm2.getAuthenticatorConfigById(realm1AuthConfigId); + Assert.assertNull(config); + + }), + // Try to update some object in realm1 through the realm2 + ((session, realm1, realm2, realm1AuthConfigId) -> { + + AuthenticatorConfigModel config = realm1.getAuthenticatorConfigById(realm1AuthConfigId); + config.getConfig().put("key1", "val1"); + realm2.updateAuthenticatorConfig(config); + + }), + // Test update from above was not successful + ((session, realm1, realm1AuthConfigId) -> { + + AuthenticatorConfigModel config = realm1.getAuthenticatorConfigById(realm1AuthConfigId); + Assert.assertNull(config.getConfig().get("key1")); + + }), + // Try remove object from realm1 in the context of realm2 + ((session, realm1, realm2, realm1AuthConfigId) -> { + + AuthenticatorConfigModel config = realm1.getAuthenticatorConfigById(realm1AuthConfigId); + realm2.removeAuthenticatorConfig(config); + + }), + // Test remove from above was not successful + ((session, realm1, realm1AuthConfigId) -> { + + AuthenticatorConfigModel config = realm1.getAuthenticatorConfigById(realm1AuthConfigId); + Assert.assertNotNull(config); + + }) + ); + } + + + @Test + @ModelTest + public void clientInitialAccessTest(KeycloakSession session1) { + doTest(session1, + // Get ID of some object from realm1 + ((session, realm1) -> { + + ClientInitialAccessModel clientInitialAccess = session.getProvider(RealmProvider.class).createClientInitialAccessModel(realm1, 10, 20); + return clientInitialAccess.getId(); + + }), + // Test lookup realm1 object in realm2 should not work + ((session, realm2, realm1ClientInitialAccessId) -> { + + ClientInitialAccessModel clientInitialAccess = session.getProvider(RealmProvider.class).getClientInitialAccessModel(realm2, realm1ClientInitialAccessId); + Assert.assertNull(clientInitialAccess); + + }), + // Try to update some object in realm1 through the realm2 + ((session, realm1, realm2, realm1ClientInitialAccessId) -> { + + // No-op, update not supported for clientInitialAccessModel + + }), + // Test update from above was not successful + ((session, realm1, realm1ClientInitialAccessId) -> { + + // No-op, update not supported for clientInitialAccessModel + + }), + // Try remove object from realm1 in the context of realm2 + ((session, realm1, realm2, realm1ClientInitialAccessId) -> { + + session.getProvider(RealmProvider.class).removeClientInitialAccessModel(realm2, realm1ClientInitialAccessId); + + }), + // Test remove from above was not successful + ((session, realm1, realm1ClientInitialAccessId) -> { + + ClientInitialAccessModel clientInitialAccess = session.getProvider(RealmProvider.class).getClientInitialAccessModel(realm1, realm1ClientInitialAccessId); + Assert.assertNotNull(clientInitialAccess); + + }) + ); + } + + @Test + @ModelTest + public void rolesTest(KeycloakSession session1) { + doTest(session1, + // Get ID of some object from realm1 + ((session, realm1) -> { + + RoleModel role = session.getProvider(RealmProvider.class).addRealmRole(realm1, "foo"); + realm1.addDefaultRole("foo"); + return role.getId(); + + }), + // Test lookup realm1 object in realm2 should not work + ((session, realm2, realm1RoleId) -> { + + RoleModel role = session.getProvider(RealmProvider.class).getRoleById(realm1RoleId, realm2); + Assert.assertNull(role); + + }), + // Try to update some object in realm1 through the realm2 + ((session, realm1, realm2, realm1RoleId) -> { + + // No-op, update done directly by calling operations on RoleModel. No explicit updateRole method on the RealmModel + + }), + // Test update from above was not successful + ((session, realm1, realm1RoleId) -> { + + // No-op, update done directly by calling operations on RoleModel. No explicit updateRole method on the RealmModel + + }), + // Try remove object from realm1 in the context of realm2 + ((session, realm1, realm2, realm1RoleId) -> { + + RoleModel role = session.getProvider(RealmProvider.class).getRoleById(realm1RoleId, realm1); + session.getProvider(RealmProvider.class).removeRole(realm2, role); + + }), + // Test remove from above was not successful + ((session, realm1, realm1RoleId) -> { + + RoleModel role = session.getProvider(RealmProvider.class).getRoleById(realm1RoleId, realm1); + Assert.assertNotNull(role); + Assert.assertTrue(realm1.getDefaultRoles().contains("foo")); + + }) + ); + } + + @Test + @ModelTest + public void userSessionsTest(KeycloakSession session1) { + doTest(session1, + // Get ID of some object from realm1 + ((session, realm1) -> { + + UserModel user = session.users().getUserByUsername("test-user@localhost", realm1); + UserSessionModel userSession = session.sessions().createUserSession(realm1, user, user.getUsername(), "1.2.3.4", "bar", false, null, null); + return userSession.getId(); + + }), + // Test lookup realm1 object in realm2 should not work + ((session, realm2, realm1SessionId) -> { + + UserSessionModel userSession = session.sessions().getUserSession(realm2, realm1SessionId); + Assert.assertNull(userSession); + + }), + // Try to update some object in realm1 through the realm2 + ((session, realm1, realm2, realm1SessionId) -> { + + // No-op, update done directly by calling operations on UserSessionModel. No explicit update method + + }), + // Test update from above was not successful + ((session, realm1, realm1SessionId) -> { + + // No-op, update done directly by calling operations on UserSessionModel. No explicit update method. + + }), + // Try remove object from realm1 in the context of realm2 + ((session, realm1, realm2, realm1SessionId) -> { + + UserSessionModel userSession = session.sessions().getUserSession(realm1, realm1SessionId); + session.sessions().removeUserSession(realm2, userSession); + + }), + // Test remove from above was not successful + ((session, realm1, realm1SessionId) -> { + + UserSessionModel userSession = session.sessions().getUserSession(realm1, realm1SessionId); + Assert.assertNotNull(userSession); + + }) + ); + } + + + private void doTest(KeycloakSession session1, + BiFunction realm1ObjectIdProducer, + TriConsumer testLookupRealm1ObjectInRealm2, + TetraConsumer updaterRealm1ObjectInRealm2, + TriConsumer testUpdateFailed, + TetraConsumer removeRealm1ObjectInRealm2, + TriConsumer testRemoveFailed + ) { + + // Transaction 1 - Lookup object of realm1 + AtomicReference realm1ObjectId = new AtomicReference<>(); + KeycloakModelUtils.runJobInTransaction(session1.getKeycloakSessionFactory(), (KeycloakSession session) -> { + + RealmModel realm1 = session.getProvider(RealmProvider.class).getRealm("test"); + + realm1ObjectId.set(realm1ObjectIdProducer.apply(session, realm1)); + + }); + + // Transaction 2 + KeycloakModelUtils.runJobInTransaction(session1.getKeycloakSessionFactory(), (KeycloakSession session) -> { + + RealmModel realm1 = session.getProvider(RealmProvider.class).getRealm("test"); + RealmModel realm2 = session.getProvider(RealmProvider.class).getRealm("foo"); + + testLookupRealm1ObjectInRealm2.accept(session, realm2, realm1ObjectId.get()); + updaterRealm1ObjectInRealm2.accept(session, realm1, realm2, realm1ObjectId.get()); + + }); + + // Transaction 3 + KeycloakModelUtils.runJobInTransaction(session1.getKeycloakSessionFactory(), (KeycloakSession session) -> { + RealmModel realm1 = session.getProvider(RealmProvider.class).getRealm("test"); + + testUpdateFailed.accept(session, realm1, realm1ObjectId.get()); + }); + + // Transaction 4 + try { + KeycloakModelUtils.runJobInTransaction(session1.getKeycloakSessionFactory(), (KeycloakSession session) -> { + RealmModel realm1 = session.getProvider(RealmProvider.class).getRealm("test"); + RealmModel realm2 = session.getProvider(RealmProvider.class).getRealm("foo"); + removeRealm1ObjectInRealm2.accept(session, realm1, realm2, realm1ObjectId.get()); + + }); + } catch (ModelException e) { + // This is fine. Attempt to remove on incorrect object can throw an exception in some cases, which will enforce transaction rollback + } + + // Transaction 5 + KeycloakModelUtils.runJobInTransaction(session1.getKeycloakSessionFactory(), (KeycloakSession session) -> { + RealmModel realm1 = session.getProvider(RealmProvider.class).getRealm("test"); + + testRemoveFailed.accept(session, realm1, realm1ObjectId.get()); + }); + } + + @FunctionalInterface + public interface TriConsumer { + void accept(T var1, U var2, V var3); + } + + @FunctionalInterface + public interface TetraConsumer { + void accept(T var1, U var2, V var3, W var4); + } +}