Skip to content

Commit

Permalink
Throw Optimistic locking exception for concurrent updating roles in g…
Browse files Browse the repository at this point in the history
…roup member
  • Loading branch information
cgeorgilakis-grnet committed Feb 24, 2025
1 parent b425b1f commit 69e3350
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 23 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes in keycloak-group-management will be documented in this file
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.5.4] - 2025-02-24

### Fixed
- Throw Optimistic locking exception for concurrent updating roles in group member

## [1.5.3] - 2025-02-24

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<quarkus.version>3.2.7.Final</quarkus.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<agm-version>1.5.3</agm-version>
<agm-version>1.5.4</agm-version>
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,11 @@
import java.time.LocalDate;
import java.util.Set;

import jakarta.persistence.*;
import org.keycloak.models.jpa.entities.GroupEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.rciam.plugins.groups.enums.MemberStatusEnum;

import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;

@Entity
@Table(name = "USER_GROUP_MEMBERSHIP_EXTENSION")
@NamedQueries({
Expand Down Expand Up @@ -83,6 +68,9 @@ public class UserGroupMembershipExtensionEntity {
@Column(name = "GROUP_ENROLLMENT_CONFIGURATION_ID")
private String groupEnrollmentConfigurationId;

@Version
private Integer version;

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "GROUP_MEMBERSHIP_ROLES", joinColumns = @JoinColumn(name = "USER_GROUP_MEMBERSHIP_EXTENSION_ID"), inverseJoinColumns = @JoinColumn(name = "GROUP_ROLES_ID"))
private Set<GroupRolesEntity> groupRoles;
Expand Down Expand Up @@ -182,4 +170,12 @@ public Set<GroupRolesEntity> getGroupRoles() {
public void setGroupRoles(Set<GroupRolesEntity> groupRoles) {
this.groupRoles = groupRoles;
}

public Integer getVersion() {
return version;
}

public void setVersion(Integer version) {
this.version = version;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ public void createOrUpdate(GroupEnrollmentRequestRepresentation rep, KeycloakSes
}

@Transactional
public void update(UserGroupMembershipExtensionRepresentation rep, UserGroupMembershipExtensionEntity entity, GroupModel group, KeycloakSession session, UserModel groupAdmin, ClientConnection clientConnection) throws UnsupportedEncodingException {
public void update(UserGroupMembershipExtensionRepresentation rep, UserGroupMembershipExtensionEntity entity, GroupModel group, KeycloakSession session, UserModel groupAdmin, ClientConnection clientConnection) {
boolean isNotMember = !MemberStatusEnum.ENABLED.equals(entity.getStatus());
boolean isMembershipExpiresAtChanges = !(( rep.getMembershipExpiresAt() == null && entity.getMembershipExpiresAt() == null) || (rep.getMembershipExpiresAt() != null && rep.getMembershipExpiresAt().equals(entity.getMembershipExpiresAt())));
entity.setValidFrom(rep.getValidFrom());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.persistence.OptimisticLockException;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
Expand Down Expand Up @@ -79,7 +80,7 @@ public GroupAdminGroupMember(KeycloakSession session, RealmModel realm, UserMode

@PUT
@Consumes("application/json")
public Response updateMember(UserGroupMembershipExtensionRepresentation rep) throws UnsupportedEncodingException {
public Response updateMember(UserGroupMembershipExtensionRepresentation rep) {
//validation tasks
//1. active member
//2. at least one role
Expand Down Expand Up @@ -109,10 +110,12 @@ public Response updateMember(UserGroupMembershipExtensionRepresentation rep) thr
throw new BadRequestException("Membership can not last more than "+ configurationRule.getMax() + " days");
}

userGroupMembershipExtensionRepository.update(rep, member, group, session, groupAdmin, clientConnection);
String groupPath = ModelToRepresentation.buildGroupPath(group);

try {

userGroupMembershipExtensionRepository.update(rep, member, group, session, groupAdmin, clientConnection);
String groupPath = ModelToRepresentation.buildGroupPath(group);

UserModel memberUser = session.users().getUserById(realm, member.getUser().getId());
customFreeMarkerEmailTemplateProvider.setUser(memberUser);
customFreeMarkerEmailTemplateProvider.sendMemberUpdateUserInformEmail(groupPath, groupAdmin, rep.getValidFrom(), rep.getMembershipExpiresAt(), rep.getGroupRoles());
Expand All @@ -127,6 +130,9 @@ public Response updateMember(UserGroupMembershipExtensionRepresentation rep) thr
});
} catch (EmailException e) {
ServicesLogger.LOGGER.failedToSendEmail(e);
} catch (OptimisticLockException e) {
e.printStackTrace();
return Response.status(Response.Status.CONFLICT).entity(String.format("Concurrent modification detected: conflicting group membership update for user %s in group %s.", member.getUser().getUsername(), group.getName())).build();
}
return Response.noContent().build();
}
Expand Down Expand Up @@ -160,7 +166,7 @@ public Response deleteMember() {

@POST
@Path("/role")
public Response addGroupRole(@QueryParam("name") String name) throws UnsupportedEncodingException {
public Response addGroupRole(@QueryParam("name") String name) {
if (!isGroupAdmin){
throw new ForbiddenException();
}
Expand Down Expand Up @@ -205,7 +211,7 @@ public Response addGroupRole(@QueryParam("name") String name) throws Unsupported

@DELETE
@Path("/role/{name}")
public Response deleteGroupRole(@PathParam("name") String name) throws UnsupportedEncodingException {
public Response deleteGroupRole(@PathParam("name") String name) {
if (!isGroupAdmin){
throw new ForbiddenException();
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/resources/META-INF/group-management-changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -855,4 +855,12 @@
/>
</changeSet>

<changeSet author="[email protected]" id="version_member">
<addColumn tableName="USER_GROUP_MEMBERSHIP_EXTENSION">
<column name="VERSION" type="INT" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>

</databaseChangeLog>

0 comments on commit 69e3350

Please sign in to comment.