Make sure users created through a registration link are managed members

Closes #30743

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
vramik 2024-07-18 14:23:04 +02:00 committed by Pedro Igor
parent 83f8622d15
commit 649b35929e
27 changed files with 337 additions and 118 deletions

View file

@ -0,0 +1,39 @@
/*
* 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.representations.idm;
public class MemberRepresentation extends UserRepresentation {
private MembershipType membershipType;
public MemberRepresentation() {
super();
}
public MemberRepresentation(UserRepresentation user) {
super(user);
}
public MembershipType getMembershipType() {
return membershipType;
}
public void setMembershipType(MembershipType membershipType) {
this.membershipType = membershipType;
}
}

View file

@ -0,0 +1,30 @@
/*
* 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.representations.idm;
public enum MembershipType {
/**
* Indicates that member can exist without group/organization.
*/
UNMANAGED,
/**
* Indicates that member cannot exist without group/organization.
*/
MANAGED;
}

View file

@ -34,7 +34,7 @@ public class OrganizationRepresentation {
private String description; private String description;
private Map<String, List<String>> attributes; private Map<String, List<String>> attributes;
private Set<OrganizationDomainRepresentation> domains; private Set<OrganizationDomainRepresentation> domains;
private List<UserRepresentation> members; private List<MemberRepresentation> members;
private List<IdentityProviderRepresentation> identityProviders; private List<IdentityProviderRepresentation> identityProviders;
public String getId() { public String getId() {
@ -119,19 +119,19 @@ public class OrganizationRepresentation {
getDomains().remove(domain); getDomains().remove(domain);
} }
public List<UserRepresentation> getMembers() { public List<MemberRepresentation> getMembers() {
return members; return members;
} }
public void setMembers(List<UserRepresentation> members) { public void setMembers(List<MemberRepresentation> members) {
this.members = members; this.members = members;
} }
public void addMember(UserRepresentation user) { public void addMember(MemberRepresentation member) {
if (members == null) { if (members == null) {
members = new ArrayList<>(); members = new ArrayList<>();
} }
members.add(user); members.add(member);
} }
public List<IdentityProviderRepresentation> getIdentityProviders() { public List<IdentityProviderRepresentation> getIdentityProviders() {

View file

@ -52,6 +52,43 @@ public class UserRepresentation extends AbstractUserRepresentation{
protected List<String> groups; protected List<String> groups;
private Map<String, Boolean> access; private Map<String, Boolean> access;
public UserRepresentation() {
}
public UserRepresentation(UserRepresentation rep) {
// AbstractUserRepresentation
this.id = rep.getId();
this.username = rep.getUsername();
this.firstName = rep.getFirstName();
this.lastName = rep.getLastName();
this.email = rep.getEmail();
this.emailVerified = rep.isEmailVerified();
this.attributes = rep.getAttributes();
this.setUserProfileMetadata(rep.getUserProfileMetadata());
this.self = rep.getSelf();
this.origin = rep.getOrigin();
this.createdTimestamp = rep.getCreatedTimestamp();
this.enabled = rep.isEnabled();
this.totp = rep.isTotp();
this.federationLink = rep.getFederationLink();
this.serviceAccountClientId = rep.getServiceAccountClientId();
this.credentials = rep.getCredentials();
this.disableableCredentialTypes = rep.getDisableableCredentialTypes();
this.requiredActions = rep.getRequiredActions();
this.federatedIdentities = rep.getFederatedIdentities();
this.realmRoles = rep.getRealmRoles();
this.clientRoles = rep.getClientRoles();
this.clientConsents = rep.getClientConsents();
this.notBefore = rep.getNotBefore();
this.applicationRoles = rep.getApplicationRoles();
this.socialLinks = rep.getSocialLinks();
this.groups = rep.getGroups();
this.access = rep.getAccess();
}
public String getSelf() { public String getSelf() {
return self; return self;
} }

View file

@ -22,13 +22,13 @@ import javax.ws.rs.GET;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.MemberRepresentation;
public interface OrganizationMemberResource { public interface OrganizationMemberResource {
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
UserRepresentation toRepresentation(); MemberRepresentation toRepresentation();
@DELETE @DELETE
Response delete(); Response delete();

View file

@ -29,8 +29,8 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.keycloak.representations.idm.MemberRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
public interface OrganizationMembersResource { public interface OrganizationMembersResource {
@ -45,7 +45,7 @@ public interface OrganizationMembersResource {
*/ */
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
List<UserRepresentation> getAll(); List<MemberRepresentation> getAll();
/** /**
* Return all organization members that match the specified filters. * Return all organization members that match the specified filters.
@ -60,7 +60,7 @@ public interface OrganizationMembersResource {
*/ */
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
List<UserRepresentation> search( List<MemberRepresentation> search(
@QueryParam("search") String search, @QueryParam("search") String search,
@QueryParam("exact") Boolean exact, @QueryParam("exact") Boolean exact,
@QueryParam("first") Integer first, @QueryParam("first") Integer first,

View file

@ -21,6 +21,7 @@ import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.MembershipMetadata;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
@ -134,6 +135,11 @@ public class InfinispanOrganizationProvider implements OrganizationProvider {
getAllStream().forEach(this::remove); getAllStream().forEach(this::remove);
} }
@Override
public boolean addManagedMember(OrganizationModel organization, UserModel user) {
return orgDelegate.addManagedMember(organization, user);
}
@Override @Override
public boolean addMember(OrganizationModel organization, UserModel user) { public boolean addMember(OrganizationModel organization, UserModel user) {
return orgDelegate.addMember(organization, user); return orgDelegate.addMember(organization, user);

View file

@ -26,6 +26,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.GroupModel.GroupMemberJoinEvent; import org.keycloak.models.GroupModel.GroupMemberJoinEvent;
import org.keycloak.models.GroupModel.GroupMemberLeaveEvent; import org.keycloak.models.GroupModel.GroupMemberLeaveEvent;
import org.keycloak.models.MembershipMetadata;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
@ -55,6 +56,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.keycloak.representations.idm.MembershipType;
import static org.keycloak.utils.StreamsUtil.closing; import static org.keycloak.utils.StreamsUtil.closing;
@ -167,7 +169,7 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
@Override @Override
public void setAttribute(String name, List<String> values) { public void setAttribute(String name, List<String> values) {
String valueToSet = (values != null && values.size() > 0) ? values.get(0) : null; String valueToSet = (values != null && !values.isEmpty()) ? values.get(0) : null;
if (UserModel.FIRST_NAME.equals(name)) { if (UserModel.FIRST_NAME.equals(name)) {
user.setFirstName(valueToSet); user.setFirstName(valueToSet);
return; return;
@ -363,7 +365,7 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
predicates.add(builder.equal(root.get("user"), getEntity())); predicates.add(builder.equal(root.get("user"), getEntity()));
queryBuilder.select(root.get("groupId")); queryBuilder.select(root.get("groupId"));
queryBuilder.where(predicates.toArray(new Predicate[0])); queryBuilder.where(predicates.toArray(Predicate[]::new));
return em.createQuery(queryBuilder); return em.createQuery(queryBuilder);
} }
@ -414,20 +416,28 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
@Override @Override
public void joinGroup(GroupModel group) { public void joinGroup(GroupModel group) {
if (RoleUtils.isDirectMember(getGroupsStream(), group)) return; joinGroup(group, null);
joinGroupImpl(group); }
@Override
public void joinGroup(GroupModel group, MembershipMetadata metadata) {
if (RoleUtils.isDirectMember(getGroupsStream(), group)) return;
joinGroupImpl(group, metadata);
} }
protected void joinGroupImpl(GroupModel group) { protected void joinGroupImpl(GroupModel group) {
joinGroupImpl(group, null);
}
protected void joinGroupImpl(GroupModel group, MembershipMetadata metadata) {
UserGroupMembershipEntity entity = new UserGroupMembershipEntity(); UserGroupMembershipEntity entity = new UserGroupMembershipEntity();
entity.setUser(getEntity()); entity.setUser(getEntity());
entity.setGroupId(group.getId()); entity.setGroupId(group.getId());
entity.setMembershipType(metadata == null ? MembershipType.UNMANAGED : metadata.getMembershipType());
em.persist(entity); em.persist(entity);
em.flush(); em.flush();
em.detach(entity); em.detach(entity);
GroupMemberJoinEvent.fire(group, session); GroupMemberJoinEvent.fire(group, session);
} }
@Override @Override
@ -437,7 +447,7 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
TypedQuery<UserGroupMembershipEntity> query = getUserGroupMappingQuery(group); TypedQuery<UserGroupMembershipEntity> query = getUserGroupMappingQuery(group);
query.setLockMode(LockModeType.PESSIMISTIC_WRITE); query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
List<UserGroupMembershipEntity> results = query.getResultList(); List<UserGroupMembershipEntity> results = query.getResultList();
if (results.size() == 0) return; if (results.isEmpty()) return;
for (UserGroupMembershipEntity entity : results) { for (UserGroupMembershipEntity entity : results) {
em.remove(entity); em.remove(entity);
} }
@ -508,7 +518,7 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
TypedQuery<UserRoleMappingEntity> query = getUserRoleMappingEntityTypedQuery(role); TypedQuery<UserRoleMappingEntity> query = getUserRoleMappingEntityTypedQuery(role);
query.setLockMode(LockModeType.PESSIMISTIC_WRITE); query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
List<UserRoleMappingEntity> results = query.getResultList(); List<UserRoleMappingEntity> results = query.getResultList();
if (results.size() == 0) return; if (results.isEmpty()) return;
for (UserRoleMappingEntity entity : results) { for (UserRoleMappingEntity entity : results) {
em.remove(entity); em.remove(entity);
} }

View file

@ -28,6 +28,7 @@ import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery; import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.io.Serializable; import java.io.Serializable;
import org.keycloak.representations.idm.MembershipType;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -62,6 +63,9 @@ public class UserGroupMembershipEntity {
@Column(name = "GROUP_ID") @Column(name = "GROUP_ID")
protected String groupId; protected String groupId;
@Column(name = "MEMBERSHIP_TYPE")
private String membershipType;
public UserEntity getUser() { public UserEntity getUser() {
return user; return user;
} }
@ -78,6 +82,14 @@ public class UserGroupMembershipEntity {
this.groupId = groupId; this.groupId = groupId;
} }
public MembershipType getMembershipType() {
return MembershipType.valueOf(membershipType);
}
public void setMembershipType(MembershipType membershipType) {
this.membershipType = membershipType.toString();
}
public static class Key implements Serializable { public static class Key implements Serializable {
protected UserEntity user; protected UserEntity user;

View file

@ -36,24 +36,29 @@ import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Root;
import java.util.stream.Collectors;
import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.GroupModel.Type; import org.keycloak.models.GroupModel.Type;
import org.keycloak.models.GroupProvider; import org.keycloak.models.GroupProvider;
import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.MembershipMetadata;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.models.ModelValidationException; import org.keycloak.models.ModelValidationException;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserManager;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider; import org.keycloak.models.UserProvider;
import org.keycloak.models.jpa.entities.GroupAttributeEntity; import org.keycloak.models.jpa.entities.GroupAttributeEntity;
import org.keycloak.models.jpa.entities.GroupEntity; import org.keycloak.models.jpa.entities.GroupEntity;
import org.keycloak.models.jpa.entities.OrganizationEntity; import org.keycloak.models.jpa.entities.OrganizationEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.jpa.entities.UserGroupMembershipEntity;
import org.keycloak.organization.OrganizationProvider; import org.keycloak.organization.OrganizationProvider;
import org.keycloak.representations.idm.MembershipType;
import org.keycloak.utils.StringUtil; import org.keycloak.utils.StringUtil;
public class JpaOrganizationProvider implements OrganizationProvider { public class JpaOrganizationProvider implements OrganizationProvider {
@ -143,8 +148,17 @@ public class JpaOrganizationProvider implements OrganizationProvider {
getAllStream().forEach(this::remove); getAllStream().forEach(this::remove);
} }
@Override
public boolean addManagedMember(OrganizationModel organization, UserModel user) {
return addMember(organization, user, new MembershipMetadata(MembershipType.MANAGED));
}
@Override @Override
public boolean addMember(OrganizationModel organization, UserModel user) { public boolean addMember(OrganizationModel organization, UserModel user) {
return addMember(organization, user, new MembershipMetadata(MembershipType.UNMANAGED));
}
private boolean addMember(OrganizationModel organization, UserModel user, MembershipMetadata metadata) {
throwExceptionIfObjectIsNull(organization, "Organization"); throwExceptionIfObjectIsNull(organization, "Organization");
throwExceptionIfObjectIsNull(user, "User"); throwExceptionIfObjectIsNull(user, "User");
@ -171,7 +185,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
throw new ModelException("User [" + user.getId() + "] is a member of a different organization"); throw new ModelException("User [" + user.getId() + "] is a member of a different organization");
} }
user.joinGroup(group); user.joinGroup(group, metadata);
user.setSingleAttribute(ORGANIZATION_ATTRIBUTE, entity.getId()); user.setSingleAttribute(ORGANIZATION_ATTRIBUTE, entity.getId());
} finally { } finally {
if (current == null) { if (current == null) {
@ -357,20 +371,23 @@ public class JpaOrganizationProvider implements OrganizationProvider {
return false; return false;
} }
List<IdentityProviderModel> brokers = organization.getIdentityProviders().toList(); UserEntity userEntity = em.find(UserEntity.class, member.getId());
if (userEntity == null) {
if (brokers.isEmpty()) {
return false; return false;
} }
RealmModel realm = getRealm(); GroupModel organizationGroup = getOrganizationGroup(organization);
List<FederatedIdentityModel> federatedIdentities = userProvider.getFederatedIdentitiesStream(realm, member) try {
.map(federatedIdentityModel -> realm.getIdentityProviderByAlias(federatedIdentityModel.getIdentityProvider())) UserGroupMembershipEntity membership = em.createNamedQuery("userMemberOf", UserGroupMembershipEntity.class)
.filter(brokers::contains) .setParameter("user", userEntity)
.map(m -> userProvider.getFederatedIdentity(realm, member, m.getAlias())) .setParameter("groupId", organizationGroup.getId())
.toList(); .getSingleResult();
em.detach(membership);
return !federatedIdentities.isEmpty(); return MembershipType.MANAGED.equals(membership.getMembershipType());
} catch (NoResultException e) {
return false;
}
} }
@Override @Override
@ -385,7 +402,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
} }
if (isManagedMember(organization, member)) { if (isManagedMember(organization, member)) {
userProvider.removeUser(getRealm(), member); return new UserManager(session).removeUser(getRealm(), member, userProvider);
} else { } else {
OrganizationModel current = (OrganizationModel) session.getAttribute(OrganizationModel.class.getName()); OrganizationModel current = (OrganizationModel) session.getAttribute(OrganizationModel.class.getName());

View file

@ -45,4 +45,14 @@
</createIndex> </createIndex>
</changeSet> </changeSet>
<changeSet author="keycloak" id="26.0.0-org-group-membership">
<addColumn tableName="USER_GROUP_MEMBERSHIP">
<column name="MEMBERSHIP_TYPE" type="VARCHAR(255)"/>
</addColumn>
<update tableName="USER_GROUP_MEMBERSHIP">
<column name="MEMBERSHIP_TYPE" value="UNMANAGED"/>
</update>
<addNotNullConstraint tableName="USER_GROUP_MEMBERSHIP" columnName="MEMBERSHIP_TYPE" columnDataType="VARCHAR(255)"/>
</changeSet>
</databaseChangeLog> </databaseChangeLog>

View file

@ -68,6 +68,8 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.keycloak.representations.idm.MemberRepresentation;
import org.keycloak.representations.idm.MembershipType;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -279,12 +281,14 @@ public class ExportUtils {
return domain; return domain;
}).forEach(org::addDomain); }).forEach(org::addDomain);
orgProvider.getMembersStream(m, null, null, -1, -1) orgProvider.getMembersStream(m, null, null, null, null)
.map(user -> { .forEach(user -> {
UserRepresentation member = new UserRepresentation(); MemberRepresentation member = new MemberRepresentation();
member.setUsername(user.getUsername()); member.setUsername(user.getUsername());
return member; member.setMembershipType(orgProvider.isManagedMember(m, user) ? MembershipType.MANAGED : MembershipType.UNMANAGED);
}).forEach(org::addMember);
org.addMember(member);
});
orgProvider.getIdentityProviders(m) orgProvider.getIdentityProviders(m)
.map(b -> { .map(b -> {

View file

@ -85,7 +85,6 @@ import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.OAuthClientRepresentation; import org.keycloak.representations.idm.OAuthClientRepresentation;
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.PartialImportRepresentation; import org.keycloak.representations.idm.PartialImportRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation;
@ -134,6 +133,8 @@ import static org.keycloak.models.utils.RepresentationToModel.createRoleMappings
import static org.keycloak.models.utils.RepresentationToModel.importGroup; import static org.keycloak.models.utils.RepresentationToModel.importGroup;
import static org.keycloak.models.utils.RepresentationToModel.importRoles; import static org.keycloak.models.utils.RepresentationToModel.importRoles;
import static org.keycloak.models.utils.StripSecretsUtils.stripSecrets; import static org.keycloak.models.utils.StripSecretsUtils.stripSecrets;
import org.keycloak.representations.idm.MemberRepresentation;
import org.keycloak.representations.idm.MembershipType;
/** /**
* This wraps the functionality about export/import for the storage. * This wraps the functionality about export/import for the storage.
@ -1598,11 +1599,15 @@ public class DefaultExportImportManager implements ExportImportManager {
provider.addIdentityProvider(org, idp); provider.addIdentityProvider(org, idp);
} }
for (UserRepresentation member : Optional.ofNullable(orgRep.getMembers()).orElse(Collections.emptyList())) { for (MemberRepresentation member : Optional.ofNullable(orgRep.getMembers()).orElse(Collections.emptyList())) {
UserModel m = session.users().getUserByUsername(newRealm, member.getUsername()); UserModel m = session.users().getUserByUsername(newRealm, member.getUsername());
if (MembershipType.MANAGED.equals(member.getMembershipType())) {
provider.addManagedMember(org, m);
} else {
provider.addMember(org, m); provider.addMember(org, m);
} }
} }
} }
} }
} }
}

View file

@ -8,34 +8,7 @@ public class BruteUser extends UserRepresentation {
Map<String, Object> bruteForceStatus; Map<String, Object> bruteForceStatus;
public BruteUser(UserRepresentation user) { public BruteUser(UserRepresentation user) {
this.id = user.getId(); super(user);
this.origin = user.getOrigin();
this.createdTimestamp = user.getCreatedTimestamp();
this.username = user.getUsername();
this.enabled = user.isEnabled();
this.totp = user.isTotp();
this.emailVerified = user.isEmailVerified();
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.email = user.getEmail();
this.federationLink = user.getFederationLink();
this.serviceAccountClientId = user.getServiceAccountClientId();
this.attributes = user.getAttributes();
this.credentials = user.getCredentials();
this.disableableCredentialTypes = user.getDisableableCredentialTypes();
this.requiredActions = user.getRequiredActions();
this.federatedIdentities = user.getFederatedIdentities();
this.realmRoles = user.getRealmRoles();
this.clientRoles = user.getClientRoles();
this.clientConsents = user.getClientConsents();
this.notBefore = user.getNotBefore();
this.applicationRoles = user.getApplicationRoles();
this.socialLinks = user.getSocialLinks();
this.groups = user.getGroups();
this.setAccess(user.getAccess());
} }
public Map<String, Object> getBruteForceStatus() { public Map<String, Object> getBruteForceStatus() {

View file

@ -0,0 +1,32 @@
/*
* 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;
import org.keycloak.representations.idm.MembershipType;
public class MembershipMetadata {
private final MembershipType membershipType;
public MembershipMetadata(MembershipType membershipType) {
this.membershipType = membershipType;
}
public MembershipType getMembershipType() {
return membershipType;
}
}

View file

@ -195,6 +195,9 @@ public interface UserModel extends RoleMapperModel {
} }
void joinGroup(GroupModel group); void joinGroup(GroupModel group);
default void joinGroup(GroupModel group, MembershipMetadata metadata) {
joinGroup(group);
}
void leaveGroup(GroupModel group); void leaveGroup(GroupModel group);
boolean isMemberOf(GroupModel group); boolean isMemberOf(GroupModel group);

View file

@ -103,7 +103,17 @@ public interface OrganizationProvider extends Provider {
void removeAll(); void removeAll();
/** /**
* Adds the given {@link UserModel} as a member of the given {@link OrganizationModel}. * Adds the given {@link UserModel} as a managed member of the given {@link OrganizationModel}.
*
* @param organization the organization
* @param user the user
* @throws ModelException if the {@link UserModel} is member of different organization
* @return {@code true} if the user was added as a member. Otherwise, returns {@code false}
*/
boolean addManagedMember(OrganizationModel organization, UserModel user);
/**
* Adds the given {@link UserModel} as an unmanaged member of the given {@link OrganizationModel}.
* *
* @param organization the organization * @param organization the organization
* @param user the user * @param user the user

View file

@ -342,7 +342,7 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
KeycloakSession session = context.getSession(); KeycloakSession session = context.getSession();
OrganizationProvider provider = session.getProvider(OrganizationProvider.class); OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
OrganizationModel orgModel = provider.getById(token.getOrgId()); OrganizationModel orgModel = provider.getById(token.getOrgId());
provider.addMember(orgModel, user); provider.addManagedMember(orgModel, user);
context.getEvent().detail(Details.ORG_ID, orgModel.getId()); context.getEvent().detail(Details.ORG_ID, orgModel.getId());
context.getAuthenticationSession().setRedirectUri(token.getRedirectUri()); context.getAuthenticationSession().setRedirectUri(token.getRedirectUri());
} }

View file

@ -48,6 +48,8 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.organization.OrganizationProvider; import org.keycloak.organization.OrganizationProvider;
import org.keycloak.representations.idm.MemberRepresentation;
import org.keycloak.representations.idm.MembershipType;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponse;
@ -131,7 +133,7 @@ public class OrganizationMemberResource {
@NoCache @NoCache
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS) @Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
@Operation( summary = "Returns a paginated list of organization members filtered according to the specified parameters") @Operation( summary = "Returns a paginated list of organization members filtered according to the specified parameters")
public Stream<UserRepresentation> search( public Stream<MemberRepresentation> search(
@Parameter(description = "A String representing either a member's username, e-mail, first name, or last name.") @QueryParam("search") String search, @Parameter(description = "A String representing either a member's username, e-mail, first name, or last name.") @QueryParam("search") String search,
@Parameter(description = "Boolean which defines whether the param 'search' must match exactly or not") @QueryParam("exact") Boolean exact, @Parameter(description = "Boolean which defines whether the param 'search' must match exactly or not") @QueryParam("exact") Boolean exact,
@Parameter(description = "The position of the first result to be processed (pagination offset)") @QueryParam("first") @DefaultValue("0") Integer first, @Parameter(description = "The position of the first result to be processed (pagination offset)") @QueryParam("first") @DefaultValue("0") Integer first,
@ -148,7 +150,7 @@ public class OrganizationMemberResource {
@Operation( summary = "Returns the member of the organization with the specified id", description = "Searches for a" + @Operation( summary = "Returns the member of the organization with the specified id", description = "Searches for a" +
"user with the given id. If one is found, and is currently a member of the organization, returns it. Otherwise," + "user with the given id. If one is found, and is currently a member of the organization, returns it. Otherwise," +
"an error response with status NOT_FOUND is returned") "an error response with status NOT_FOUND is returned")
public UserRepresentation get(@PathParam("id") String id) { public MemberRepresentation get(@PathParam("id") String id) {
if (StringUtil.isBlank(id)) { if (StringUtil.isBlank(id)) {
throw ErrorResponse.error("id cannot be null", Status.BAD_REQUEST); throw ErrorResponse.error("id cannot be null", Status.BAD_REQUEST);
} }
@ -160,8 +162,8 @@ public class OrganizationMemberResource {
@DELETE @DELETE
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS) @Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
@Operation(summary = "Removes the user with the specified id from the organization", description = "Breaks the association " + @Operation(summary = "Removes the user with the specified id from the organization", description = "Breaks the association " +
"between the user and organization. The user itself is not deleted. If no user is found, or if they are not " + "between the user and organization. The user itself is deleted in case the membership is managed, otherwise the user is not deleted. " +
"a member of the organization, an error response is returned") "If no user is found, or if they are not a member of the organization, an error response is returned")
public Response delete(@PathParam("id") String id) { public Response delete(@PathParam("id") String id) {
if (StringUtil.isBlank(id)) { if (StringUtil.isBlank(id)) {
throw ErrorResponse.error("id cannot be null", Status.BAD_REQUEST); throw ErrorResponse.error("id cannot be null", Status.BAD_REQUEST);
@ -211,7 +213,9 @@ public class OrganizationMemberResource {
return member; return member;
} }
private UserRepresentation toRepresentation(UserModel member) { private MemberRepresentation toRepresentation(UserModel member) {
return ModelToRepresentation.toRepresentation(session, realm, member); MemberRepresentation result = new MemberRepresentation(ModelToRepresentation.toRepresentation(session, realm, member));
result.setMembershipType(provider.isManagedMember(organization, member) ? MembershipType.MANAGED : MembershipType.UNMANAGED);
return result;
} }
} }

View file

@ -58,7 +58,7 @@ public class IdpAddOrganizationMemberAuthenticator extends AbstractIdpAuthentica
return; return;
} }
provider.addMember(organization, user); provider.addManagedMember(organization, user);
context.success(); context.success();
} }

View file

@ -180,7 +180,7 @@ public class RealmAttributeUpdater extends ServerResourceUpdater<RealmAttributeU
return this; return this;
} }
public RealmAttributeUpdater setOrganizationEnabled(Boolean organizationsEnabled) { public RealmAttributeUpdater setOrganizationsEnabled(Boolean organizationsEnabled) {
rep.setOrganizationsEnabled(organizationsEnabled); rep.setOrganizationsEnabled(organizationsEnabled);
return this; return this;
} }

View file

@ -29,6 +29,7 @@ import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.core.Response.Status;
import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.keycloak.admin.client.resource.OrganizationResource; import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
@ -37,6 +38,7 @@ import org.keycloak.models.OrganizationModel.IdentityProviderRedirectMode;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.MemberRepresentation;
import org.keycloak.representations.idm.OrganizationDomainRepresentation; import org.keycloak.representations.idm.OrganizationDomainRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
@ -79,6 +81,11 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
protected BrokerConfiguration bc = brokerConfigFunction.apply(organizationName); protected BrokerConfiguration bc = brokerConfigFunction.apply(organizationName);
@Override
protected TestCleanup getCleanup() {
return getCleanup(TEST_REALM_NAME);
}
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
testRealm.getClients().addAll(bc.createConsumerClients()); testRealm.getClients().addAll(bc.createConsumerClients());
@ -144,15 +151,15 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
return org; return org;
} }
protected UserRepresentation addMember(OrganizationResource organization) { protected MemberRepresentation addMember(OrganizationResource organization) {
return addMember(organization, memberEmail); return addMember(organization, memberEmail);
} }
protected UserRepresentation addMember(OrganizationResource organization, String email) { protected MemberRepresentation addMember(OrganizationResource organization, String email) {
return addMember(organization, email, null, null); return addMember(organization, email, null, null);
} }
protected UserRepresentation addMember(OrganizationResource organization, String email, String firstName, String lastName) { protected MemberRepresentation addMember(OrganizationResource organization, String email, String firstName, String lastName) {
UserRepresentation expected = new UserRepresentation(); UserRepresentation expected = new UserRepresentation();
expected.setEmail(email); expected.setEmail(email);
@ -172,7 +179,7 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
try (Response response = organization.members().addMember(userId)) { try (Response response = organization.members().addMember(userId)) {
assertEquals(Status.CREATED.getStatusCode(), response.getStatus()); assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
UserRepresentation actual = organization.members().member(userId).toRepresentation(); MemberRepresentation actual = organization.members().member(userId).toRepresentation();
assertNotNull(expected); assertNotNull(expected);
assertEquals(userId, actual.getId()); assertEquals(userId, actual.getId());
@ -210,6 +217,12 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
appPage.assertCurrent(); appPage.assertCurrent();
assertThat(appPage.getRequestType(), is(AppPage.RequestType.AUTH_RESPONSE)); assertThat(appPage.getRequestType(), is(AppPage.RequestType.AUTH_RESPONSE));
} }
List<UserRepresentation> users = realmsResouce().realm(bc.consumerRealmName()).users().search(username, Boolean.TRUE);
if (!users.isEmpty()) {
assertThat(users, Matchers.hasSize(1));
getCleanup(bc.consumerRealmName()).addUserId(users.get(0).getId());
}
} }
protected void assertIsMember(String userEmail, OrganizationResource organization) { protected void assertIsMember(String userEmail, OrganizationResource organization) {
@ -276,11 +289,11 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
// user automatically redirected to the organization identity provider // user automatically redirected to the organization identity provider
if (autoIDPRedirect) { if (autoIDPRedirect) {
Assert.assertTrue("Driver should be on the provider realm page right now", assertThat("Driver should be on the provider realm page right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/")); driver.getCurrentUrl(), Matchers.containsString("/auth/realms/" + bc.providerRealmName() + "/"));
} else { } else {
Assert.assertTrue("Driver should be on the consumer realm page right now", assertThat("Driver should be on the consumer realm page right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/")); driver.getCurrentUrl(), Matchers.containsString("/auth/realms/" + bc.consumerRealmName() + "/"));
} }
} }
} }

View file

@ -34,6 +34,7 @@ import jakarta.mail.internet.MimeMessage;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.core.Response.Status;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import static org.hamcrest.Matchers.equalTo;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -42,6 +43,8 @@ import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.cookie.CookieType; import org.keycloak.cookie.CookieType;
import org.keycloak.representations.idm.ErrorRepresentation; import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.MemberRepresentation;
import org.keycloak.representations.idm.MembershipType;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
@ -113,7 +116,9 @@ public class OrganizationInvitationLinkTest extends AbstractOrganizationTest {
List<UserRepresentation> users = testRealm().users().searchByEmail(email, true); List<UserRepresentation> users = testRealm().users().searchByEmail(email, true);
assertThat(users, Matchers.not(empty())); assertThat(users, Matchers.not(empty()));
// user is a member // user is a member
Assert.assertNotNull(organization.members().member(users.get(0).getId()).toRepresentation()); MemberRepresentation member = organization.members().member(users.get(0).getId()).toRepresentation();
Assert.assertNotNull(member);
assertThat(member.getMembershipType(), equalTo(MembershipType.MANAGED));
getCleanup().addCleanup(() -> testRealm().users().get(users.get(0).getId()).remove()); getCleanup().addCleanup(() -> testRealm().users().get(users.get(0).getId()).remove());
// authenticated to the account console // authenticated to the account console

View file

@ -406,7 +406,7 @@ public class OrganizationTest extends AbstractOrganizationTest {
OrganizationRepresentation existing = createOrganization("acme", "acme.org", "acme.net"); OrganizationRepresentation existing = createOrganization("acme", "acme.org", "acme.net");
// disable the organization provider and try to access REST endpoints // disable the organization provider and try to access REST endpoints
try (RealmAttributeUpdater rau = new RealmAttributeUpdater(testRealm()) try (RealmAttributeUpdater rau = new RealmAttributeUpdater(testRealm())
.setOrganizationEnabled(Boolean.FALSE) .setOrganizationsEnabled(Boolean.FALSE)
.update()) { .update()) {
OrganizationRepresentation org = createRepresentation("some", "some.com"); OrganizationRepresentation org = createRepresentation("some", "some.com");

View file

@ -27,6 +27,8 @@ import java.util.List;
import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.OrganizationIdentityProviderResource; import org.keycloak.admin.client.resource.OrganizationIdentityProviderResource;
@ -45,6 +47,7 @@ import org.keycloak.representations.idm.OrganizationDomainRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest; import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
@ -212,14 +215,7 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
@Test @Test
public void testLinkExistingAccount() { public void testLinkExistingAccount() {
// create a realm user in the consumer realm createUserInConsumerRealm();
realmsResouce().realm(bc.consumerRealmName()).users()
.create(UserBuilder.create()
.username(bc.getUserLogin())
.email(bc.getUserEmail())
.password(bc.getUserPassword())
.enabled(true).build()
).close();
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId()); OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
OrganizationIdentityProviderResource broker = organization.identityProviders().get(bc.getIDPAlias()); OrganizationIdentityProviderResource broker = organization.identityProviders().get(bc.getIDPAlias());
@ -242,14 +238,7 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
@Test @Test
public void testExistingUserUsingOrgDomain() { public void testExistingUserUsingOrgDomain() {
// create a realm user in the consumer realm createUserInConsumerRealm();
realmsResouce().realm(bc.consumerRealmName()).users()
.create(UserBuilder.create()
.username(bc.getUserLogin())
.email(bc.getUserEmail())
.password(bc.getUserPassword())
.enabled(true).build()
).close();
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId()); OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
OrganizationIdentityProviderResource broker = organization.identityProviders().get(bc.getIDPAlias()); OrganizationIdentityProviderResource broker = organization.identityProviders().get(bc.getIDPAlias());
@ -425,6 +414,7 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
List<FederatedIdentityRepresentation> federatedIdentities = testRealm().users().get(user.getId()).getFederatedIdentity(); List<FederatedIdentityRepresentation> federatedIdentities = testRealm().users().get(user.getId()).getFederatedIdentity();
assertEquals(1, federatedIdentities.size()); assertEquals(1, federatedIdentities.size());
assertEquals(bc.getIDPAlias(), federatedIdentities.get(0).getIdentityProvider()); assertEquals(bc.getIDPAlias(), federatedIdentities.get(0).getIdentityProvider());
} }
@Test @Test
@ -689,4 +679,18 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
} catch (NotFoundException ignore) { } catch (NotFoundException ignore) {
} }
} }
private void createUserInConsumerRealm() {
// create a realm user in the consumer realm
try (Response response = realmsResouce().realm(bc.consumerRealmName()).users()
.create(UserBuilder.create()
.username(bc.getUserLogin())
.email(bc.getUserEmail())
.password(bc.getUserPassword())
.enabled(true).build())) {
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
String id = ApiUtil.getCreatedId(response);
getCleanup(bc.consumerRealmName()).addUserId(id);
}
}
} }

View file

@ -86,7 +86,7 @@ public class OrganizationMemberAuthenticationTest extends AbstractOrganizationTe
// disable the organization provider // disable the organization provider
try (RealmAttributeUpdater rau = new RealmAttributeUpdater(testRealm()) try (RealmAttributeUpdater rau = new RealmAttributeUpdater(testRealm())
.setOrganizationEnabled(Boolean.FALSE) .setOrganizationsEnabled(Boolean.FALSE)
.update()) { .update()) {
// access the page again, now it should be present username and password fields // access the page again, now it should be present username and password fields

View file

@ -53,6 +53,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.organization.OrganizationProvider; import org.keycloak.organization.OrganizationProvider;
import org.keycloak.representations.idm.ErrorRepresentation; import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.MemberRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig; import org.keycloak.representations.userprofile.config.UPConfig;
@ -68,7 +69,7 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
@Test @Test
public void testUpdate() { public void testUpdate() {
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId()); OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
UserRepresentation expected = addMember(organization); UserRepresentation expected = getUserRepFromMemberRep(addMember(organization));
expected.setFirstName("f"); expected.setFirstName("f");
expected.setLastName("l"); expected.setLastName("l");
@ -90,7 +91,7 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
upConfig.setUnmanagedAttributePolicy(UnmanagedAttributePolicy.ENABLED); upConfig.setUnmanagedAttributePolicy(UnmanagedAttributePolicy.ENABLED);
testRealm().users().userProfile().update(upConfig); testRealm().users().userProfile().update(upConfig);
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId()); OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
UserRepresentation expected = addMember(organization); UserRepresentation expected = getUserRepFromMemberRep(addMember(organization));
expected.singleAttribute(ORGANIZATION_ATTRIBUTE, "invalid"); expected.singleAttribute(ORGANIZATION_ATTRIBUTE, "invalid");
@ -161,7 +162,7 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
expected.add(addMember(organization, "member-" + i + "@neworg.org")); expected.add(addMember(organization, "member-" + i + "@neworg.org"));
} }
List<UserRepresentation> existing = organization.members().getAll(); List<MemberRepresentation> existing = organization.members().getAll();
assertFalse(existing.isEmpty()); assertFalse(existing.isEmpty());
assertEquals(expected.size(), existing.size()); assertEquals(expected.size(), existing.size());
for (UserRepresentation expectedRep : expected) { for (UserRepresentation expectedRep : expected) {
@ -200,7 +201,7 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
assertThat(existingOrg.isEnabled(), is(false)); assertThat(existingOrg.isEnabled(), is(false));
// now fetch all users from the org - unmanaged users should still be enabled, but managed ones should not. // now fetch all users from the org - unmanaged users should still be enabled, but managed ones should not.
List<UserRepresentation> existing = organization.members().getAll(); List<MemberRepresentation> existing = organization.members().getAll();
assertThat(existing, not(empty())); assertThat(existing, not(empty()));
assertThat(existing, hasSize(6)); assertThat(existing, hasSize(6));
for (UserRepresentation user : existing) { for (UserRepresentation user : existing) {
@ -213,10 +214,10 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
// fetching users from the users endpoint should have the same result. // fetching users from the users endpoint should have the same result.
UserRepresentation disabledUser = null; UserRepresentation disabledUser = null;
existing = testRealm().users().search("*neworg*",0, 10); List<UserRepresentation> existingUsers = testRealm().users().search("*neworg*",0, 10);
assertThat(existing, not(empty())); assertThat(existingUsers, not(empty()));
assertThat(existing, hasSize(6)); assertThat(existingUsers, hasSize(6));
for (UserRepresentation user : existing) { for (UserRepresentation user : existingUsers) {
if (user.getEmail().equals(bc.getUserEmail())) { if (user.getEmail().equals(bc.getUserEmail())) {
assertThat(user.isEnabled(), is(false)); assertThat(user.isEnabled(), is(false));
disabledUser = user; disabledUser = user;
@ -254,7 +255,7 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
// disable the organization provider // disable the organization provider
try (RealmAttributeUpdater rau = new RealmAttributeUpdater(testRealm()) try (RealmAttributeUpdater rau = new RealmAttributeUpdater(testRealm())
.setOrganizationEnabled(Boolean.FALSE) .setOrganizationsEnabled(Boolean.FALSE)
.update()) { .update()) {
// now fetch all members from the realm - unmanaged users should still be enabled, but managed ones should not. // now fetch all members from the realm - unmanaged users should still be enabled, but managed ones should not.
@ -305,7 +306,7 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
@Test @Test
public void testUpdateEmailUnmanagedMember() { public void testUpdateEmailUnmanagedMember() {
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId()); OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
UserRepresentation expected = addMember(organization); UserRepresentation expected = getUserRepFromMemberRep(addMember(organization));
expected.setEmail("some@unknown.org"); expected.setEmail("some@unknown.org");
UserResource userResource = testRealm().users().get(expected.getId()); UserResource userResource = testRealm().users().get(expected.getId());
userResource.update(expected); userResource.update(expected);
@ -315,10 +316,14 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
} }
private UserRepresentation getUserRepFromMemberRep(MemberRepresentation member) {
return new UserRepresentation(member);
}
@Test @Test
public void testDeleteMembersOnOrganizationRemoval() { public void testDeleteMembersOnOrganizationRemoval() {
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId()); OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
List<UserRepresentation> expected = new ArrayList<>(); List<MemberRepresentation> expected = new ArrayList<>();
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
expected.add(addMember(organization, "member-" + i + "@neworg.org")); expected.add(addMember(organization, "member-" + i + "@neworg.org"));
@ -326,19 +331,19 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
organization.delete().close(); organization.delete().close();
for (UserRepresentation member : expected) { for (MemberRepresentation member : expected) {
try { try {
organization.members().member(member.getId()).toRepresentation(); organization.members().member(member.getId()).toRepresentation();
fail("should be deleted"); fail("should be deleted");
} catch (NotFoundException ignore) {} } catch (NotFoundException ignore) {}
} }
for (UserRepresentation member : expected) { for (MemberRepresentation member : expected) {
// users should exist as they are not managed by the organization // users should exist as they are not managed by the organization
testRealm().users().get(member.getId()).toRepresentation(); testRealm().users().get(member.getId()).toRepresentation();
} }
for (UserRepresentation member : expected) { for (MemberRepresentation member : expected) {
try { try {
// user no longer bound to the organization // user no longer bound to the organization
organization.members().getOrganization(member.getId()); organization.members().getOrganization(member.getId());
@ -361,7 +366,7 @@ public class OrganizationMemberTest extends AbstractOrganizationTest {
expected.add(addMember(organization, "thejoker@neworg.org", "Jack", "White")); expected.add(addMember(organization, "thejoker@neworg.org", "Jack", "White"));
// exact search - username/e-mail, first name, last name. // exact search - username/e-mail, first name, last name.
List<UserRepresentation> existing = organization.members().search("brucewayne@neworg.org", true, null, null); List<MemberRepresentation> existing = organization.members().search("brucewayne@neworg.org", true, null, null);
assertThat(existing, hasSize(1)); assertThat(existing, hasSize(1));
assertThat(existing.get(0).getUsername(), is(equalTo("brucewayne@neworg.org"))); assertThat(existing.get(0).getUsername(), is(equalTo("brucewayne@neworg.org")));
assertThat(existing.get(0).getEmail(), is(equalTo("brucewayne@neworg.org"))); assertThat(existing.get(0).getEmail(), is(equalTo("brucewayne@neworg.org")));