Skip to content

Commit

Permalink
Group admin can create a user group member based on username (REST AP…
Browse files Browse the repository at this point in the history
…I) and Recalculate effective membership expires at when pending groups become active
  • Loading branch information
cgeorgilakis-grnet committed Dec 17, 2024
1 parent bc06efb commit 043944c
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 27 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ 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.1.0]

### Added
- Group admin can create a user group member based on username (REST API)

### Fixed
- Recalculate effective membership expires at when pending groups become active

## [1.0.4] - 2024-12-02

### Fixed
Expand Down
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ A keycloak plugin to support advanced group management features:
| 0.13.0 | 22.0.5-1.2 + |
| 0.18.0 | 22.0.10-1.4 + |
| 0.19.0 | 22.0.10-1.8 + |
| 0.20.0 | 22.0.10-1.8 + |
| 0.21.0 | 22.0.11-1.8 + |




## General configuration options
Expand All @@ -34,7 +30,21 @@ All web services to be executed needs realm management rights role.
- 'keycloakUrl' = Keycloak main url
- 'userAssuranceForEnrollment' = User assurance (default 'assurance')
- 'userIdentifierForEnrollment' = User identifier (default 'username')
2. (optional) For general group management configuration options execute following web service (necessary during first time deployed):
2. You could create account roles 'manage-groups' and 'manage-groups-extended' for managing groups.
'manage-groups' is a special role that can manage all groups in a same way as group admins.
Actions that can not be done with this role are:
- delete group
- delete role
- invite a user
- add group member role
- delete group member role
Among others users with this role can also:
- create top level group
- create group configuration
- can view all realm users
'manage-groups-extended' is a role for comanage to Keycloak migration.
User with this role has the same rigths plus some rules in creating a group member does not take into account with this role.
3. (optional) For general group management configuration options execute following web service (necessary during first time deployed):

`curl --request PUT \
--url {server_url}/realms/{realmName}/agm/admin/configuration \
Expand All @@ -50,7 +60,7 @@ Parameter explanation:
- invitation-expiration-period = After how many hours the invitation will be expired. (default value is 72)
- expiration-notification-period = How many days before Group Membership expiration (or aup expiration) notification email will be sent to user. Can be overridden per Group. (default value is 21)

3. For configuring entitlements user attribute you must execute the following web service :
4. For configuring entitlements user attribute you must execute the following web service :
`curl --request POST \
--url {server_url}/realms/{realmName}/agm/admin/member-user-attribute/configuration \
--header 'Accept: application/json' \
Expand All @@ -64,7 +74,7 @@ Parameter explanation:

Only authority is optional.

4. Configuration rules exists for group configuration options. Web service example:
5. Configuration rules exists for group configuration options. Web service example:
`curl --request POST \
--url {server_url}/realms/{realmName}/agm/admin/configuration-rules \
--header 'Accept: application/json' \
Expand Down Expand Up @@ -148,6 +158,7 @@ Main url : {server_url}/realms/{realm}/agm
| /account/group-admin/group/{groupId}/roles | POST | create group role | GroupAdminGroup |
| /account/group-admin/group/{groupId}/role/{name} | DELETE | delete group role | GroupAdminGroup |
| /account/group-admin/group/{groupId}/members | GET | get all group members pager, being able to search and get by type (fe active) | GroupAdminGroupMembers |
| /account/group-admin/group/{groupId}/members | POST | create a user group member based on username | GroupAdminGroupMembers |
| /account/group-admin/group/{groupId}/members/invitation | POST | send invitation to a user based on email | GroupAdminGroupMembers |
| /account/group-admin/group/{groupId}/member/{memberId} | PUT | update specific fields of group member | GroupAdminGroupMembers |
| /account/group-admin/group/{groupId}/member/{memberId} | DELETE | delete group member | GroupAdminGroupMember |
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.0.4</agm-version>
<agm-version>1.1.0</agm-version>
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class CustomFreeMarkerEmailTemplateProvider extends FreeMarkerEmailTempla

//must be changed to a ui ( account console url)
private static final String enrollmentUrl = "realms/{realmName}/account/#/groups/groupenrollments?id={id}";
private static final String MEMBER_URL = "realms/{realmName}/account/#/groups/showgroups/{id}";
private static final String enrollmentStartUrl = "/realms/{realmName}/account/#/enroll?groupPath={path}";
private static final String finishGroupInvitation = "realms/{realmName}/account/#/invitation/{id}";
private static final String adminGroupPageUrl = "realms/{realmName}/account/#/groups/admingroups/{id}?tab=admins";
Expand Down Expand Up @@ -196,7 +197,7 @@ public void sendExpiredGroupMemberEmailToAdmin(UserModel userRequest, String gro
send("adminGroupUserRemovalSubject", Stream.of(groupPath).collect(Collectors.toList()), "expired-group-membership-admin.ftl", attributes);
}

public void sendExpiredGroupMemberEmailToUser(String groupPath, String groupId, List<String> subgroupsPaths, String serverUrl) throws EmailException {
public void sendExpiredGroupMemberEmailToUser(String groupPath, List<String> subgroupsPaths, String serverUrl) throws EmailException {
attributes.put("fullname", user.getFirstName() + " " + user.getLastName());
attributes.put("groupPath", groupPath);
attributes.put("subgroupsStr", subgroupsStrCalculation(subgroupsPaths));
Expand All @@ -205,7 +206,7 @@ public void sendExpiredGroupMemberEmailToUser(String groupPath, String groupId,
send("userRemovalSubject", Stream.of(groupPath).collect(Collectors.toList()), "expired-group-membership-user.ftl", attributes);
}

public void sendExpiredGroupMembershipNotification(String groupPath, String date, String groupId, String serverUrl) throws EmailException {
public void sendExpiredGroupMembershipNotification(String groupPath, String date, String serverUrl) throws EmailException {
attributes.put("fullname", user.getFirstName() + " " + user.getLastName());
attributes.put("groupPath", groupPath);
attributes.put("date", date);
Expand Down Expand Up @@ -346,25 +347,26 @@ public void sendMemberUpdateUserInformEmail(String groupPath, UserModel admin, L
send("memberUpdateUserInformSubject", Stream.of(groupPath).collect(Collectors.toList()), "member-update-user-inform.ftl", attributes);
}

public void sendMemberCreateAdminInformEmail(String groupPath, UserModel userChanged, UserModel admin, LocalDate validFrom, LocalDate membershipExpiresAt, List<String> roles) throws EmailException {
public void sendMemberCreateAdminInformEmail(String groupId, String groupPath, UserModel userChanged, UserModel admin) throws EmailException {
attributes.put("fullname", user.getFirstName() + " " + user.getLastName());
attributes.put("groupPath", groupPath);
attributes.put("userFullName", userChanged.getFirstName() + " " + userChanged.getLastName());
attributes.put("adminFullName", admin.getFirstName() + " " + admin.getLastName());
attributes.put("validFrom", validFrom.format(Utils.dateFormatter));
attributes.put("membershipExpiresAt", membershipExpiresAt != null ? membershipExpiresAt.format(Utils.dateFormatter) : "N/A");
attributes.put("roles", roles.stream().collect(Collectors.joining(",")));
String groupUrl = session.getContext().getUri().getBaseUri().toString() + membersGroupPageUrl ;
attributes.put("groupUrl", groupUrl.replace("{realmName}", realm.getName()).replace("{id}", groupId));
attributes.put("signatureMessage", signatureMessage);
send("memberCreateAdminInformSubject", Stream.of(groupPath).collect(Collectors.toList()),"member-create-admin-inform.ftl", attributes);
}

public void sendMemberCreateUserInformEmail(String groupPath, UserModel admin, LocalDate validFrom, LocalDate membershipExpiresAt, List<String> roles) throws EmailException {
public void sendMemberCreateUserInformEmail(String groupId, String groupPath, UserModel admin, LocalDate validFrom, LocalDate membershipExpiresAt, List<String> roles) throws EmailException {
attributes.put("fullname", user.getFirstName() + " " + user.getLastName());
attributes.put("groupPath", groupPath);
attributes.put("adminFullName", admin.getFirstName() + " " + admin.getLastName());
attributes.put("validFrom", validFrom.format(Utils.dateFormatter));
attributes.put("membershipExpiresAt", membershipExpiresAt != null ? membershipExpiresAt.format(Utils.dateFormatter) : "N/A");
attributes.put("roles", roles.stream().collect(Collectors.joining(",")));
String memberUrl = session.getContext().getUri().getBaseUri().toString() + MEMBER_URL ;
attributes.put("memberUrl", memberUrl.replace("{realmName}", realm.getName()).replace("{id}", groupId));
attributes.put("signatureMessage", signatureMessage);
send("memberCreateUserInformSubject", Stream.of(groupPath).collect(Collectors.toList()), "member-create-user-inform.ftl", attributes);
}
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/org/rciam/plugins/groups/helpers/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public class Utils {
public static final String GROUP_MEMBERSHIP_SUSPEND = "GROUP_MEMBERSHIP_SUSPEND";
public static final String NO_FOUND_GROUP_CONFIGURATION = "Could not find this group configuration";
public static final String DEFAULT_GROUP_ROLE_NAME = "manage-groups";
public static final String DEFAULT_GROUP_ROLE_NAME_EXTENDED = "manage-groups-extended";
public static final String DESCRIPTION = "description";
public static final String USER_ASSURANCE_FOR_ENROLLMENT = "userAssuranceForEnrollment";
public static final String DEFAULT_USER_ASSURANCE_FOR_ENROLLMENT = "assurance";
Expand Down Expand Up @@ -227,7 +228,12 @@ public static Set<GroupModel> getAllSubgroups(GroupModel group){

public static boolean hasManageGroupsAccountRole(RealmModel realm, UserModel user) {
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
return client!= null && user.hasRole(client.getRole(DEFAULT_GROUP_ROLE_NAME));
return client!= null && (user.hasRole(client.getRole(DEFAULT_GROUP_ROLE_NAME)) || user.hasRole(client.getRole(DEFAULT_GROUP_ROLE_NAME_EXTENDED)));
}

public static boolean hasManageExtendedGroupsAccountRole(RealmModel realm, UserModel user) {
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
return client!= null && user.hasRole(client.getRole(DEFAULT_GROUP_ROLE_NAME_EXTENDED));
}

public static FederatedIdentityRepresentation getFederatedIdentityRep(RealmModel realm, String idPAlias) {
Expand Down
Loading

0 comments on commit 043944c

Please sign in to comment.