Add enabled field to OrganizationEntity

Closes #28891

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
Stefan Guilhen 2024-05-03 17:51:44 -03:00 committed by Pedro Igor
parent 4c6f3ce35d
commit dae1eada3d
13 changed files with 171 additions and 39 deletions

View file

@ -28,6 +28,7 @@ public class OrganizationRepresentation {
private String id;
private String name;
private boolean enabled = true;
private Map<String, List<String>> attributes;
private Set<OrganizationDomainRepresentation> domains;
@ -47,6 +48,14 @@ public class OrganizationRepresentation {
return name;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}

View file

@ -19,10 +19,12 @@ package org.keycloak.models.cache.infinispan;
import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.Profile;
import org.keycloak.credential.CredentialInput;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.component.ComponentModel;
@ -54,6 +56,7 @@ import org.keycloak.models.cache.infinispan.events.UserUpdatedEvent;
import org.keycloak.models.cache.infinispan.stream.InIdentityProviderPredicate;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ReadOnlyUserModelDelegate;
import org.keycloak.organization.OrganizationProvider;
import org.keycloak.storage.CacheableStorageProviderModel;
import org.keycloak.storage.DatastoreProvider;
import org.keycloak.storage.StoreManagers;
@ -336,6 +339,20 @@ public class UserCacheSession implements UserCache, OnCreateComponent, OnUpdateC
protected UserModel cacheUser(RealmModel realm, UserModel delegate, Long revision) {
int notBefore = getDelegate().getNotBeforeOfUser(realm, delegate);
if (Profile.isFeatureEnabled(Profile.Feature.ORGANIZATION)) {
// check if user is member of a disabled organization.
OrganizationProvider organizationProvider = session.getProvider(OrganizationProvider.class);
OrganizationModel organization = organizationProvider.getByMember(delegate);
if (organization != null && organization.isManaged(delegate) && !organization.isEnabled()) {
return new ReadOnlyUserModelDelegate(delegate) {
@Override
public boolean isEnabled() {
return false;
}
};
}
}
StorageId storageId = delegate.getFederationLink() != null ?
new StorageId(delegate.getFederationLink(), delegate.getId()) : new StorageId(delegate.getId());
CachedUser cached = null;

View file

@ -51,6 +51,9 @@ public class OrganizationEntity {
@Column(name = "NAME")
private String name;
@Column(name = "ENABLED")
private boolean enabled;
@Column(name = "REALM_ID")
private String realmId;
@ -72,6 +75,14 @@ public class OrganizationEntity {
this.name = name;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getRealmId() {
return realmId;
}

View file

@ -80,6 +80,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
entity.setGroupId(group.getId());
entity.setRealmId(realm.getId());
entity.setName(name);
entity.setEnabled(true);
em.persist(entity);

View file

@ -76,6 +76,16 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
return entity.getName();
}
@Override
public boolean isEnabled() {
return entity.isEnabled();
}
@Override
public void setEnabled(boolean enabled) {
entity.setEnabled(enabled);
}
@Override
public void setAttributes(Map<String, List<String>> attributes) {
if (attributes == null) {

View file

@ -83,6 +83,9 @@
<column name="ID" type="VARCHAR(255)">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="ENABLED" type="BOOLEAN">
<constraints nullable="false"/>
</column>
<column name="REALM_ID" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>

View file

@ -33,6 +33,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jboss.logging.Logger;
import org.keycloak.common.Profile;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.common.util.reflections.Types;
import org.keycloak.component.ComponentFactory;
@ -50,6 +51,7 @@ import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -62,6 +64,7 @@ import org.keycloak.models.cache.OnUserCache;
import org.keycloak.models.cache.UserCache;
import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.ReadOnlyUserModelDelegate;
import org.keycloak.organization.OrganizationProvider;
import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.datastore.DefaultDatastoreProvider;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
@ -111,6 +114,20 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
* @return
*/
protected UserModel importValidation(RealmModel realm, UserModel user) {
if (Profile.isFeatureEnabled(Profile.Feature.ORGANIZATION) && user != null) {
// check if user belongs to a disabled organization
OrganizationProvider organizationProvider = session.getProvider(OrganizationProvider.class);
OrganizationModel organization = organizationProvider.getByMember(user);
if (organization != null && organization.isManaged(user) && !organization.isEnabled()) {
return new ReadOnlyUserModelDelegate(user) {
@Override
public boolean isEnabled() {
return false;
}
};
}
}
if (user == null || user.getFederationLink() == null) return user;
UserStorageProviderModel model = getStorageProviderModel(realm, user.getFederationLink());

View file

@ -34,6 +34,10 @@ public interface OrganizationModel {
String getName();
boolean isEnabled();
void setEnabled(boolean enabled);
Map<String, List<String>> getAttributes();
void setAttributes(Map<String, List<String>> attributes);

View file

@ -173,6 +173,7 @@ public class OrganizationResource {
rep.setId(model.getId());
rep.setName(model.getName());
rep.setEnabled(model.isEnabled());
rep.setAttributes(model.getAttributes());
model.getDomains().filter(Objects::nonNull).map(this::toRepresentation)
.forEach(rep::addDomain);
@ -193,6 +194,7 @@ public class OrganizationResource {
}
model.setName(rep.getName());
model.setEnabled(rep.isEnabled());
model.setAttributes(rep.getAttributes());
model.setDomains(Optional.ofNullable(rep.getDomains()).orElse(Set.of()).stream()
.filter(Objects::nonNull)

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.organization.admin;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
import java.util.List;
import java.util.function.Function;
@ -28,10 +29,12 @@ import jakarta.ws.rs.core.Response.Status;
import org.jboss.arquillian.graphene.page.Page;
import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.admin.AbstractAdminTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.admin.Users;
@ -200,4 +203,43 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
return actual;
}
}
protected void assertBrokerRegistration(OrganizationResource organization, String email) {
// login with email only
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
log.debug("Logging in");
Assert.assertFalse(loginPage.isPasswordInputPresent());
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
loginPage.loginUsername(email);
// user automatically redirected to the organization identity provider
waitForPage(driver, "sign in to", true);
Assert.assertTrue("Driver should be on the provider realm page right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
// login to the organization identity provider and run the configured first broker login flow
loginPage.login(email, bc.getUserPassword());
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
Assert.assertTrue("We must be on correct realm right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
log.debug("Updating info on updateAccount page");
updateAccountInformationPage.updateAccountInformation(bc.getUserLogin(), email, "Firstname", "Lastname");
assertIsMember(email, organization);
}
protected void assertIsMember(String userEmail, OrganizationResource organization) {
UserRepresentation account = getUserRepresentation(userEmail);
UserRepresentation member = organization.members().member(account.getId()).toRepresentation();
Assert.assertEquals(account.getId(), member.getId());
}
protected UserRepresentation getUserRepresentation(String userEmail) {
UsersResource users = adminClient.realm(bc.consumerRealmName()).users();
List<UserRepresentation> reps = users.searchByEmail(userEmail, true);
Assert.assertFalse(reps.isEmpty());
Assert.assertEquals(1, reps.size());
return reps.get(0);
}
}

View file

@ -582,37 +582,6 @@ public class OrganizationBrokerSelfRegistrationTest extends AbstractOrganization
appPage.assertCurrent();
}
private void assertBrokerRegistration(OrganizationResource organization, String email) {
// login with email only
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
log.debug("Logging in");
Assert.assertFalse(loginPage.isPasswordInputPresent());
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
loginPage.loginUsername(email);
// user automatically redirected to the organization identity provider
waitForPage(driver, "sign in to", true);
Assert.assertTrue("Driver should be on the provider realm page right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
// login to the organization identity provider and run the configured first broker login flow
loginPage.login(email, bc.getUserPassword());
waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
Assert.assertTrue("We must be on correct realm right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
log.debug("Updating info on updateAccount page");
updateAccountInformationPage.updateAccountInformation(bc.getUserLogin(), email, "Firstname", "Lastname");
assertIsMember(email, organization);
}
private void assertIsMember(String userEmail, OrganizationResource organization) {
UserRepresentation account = getUserRepresentation(userEmail);
UserRepresentation member = organization.members().member(account.getId()).toRepresentation();
Assert.assertEquals(account.getId(), member.getId());
}
private void assertIsNotMember(String userEmail, OrganizationResource organization) {
UsersResource users = adminClient.realm(bc.consumerRealmName()).users();
List<UserRepresentation> reps = users.searchByEmail(userEmail, true);
@ -629,12 +598,4 @@ public class OrganizationBrokerSelfRegistrationTest extends AbstractOrganization
} catch (NotFoundException ignore) {
}
}
private UserRepresentation getUserRepresentation(String userEmail) {
UsersResource users = adminClient.realm(bc.consumerRealmName()).users();
List<UserRepresentation> reps = users.searchByEmail(userEmail, true);
Assert.assertFalse(reps.isEmpty());
Assert.assertEquals(1, reps.size());
return reps.get(0);
}
}

View file

@ -22,6 +22,7 @@ import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@ -160,6 +161,55 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
assertEquals(expectedRep.getEmail(), existingRep.getEmail());
assertEquals(expectedRep.getFirstName(), existingRep.getFirstName());
assertEquals(expectedRep.getLastName(), existingRep.getLastName());
assertTrue(expectedRep.isEnabled());
}
}
@Test
public void testGetAllDisabledOrganization() {
OrganizationRepresentation orgRep = createOrganization();
OrganizationResource organization = testRealm().organizations().get(orgRep.getId());
// add some unmanaged members to the organization.
for (int i = 0; i < 5; i++) {
addMember(organization, "member-" + i + "@neworg.org");
}
// onboard a test user by authenticating using the organization's provider.
super.assertBrokerRegistration(organization, bc.getUserEmail());
// disable the organization and check that fetching its representation has it disabled.
orgRep.setEnabled(false);
try (Response response = organization.update(orgRep)) {
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
OrganizationRepresentation existingOrg = organization.toRepresentation();
assertThat(orgRep.getId(), is(equalTo(existingOrg.getId())));
assertThat(orgRep.getName(), is(equalTo(existingOrg.getName())));
assertThat(existingOrg.isEnabled(), is(false));
// now fetch all users from the org - unmanaged users should still be enabled, but managed ones should not.
List<UserRepresentation> existing = organization.members().getAll();;
assertThat(existing, not(empty()));
assertThat(existing, hasSize(6));
for (UserRepresentation user : existing) {
if (user.getEmail().equals(bc.getUserEmail())) {
assertThat(user.isEnabled(), is(false));
} else {
assertThat(user.isEnabled(), is(true));
}
}
// fetching users from the users endpoint should have the same result.
existing = testRealm().users().search("*neworg*",0, 10);
assertThat(existing, not(empty()));
assertThat(existing, hasSize(6));
for (UserRepresentation user : existing) {
if (user.getEmail().equals(bc.getUserEmail())) {
assertThat(user.isEnabled(), is(false));
} else {
assertThat(user.isEnabled(), is(true));
}
}
}

View file

@ -55,6 +55,7 @@ public class OrganizationTest extends AbstractOrganizationTest {
assertEquals(organizationName, expected.getName());
expected.setName("acme");
expected.setEnabled(false);
OrganizationResource organization = testRealm().organizations().get(expected.getId());
@ -66,6 +67,7 @@ public class OrganizationTest extends AbstractOrganizationTest {
assertEquals(expected.getId(), existing.getId());
assertEquals(expected.getName(), existing.getName());
assertEquals(1, existing.getDomains().size());
assertThat(existing.isEnabled(), is(false));
}
@Test
@ -75,6 +77,7 @@ public class OrganizationTest extends AbstractOrganizationTest {
assertNotNull(existing);
assertEquals(expected.getId(), existing.getId());
assertEquals(expected.getName(), existing.getName());
assertThat(expected.isEnabled(), is(true));
}
@Test
@ -103,6 +106,7 @@ public class OrganizationTest extends AbstractOrganizationTest {
assertThat(existing, hasSize(1));
OrganizationRepresentation orgRep = existing.get(0);
assertThat(orgRep.getName(), is(equalTo("wayne-industries")));
assertThat(orgRep.isEnabled(), is(true));
assertThat(orgRep.getDomains(), hasSize(2));
assertThat(orgRep.getDomain("wayneind.com"), not(nullValue()));
assertThat(orgRep.getDomain("wayneind-gotham.com"), not(nullValue()));
@ -111,6 +115,7 @@ public class OrganizationTest extends AbstractOrganizationTest {
assertThat(existing, hasSize(1));
orgRep = existing.get(0);
assertThat(orgRep.getName(), is(equalTo("Gotham-Bank")));
assertThat(orgRep.isEnabled(), is(true));
assertThat(orgRep.getDomains(), hasSize(2));
assertThat(orgRep.getDomain("gtbank.com"), not(nullValue()));
assertThat(orgRep.getDomain("gtbank.net"), not(nullValue()));