From 04bd6653ecc7e16ad7b82bf64eccc8593fe96602 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Thu, 18 Jul 2024 21:21:50 -0300 Subject: [PATCH] Invalidating domain cache and introducing cache for more query methods Signed-off-by: Pedro Igor --- .../organization/CachedMembership.java | 49 +++++ .../organization/CachedOrganizationIds.java | 48 +++++ .../InfinispanOrganizationProvider.java | 144 ++++++++++--- ...InfinispanOrganizationProviderFactory.java | 19 +- .../organization/OrganizationAdapter.java | 6 +- .../jpa/JpaOrganizationProvider.java | 3 +- .../AbstractBrokerSelfRegistrationTest.java | 1 - .../cache/OrganizationCacheTest.java | 190 ++++++++++++++++++ 8 files changed, 417 insertions(+), 43 deletions(-) create mode 100644 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedMembership.java create mode 100644 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedOrganizationIds.java create mode 100755 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedMembership.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedMembership.java new file mode 100644 index 0000000000..ec20218dee --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedMembership.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.models.cache.infinispan.organization; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned; +import org.keycloak.models.cache.infinispan.entities.InRealm; + +public class CachedMembership extends AbstractRevisioned implements InRealm { + + private final RealmModel realm; + private final boolean managed; + private final boolean isMember; + + public CachedMembership(Long revision, String key, RealmModel realm, boolean managed, boolean isMember) { + super(revision, key); + this.realm = realm; + this.managed = managed; + this.isMember = isMember; + } + + @Override + public String getRealm() { + return realm.getId(); + } + + public boolean isManaged() { + return managed; + } + + public boolean isMember() { + return isMember; + } +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedOrganizationIds.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedOrganizationIds.java new file mode 100644 index 0000000000..23323092dd --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedOrganizationIds.java @@ -0,0 +1,48 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.models.cache.infinispan.organization; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import org.keycloak.models.OrganizationModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned; +import org.keycloak.models.cache.infinispan.entities.InRealm; + +public class CachedOrganizationIds extends AbstractRevisioned implements InRealm { + + private final RealmModel realm; + private final Set orgIds = new HashSet<>(); + + public CachedOrganizationIds(Long revision, String id, RealmModel realm, Stream organizations) { + super(revision, id); + this.realm = realm; + organizations.map(OrganizationModel::getId).forEach(orgIds::add); + } + + @Override + public String getRealm() { + return realm.getId(); + } + + public Set getOrgIds() { + return orgIds; + } +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProvider.java index 156b91fc6a..37a4afaac6 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProvider.java @@ -21,7 +21,7 @@ import java.util.Map; import java.util.stream.Stream; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.MembershipMetadata; +import org.keycloak.models.OrganizationDomainModel; import org.keycloak.models.OrganizationModel; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; @@ -56,7 +56,7 @@ public class InfinispanOrganizationProvider implements OrganizationProvider { @Override public boolean remove(OrganizationModel organization) { - registerOrganizationInvalidation(organization.getId()); + registerOrganizationInvalidation(organization); registerCountInvalidation(); return orgDelegate.remove(organization); } @@ -73,47 +73,42 @@ public class InfinispanOrganizationProvider implements OrganizationProvider { Long loaded = realmCache.getCache().getCurrentRevision(id); OrganizationModel model = orgDelegate.getById(id); if (model == null) return null; - if (realmCache.getInvalidations().contains(id)) return model; + if (isInvalid(id)) return model; cached = new CachedOrganization(loaded, getRealm(), model); realmCache.getCache().addRevisioned(cached, realmCache.getStartupRevision()); // no need to check for realm invalidation as IdP changes are handled by events within InfinispanOrganizationProviderFactory - } else if (realmCache.getInvalidations().contains(id)) { + } else if (isInvalid(id)) { return orgDelegate.getById(id); } else if (managedOrganizations.containsKey(id)) { return managedOrganizations.get(id); } - OrganizationAdapter adapter = new OrganizationAdapter(cached, realmCache, orgDelegate); + OrganizationAdapter adapter = new OrganizationAdapter(cached, realmCache, orgDelegate, this); managedOrganizations.put(id, adapter); return adapter; } @Override public OrganizationModel getByDomainName(String domainName) { - String cacheKey = getRealm().getId() + "+.org.domain.name." + domainName; - CachedOrganization cached = realmCache.getCache().get(cacheKey, CachedOrganization.class); - String realmId = getRealm().getId(); - if (cached != null && !cached.getRealm().equals(realmId)) { - cached = null; + String cacheKey = cacheKeyByDomain(domainName); + + if (isInvalid(cacheKey)) { + return orgDelegate.getByDomainName(domainName); } + CachedOrganizationIds cached = realmCache.getCache().get(cacheKey, CachedOrganizationIds.class); + if (cached == null) { Long loaded = realmCache.getCache().getCurrentRevision(cacheKey); OrganizationModel model = orgDelegate.getByDomainName(domainName); - if (model == null) return null; - if (realmCache.getInvalidations().contains(model.getId())) return model; - cached = new CachedOrganization(loaded, getRealm(), model); + if (model == null) { + return null; + } + cached = new CachedOrganizationIds(loaded, cacheKey, getRealm(), Stream.of(model)); realmCache.getCache().addRevisioned(cached, realmCache.getStartupRevision()); - - // no need to check for realm invalidation as IdP changes are handled by events within InfinispanOrganizationProviderFactory - } else if (realmCache.getInvalidations().contains(cached.getId())) { - return orgDelegate.getByDomainName(domainName); - } else if (managedOrganizations.containsKey(cached.getId())) { - return managedOrganizations.get(cached.getId()); } - OrganizationAdapter adapter = new OrganizationAdapter(cached, realmCache, orgDelegate); - managedOrganizations.put(cacheKey, adapter); - return adapter; + + return cached.getOrgIds().stream().map(this::getById).findAny().orElse(null); } @Override @@ -137,16 +132,19 @@ public class InfinispanOrganizationProvider implements OrganizationProvider { @Override public boolean addManagedMember(OrganizationModel organization, UserModel user) { + registerMemberInvalidation(organization, user); return orgDelegate.addManagedMember(organization, user); } @Override public boolean addMember(OrganizationModel organization, UserModel user) { + registerMemberInvalidation(organization, user); return orgDelegate.addMember(organization, user); } @Override public boolean removeMember(OrganizationModel organization, UserModel member) { + registerMemberInvalidation(organization, member); return orgDelegate.removeMember(organization, member); } @@ -157,24 +155,76 @@ public class InfinispanOrganizationProvider implements OrganizationProvider { @Override public UserModel getMemberById(OrganizationModel organization, String id) { - return orgDelegate.getMemberById(organization, id); + RealmModel realm = getRealm(); + UserModel user = session.users().getUserById(realm, id); + + if (user == null) { + return null; + } + + String cacheKey = cacheKeyMembership(realm, organization, user); + + if (isInvalid(cacheKey)) { + return orgDelegate.getMemberById(organization, user.getId()); + } + + CachedMembership cached = realmCache.getCache().get(cacheKey, CachedMembership.class); + + if (cached == null) { + boolean isManaged = orgDelegate.isManagedMember(organization, user); + Long loaded = realmCache.getCache().getCurrentRevision(cacheKey); + UserModel member = orgDelegate.getMemberById(organization, user.getId()); + cached = new CachedMembership(loaded, cacheKeyMembership(realm, organization, user), realm, isManaged, member != null); + realmCache.getCache().addRevisioned(cached, realmCache.getStartupRevision()); + } + + return cached.isMember() ? user : null; } @Override public Stream getByMember(UserModel member) { - return orgDelegate.getByMember(member); + String cacheKey = cacheKeyByMember(member); + + if (isInvalid(cacheKey)) { + return orgDelegate.getByMember(member); + } + + CachedOrganizationIds cached = realmCache.getCache().get(cacheKey, CachedOrganizationIds.class); + + if (cached == null) { + Long loaded = realmCache.getCache().getCurrentRevision(cacheKey); + Stream model = orgDelegate.getByMember(member); + cached = new CachedOrganizationIds(loaded, cacheKey, getRealm(), model); + realmCache.getCache().addRevisioned(cached, realmCache.getStartupRevision()); + } + + return cached.getOrgIds().stream().map(this::getById); } @Override - public boolean isManagedMember(OrganizationModel organization, UserModel member) { - return orgDelegate.isManagedMember(organization, member); + public boolean isManagedMember(OrganizationModel organization, UserModel user) { + UserModel member = getMemberById(organization, user.getId()); + + if (member == null) { + return false; + } + + String cacheKey = cacheKeyMembership(getRealm(), organization, member); + CachedMembership cached = realmCache.getCache().get(cacheKey, CachedMembership.class); + + if (cached == null) { + return orgDelegate.isManagedMember(organization, user); + } + + return cached.isManaged(); + } @Override public boolean addIdentityProvider(OrganizationModel organization, IdentityProviderModel identityProvider) { boolean added = orgDelegate.addIdentityProvider(organization, identityProvider); if (added) { - registerOrganizationInvalidation(organization.getId()); + registerOrganizationInvalidation(organization); } return added; } @@ -188,7 +238,7 @@ public class InfinispanOrganizationProvider implements OrganizationProvider { public boolean removeIdentityProvider(OrganizationModel organization, IdentityProviderModel identityProvider) { boolean removed = orgDelegate.removeIdentityProvider(organization, identityProvider); if (removed) { - registerOrganizationInvalidation(organization.getId()); + registerOrganizationInvalidation(organization); } return removed; } @@ -204,7 +254,7 @@ public class InfinispanOrganizationProvider implements OrganizationProvider { CachedOrganizationCount cached = realmCache.getCache().get(cacheKey, CachedOrganizationCount.class); // cached and not invalidated - if (cached != null && !realmCache.getInvalidations().contains(cacheKey)) { + if (cached != null && !isInvalid(cacheKey)) { return cached.getCount(); } @@ -221,13 +271,20 @@ public class InfinispanOrganizationProvider implements OrganizationProvider { orgDelegate.close(); } - void registerOrganizationInvalidation(String orgId) { - OrganizationAdapter adapter = managedOrganizations.get(orgId); + void registerOrganizationInvalidation(OrganizationModel organization) { + String id = organization.getId(); + + realmCache.registerInvalidation(id); + organization.getDomains() + .map(OrganizationDomainModel::getName) + .map(this::cacheKeyByDomain) + .forEach(realmCache::registerInvalidation); + + OrganizationAdapter adapter = managedOrganizations.get(id); + if (adapter != null) { adapter.invalidate(); } - - realmCache.registerInvalidation(orgId); } private void registerCountInvalidation() { @@ -245,4 +302,25 @@ public class InfinispanOrganizationProvider implements OrganizationProvider { private Stream getCacheDelegates(Stream backendOrganizations) { return backendOrganizations.map(OrganizationModel::getId).map(this::getById); } + + private String cacheKeyByDomain(String domainName) { + return getRealm().getId() + ".org.domain.name." + domainName; + } + + private String cacheKeyByMember(UserModel user) { + return getRealm().getId() + ".org.member." + user.getId() + ".orgs"; + } + + private String cacheKeyMembership(RealmModel realm, OrganizationModel organization, UserModel user) { + return realm.getId() + ".org." + organization.getId() + ".member." + user.getId() + ".membership"; + } + + void registerMemberInvalidation(OrganizationModel organization, UserModel member) { + realmCache.registerInvalidation(cacheKeyByMember(member)); + realmCache.registerInvalidation(cacheKeyMembership(getRealm(), organization, member)); + } + + private boolean isInvalid(String cacheKey) { + return realmCache.getInvalidations().contains(cacheKey); + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProviderFactory.java index 7293c10009..872dcb946f 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProviderFactory.java @@ -22,6 +22,7 @@ import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; import org.keycloak.organization.OrganizationProvider; import org.keycloak.models.OrganizationModel; import org.keycloak.organization.OrganizationProviderFactory; @@ -41,12 +42,17 @@ public class InfinispanOrganizationProviderFactory implements OrganizationProvid @Override public void postInit(KeycloakSessionFactory factory) { - factory.register(event -> { - if (event instanceof RealmModel.IdentityProviderUpdatedEvent idpUpdatedEvent) { - registerOrganizationInvalidation(idpUpdatedEvent.getKeycloakSession(), idpUpdatedEvent.getUpdatedIdentityProvider()); + factory.register(e -> { + if (e instanceof RealmModel.IdentityProviderUpdatedEvent event) { + registerOrganizationInvalidation(event.getKeycloakSession(), event.getUpdatedIdentityProvider()); } - if (event instanceof RealmModel.IdentityProviderRemovedEvent idpRemovedEvent) { - registerOrganizationInvalidation(idpRemovedEvent.getKeycloakSession(), idpRemovedEvent.getRemovedIdentityProvider()); + if (e instanceof RealmModel.IdentityProviderRemovedEvent event) { + registerOrganizationInvalidation(event.getKeycloakSession(), event.getRemovedIdentityProvider()); + } + if (e instanceof UserModel.UserRemovedEvent event) { + KeycloakSession session = event.getKeycloakSession(); + InfinispanOrganizationProvider orgProvider = (InfinispanOrganizationProvider) session.getProvider(OrganizationProvider.class, getId()); + orgProvider.getByMember(event.getUser()).forEach(organization -> orgProvider.registerMemberInvalidation(organization, event.getUser())); } }); } @@ -55,7 +61,8 @@ public class InfinispanOrganizationProviderFactory implements OrganizationProvid if (idp.getConfig().get(OrganizationModel.ORGANIZATION_ATTRIBUTE) != null) { InfinispanOrganizationProvider orgProvider = (InfinispanOrganizationProvider) session.getProvider(OrganizationProvider.class, getId()); if (orgProvider != null) { - orgProvider.registerOrganizationInvalidation(idp.getConfig().get(OrganizationModel.ORGANIZATION_ATTRIBUTE)); + OrganizationModel organization = orgProvider.getById(idp.getConfig().get(OrganizationModel.ORGANIZATION_ATTRIBUTE)); + orgProvider.registerOrganizationInvalidation(organization); } } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/OrganizationAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/OrganizationAdapter.java index faf64411b3..10f5f08a70 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/OrganizationAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/OrganizationAdapter.java @@ -36,11 +36,13 @@ public class OrganizationAdapter implements OrganizationModel { private final CacheRealmProvider realmCache; private final CachedOrganization cached; private final OrganizationProvider delegate; + private final InfinispanOrganizationProvider organizationCache; - public OrganizationAdapter(CachedOrganization cached, CacheRealmProvider realmCache, OrganizationProvider delegate) { + public OrganizationAdapter(CachedOrganization cached, CacheRealmProvider realmCache, OrganizationProvider delegate, InfinispanOrganizationProvider organizationCache) { this.cached = cached; this.realmCache = realmCache; this.delegate = delegate; + this.organizationCache = organizationCache; this.modelSupplier = this::getOrganizationModel; } @@ -62,8 +64,8 @@ public class OrganizationAdapter implements OrganizationModel { private void getDelegateForUpdate() { if (updated == null) { - realmCache.registerInvalidation(cached.getId()); updated = modelSupplier.get(); + organizationCache.registerOrganizationInvalidation(updated); if (updated == null) throw new IllegalStateException("Not found in database"); } } diff --git a/model/jpa/src/main/java/org/keycloak/organization/jpa/JpaOrganizationProvider.java b/model/jpa/src/main/java/org/keycloak/organization/jpa/JpaOrganizationProvider.java index 00b5400ef1..92fd299abe 100644 --- a/model/jpa/src/main/java/org/keycloak/organization/jpa/JpaOrganizationProvider.java +++ b/model/jpa/src/main/java/org/keycloak/organization/jpa/JpaOrganizationProvider.java @@ -125,8 +125,9 @@ public class JpaOrganizationProvider implements OrganizationProvider { GroupModel group = getOrganizationGroup(entity); if (group != null) { + OrganizationProvider provider = session.getProvider(OrganizationProvider.class); //TODO: won't scale, requires a better mechanism for bulk deleting users - userProvider.getGroupMembersStream(realm, group).forEach(userModel -> removeMember(organization, userModel)); + userProvider.getGroupMembersStream(realm, group).forEach(userModel -> provider.removeMember(organization, userModel)); groupProvider.removeGroup(realm, group); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/broker/AbstractBrokerSelfRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/broker/AbstractBrokerSelfRegistrationTest.java index 6dae60fbcd..e3683aaa79 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/broker/AbstractBrokerSelfRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/broker/AbstractBrokerSelfRegistrationTest.java @@ -417,7 +417,6 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz List federatedIdentities = testRealm().users().get(user.getId()).getFederatedIdentity(); assertEquals(1, federatedIdentities.size()); assertEquals(bc.getIDPAlias(), federatedIdentities.get(0).getIdentityProvider()); - } @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java new file mode 100755 index 0000000000..6399700d4d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/cache/OrganizationCacheTest.java @@ -0,0 +1,190 @@ +/* + * Copyright 2024 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.organization.cache; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.common.Profile.Feature; +import org.keycloak.models.OrganizationDomainModel; +import org.keycloak.models.OrganizationModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.organization.OrganizationProvider; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.arquillian.annotation.EnableFeature; +import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest; +import org.keycloak.testsuite.runonserver.RunOnServer; + +@EnableFeature(Feature.ORGANIZATION) +public class OrganizationCacheTest extends AbstractOrganizationTest { + + @Before + public void onBefore() { + createOrganization("orga"); + createOrganization("orgb"); + } + + @After + public void onAfter() { + List users = testRealm().users().search("member"); + + if (!users.isEmpty()) { + UserRepresentation member = users.get(0); + testRealm().users().get(member.getId()).remove(); + } + } + + @Test + public void testGetByDomain() { + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel acme = orgProvider.getByDomainName("orga.org"); + assertNotNull(acme); + acme.setDomains(Set.of(new OrganizationDomainModel("acme.org"))); + }); + + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel acme = orgProvider.getByDomainName("orga.org"); + assertNull(acme); + acme = orgProvider.getByDomainName("acme.org"); + assertNotNull(acme); + }); + + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel acme = orgProvider.getByDomainName("acme.org"); + assertNotNull(acme); + orgProvider.remove(acme); + }); + + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel acme = orgProvider.getByDomainName("acme.org"); + assertNull(acme); + }); + } + + @Test + public void testGetByMember() { + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel orga = orgProvider.getByDomainName("orga.org"); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().addUser(realm, "member"); + member.setEnabled(true); + orgProvider.addMember(orga, member); + OrganizationModel orgb = orgProvider.getByDomainName("orgb.org"); + orgProvider.addMember(orgb, member); + }); + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().getUserByUsername(realm, "member"); + Stream memberOf = orgProvider.getByMember(member); + assertEquals(2, memberOf.count()); + }); + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel orga = orgProvider.getByDomainName("orga.org"); + orgProvider.remove(orga); + }); + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().getUserByUsername(realm, "member"); + Stream memberOf = orgProvider.getByMember(member); + assertEquals(1, memberOf.count()); + }); + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().getUserByUsername(realm, "member"); + OrganizationModel orgb = orgProvider.getByDomainName("orgb.org"); + orgProvider.removeMember(orgb, member); + }); + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().getUserByUsername(realm, "member"); + Stream memberOf = orgProvider.getByMember(member); + assertEquals(0, memberOf.count()); + }); + } + + @Test + public void testGetByMemberId() { + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel orga = orgProvider.getByDomainName("orga.org"); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().addUser(realm, "member"); + member.setEnabled(true); + orgProvider.addMember(orga, member); + OrganizationModel orgb = orgProvider.getByDomainName("orgb.org"); + orgProvider.addMember(orgb, member); + }); + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel org = orgProvider.getByDomainName("orga.org"); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().getUserByUsername(realm, "member"); + UserModel memberOf = orgProvider.getMemberById(org, member.getId()); + assertNotNull(memberOf); + }); + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel org = orgProvider.getByDomainName("orga.org"); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().getUserByUsername(realm, "member"); + orgProvider.removeMember(org, member); + }); + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel orga = orgProvider.getByDomainName("orga.org"); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().getUserByUsername(realm, "member"); + assertNull(orgProvider.getMemberById(orga, member.getId())); + OrganizationModel orgb = orgProvider.getByDomainName("orgb.org"); + assertNotNull(orgProvider.getMemberById(orgb, member.getId())); + }); + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + OrganizationModel orgb = orgProvider.getByDomainName("orgb.org"); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().getUserByUsername(realm, "member"); + assertEquals(1, orgProvider.getByMember(member).count()); + orgProvider.remove(orgb); + }); + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> { + OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); + RealmModel realm = session.getContext().getRealm(); + UserModel member = session.users().getUserByUsername(realm, "member"); + assertEquals(0, orgProvider.getByMember(member).count()); + }); + } +}