Skip to content

Commit 657fbf7

Browse files
Implement Group Types (geonetwork#8741)
* Add groupType property to Group * Prevent importing or creating records on non-workspace groups * Prevent changing groupType from workspace to non-workspace for groups that own records and prevent changing a non system group to a system group if it already has privileges assigned * Fix restricted groups can have permissions set in batch privileges ui * Prevent changing the owner group to a non-workspace group in the editor * Update TransferApi to prevent transferring ownership to a non-workspace group * Improve exception messages * Fix users in group label is always shown * Prevent setting profiles other than registered user for system groups * Prevent setting privileges for system groups * Disable minimumProfileForPrivileges when group type is system * Add error modals for transfer ownership * Add SQL statements for new column in groups table * Update manual * Update documentation * Fix minimumProfileForPrivileges is not reset --------- Co-authored-by: Jody Garnett <[email protected]>
1 parent d37aebd commit 657fbf7

File tree

27 files changed

+552
-144
lines changed

27 files changed

+552
-144
lines changed

docs/manual/docs/administrator-guide/managing-users-and-groups/creating-group.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,50 @@ Additional settings:
3030
- **Editing**: Specifies which groups can edit the metadata record.
3131
- **Notify**: Determines which groups are notified when a file managed by GeoNetwork is downloaded.
3232

33+
## Group Type
34+
35+
Administrators can define the type of group being created. There are three group types available, each with specific characteristics and use cases:
36+
37+
### 1. Workspace Group
38+
39+
- **Description**: This is the default group type. It allows the group to own metadata records and have privileges assigned to them.
40+
- **Use Case**: Suitable for groups that need full control over metadata records, including creation, import, and transfer.
41+
42+
### 2. Record Privilege Group
43+
44+
- **Description**: This group type can have privileges assigned to specific metadata records but cannot own metadata records.
45+
- **Use Case**: Ideal for groups that require access to metadata records without ownership rights.
46+
47+
### 3. System Privilege Group
48+
49+
- **Description**: This group type is used to manage system-level privileges. It cannot own metadata records or have privileges assigned to them.
50+
- **Use Case**: Designed for administrative purposes where system-level access is required without metadata ownership.
51+
- **Profiles**: Users are not assigned profiles in System Privilege Groups, since their purpose is to grant system-level privileges to all members. Instead, users in these groups are automatically given the Registered User profile behind the scenes.
52+
53+
### Group Type and Operations Allowed Matrix
54+
55+
The table below summarizes the operations allowed for each group type:
56+
57+
| **Group Type** | **Can Create Records in Group** | **Can Import Records in Group** | **Can Transfer Records to Group** | **Can Group Have Privileges on a Record** | **Group Has Profiles** |
58+
|-------------------------|:------------------------------:|:-------------------------------:|:---------------------------------:|:-----------------------------------------:|:-----------------------:|
59+
| **Workspace** ||||||
60+
| **Record Privilege** | | | |||
61+
| **System Privilege** | | | | | |
62+
63+
!!! Note
64+
65+
- **Workspace Group**: Provides the most flexibility and is the default choice for most use cases.
66+
- **Record Privilege Group**: Focused on access control without ownership.
67+
- **System Privilege Group**: Reserved for system-level operations and does not interact with metadata records directly.
68+
3369
## Minimum user profile allowed to set privileges
3470

3571
This setting allows administrators to control the minimum user profile required to assign privileges for a group. It provides enhanced control over who can manage sensitive privileges for users within the group.
3672

73+
!!! Note
74+
75+
This setting is unset and disabled when the group is a **System Privilege Group**. System privilege groups do not have profiles and cannot have privileges assigned to them.
76+
3777
### Default setting
3878

3979
By default, the **"Minimum User Profile Allowed to Set Privileges"** is set to **No Restrictions**. This means that any user with permission to manage privileges for a metadata record can assign privileges for users in this group.

docs/manual/docs/user-guide/describing-information/creating-metadata.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This topic guides you through the process of adding new metadata records with as
77
To add or edit metadata, the user:
88

99
- Must have an `editor` profile or higher.
10-
- Should be a member of a group you want to add information for.
10+
- Should be a member of the [Workspace Group](../../administrator-guide/managing-users-and-groups/creating-group.md#1-workspace-group) you want to add information for.
1111

1212
Contact your administrator if you don't have the correct profile.
1313

@@ -25,7 +25,7 @@ Contact your administrator if you don't have the correct profile.
2525

2626
!!! Note
2727

28-
If only one group is defined in the catalog, the default group is selected.
28+
If only one group is defined in the catalog, the default group is selected. If a group is not a [Workspace Group](../../administrator-guide/managing-users-and-groups/creating-group.md#1-workspace-group) it will not be shown as an option in the list.
2929

3030
Next steps:
3131

docs/manual/docs/user-guide/describing-information/importing-metadata.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ The user should have an `editor` profile to access metadata.
3737
- `Apply XSLT conversion` allows to transform the record loaded using an XSLT stylesheet. A list of predefined transformations is provided. The selected transformation should be compatible with the standard of the loaded record (see [Adding XSLT conversion for import](../workflow/batchupdate-xsl.md#customizing-xslt-conversion)).
3838
- `Validate` trigger the validation of the record before loading it. In case of error the record is rejected and an error reported.
3939
- `Assign to current catalog` assign the current catalog as origin for the record, in case the MEF file indicate another source.
40-
- `Assign to Group` define the group of the loaded record.
40+
- `Assign to Group` define the group of the loaded record. Only [Workspace Groups](../../administrator-guide/managing-users-and-groups/creating-group.md#1-workspace-group) can be selected.
4141
- `Assign to Category` define a local category to assign to the loaded record.
4242

4343
3. Click `import` to trigger the import. After processing, a summary is provided with the following details:

docs/manual/docs/user-guide/publishing/managing-privileges.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ To manage privileges to your metadata record and any attached data, you will nee
44

55
For example, you can specify that the metadata and related services are visible to all (Internet users) or just to internal users only (Intranet). Privileges are assigned on a per group basis. Depending on the user profile (Guest, Registered User, Editor, Admin etc.) access to these functions may differ on a per user basis.
66

7+
!!! Note
8+
9+
[System Privilege Groups](../../administrator-guide/managing-users-and-groups/creating-group.md#3-system-privilege-group) are special groups which cannot have privileges set for specific metadata records.
10+
711
## Assigning privileges
812

913
To assign privileges, follow these steps:
@@ -85,6 +89,7 @@ You can set privileges on a selected set of records in the search results using
8589

8690
The following rules apply:
8791

88-
- the groups are those that the user belongs to
92+
- If the "[Only set privileges to user's groups](../../administrator-guide/configuring-the-catalog/system-configuration.md#metadata-privileges)" setting is set, only the groups the user is a member of will be shown in the list.
93+
- [System Privilege Groups](../../administrator-guide/managing-users-and-groups/creating-group.md#3-system-privilege-group) are not shown
8994
- the privileges specified will only be applied to records that the user has ownership or administration rights on - any other records will be skipped
9095
- the current records privileges will be reset and replaced by the selected privilege

docs/manual/docs/user-guide/publishing/transferring-privileges.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ How to open the Transfer Ownership page
2121
The drop down is filled with all Editors visible to you. If you are not an Administrator, you will view only a subset of all Editors.
2222

2323
- **Select group**: Select one of the groups this user is a member of. Privileges to groups All and Intranet are not transferable.
24+
25+
!!! Note
26+
27+
Ownership of metadata records can only be transfered to [Workspace Groups](../../administrator-guide/managing-users-and-groups/creating-group.md#1-workspace-group). Other group types will not be shown in the list.

domain/src/main/java/org/fao/geonet/domain/Group.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public class Group extends Localized implements Serializable {
8787
private List<MetadataCategory> allowedCategories;
8888
private Boolean enableAllowedCategories;
8989
private Profile minimumProfileForPrivileges;
90+
private GroupType type;
9091

9192
/**
9293
* Get the id of the group.
@@ -371,4 +372,35 @@ public Group setMinimumProfileForPrivileges(Profile minimumProfileForPrivileges)
371372
this.minimumProfileForPrivileges = minimumProfileForPrivileges;
372373
return this;
373374
}
375+
376+
/**
377+
* Get the type of the group.
378+
*
379+
* @return the type of the group.
380+
*/
381+
@Enumerated(EnumType.STRING)
382+
public GroupType getType() {
383+
return type;
384+
}
385+
386+
/**
387+
* Set the type of the group.
388+
*
389+
* @param type the type of the group.
390+
* @return this group entity object.
391+
*/
392+
public Group setType(GroupType type) {
393+
this.type = type;
394+
return this;
395+
}
396+
397+
/**
398+
* Check if this group is a workspace group.
399+
*
400+
* @return true if this group is a workspace group.
401+
*/
402+
@Transient
403+
public Boolean isWorkspace() {
404+
return GroupType.Workspace.equals(type);
405+
}
374406
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.fao.geonet.domain;
2+
3+
/**
4+
* Enum representing different types of groups in the system.
5+
*/
6+
public enum GroupType {
7+
/**
8+
* Represents a workspace group type.
9+
* Workspace groups can create, import, and own metadata records as well as be assigned privileges for specific records.
10+
*/
11+
Workspace,
12+
13+
/**
14+
* Represents a record privilege group type.
15+
* Record privilege groups can be assigned privileges for specific records but cannot own metadata.
16+
*/
17+
RecordPrivilege,
18+
19+
/**
20+
* Represents a system privilege group type.
21+
* System privilege groups are for system permissions only; they cannot own metadata or be assigned privileges for specific records.
22+
*/
23+
SystemPrivilege
24+
}

services/src/main/java/org/fao/geonet/api/groups/GroupsApi.java

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -474,39 +474,78 @@ public void updateGroup(
474474
description = API_PARAM_GROUP_DETAILS
475475
)
476476
@RequestBody
477-
Group group
477+
Group group,
478+
@Parameter(hidden = true)
479+
ServletRequest request
478480
) throws Exception {
479-
final Optional<Group> existing = groupRepository.findById(groupIdentifier);
480-
if (!existing.isPresent()) {
481-
throw new ResourceNotFoundException(String.format(
482-
MSG_GROUP_WITH_IDENTIFIER_NOT_FOUND, groupIdentifier
483-
));
484-
} else {
485-
if(group.getName().length() > GROUPNAME_MAX_LENGHT) {
486-
throw new IllegalArgumentException(String.format("Group name cannot be longer than %d characters.", GROUPNAME_MAX_LENGHT));
481+
482+
Locale locale = languageUtils.parseAcceptLanguage(request.getLocales());
483+
484+
final Group existingGroup = groupRepository.findById(groupIdentifier)
485+
.orElseThrow(() -> new ResourceNotFoundException(String.format(
486+
MSG_GROUP_WITH_IDENTIFIER_NOT_FOUND, groupIdentifier)));
487+
488+
if (group.getName().length() > GROUPNAME_MAX_LENGHT) {
489+
throw new IllegalArgumentException(String.format("Group name cannot be longer than %d characters.", GROUPNAME_MAX_LENGHT));
490+
}
491+
492+
if (!GROUPNAME_PATTERN_REGEX.matcher(group.getName()).matches()) {
493+
throw new IllegalArgumentException("Group name may only contain alphanumeric characters "
494+
+ "or single hyphens. Cannot begin or end with a hyphen."
495+
);
496+
}
497+
498+
GroupType existingGroupType = existingGroup.getType();
499+
GroupType newGroupType = group.getType();
500+
501+
// If changing a workspace group to a non-workspace group
502+
if (existingGroupType == GroupType.Workspace && newGroupType != GroupType.Workspace) {
503+
// Check if the group owns any metadata
504+
final long metadataCount = metadataRepository.count(where((Specification<Metadata>)
505+
MetadataSpecs.isOwnedByOneOfFollowingGroups(Arrays.asList(group.getId()))));
506+
if (metadataCount > 0) {
507+
throw new NotAllowedException(messages.getMessage("api.groups.unset_workspace.owns_metadata", new
508+
Object[]{group.getName()}, locale));
487509
}
510+
}
488511

489-
if (GROUPNAME_PATTERN_REGEX.matcher(group.getName()).matches() == false) {
490-
throw new IllegalArgumentException("Group name may only contain alphanumeric characters "
491-
+ "or single hyphens. Cannot begin or end with a hyphen."
492-
);
512+
// If changing a group to a system group
513+
if (existingGroupType != GroupType.SystemPrivilege && newGroupType == GroupType.SystemPrivilege) {
514+
// Check if the group has any users associated with it that are not registered users
515+
final long invalidProfileCount = userGroupRepository.count(UserGroupSpecs.hasGroupId(group.getId())
516+
.and(Specification.not(UserGroupSpecs.hasProfile(Profile.RegisteredUser))));
517+
if (invalidProfileCount > 0) {
518+
throw new NotAllowedException(messages.getMessage("api.groups.set_system.invalid_profile",
519+
new Object[]{group.getName()}, locale));
520+
}
521+
// Check if the group has any privileges associated with it
522+
final long operationAllowedCount = operationAllowedRepo.count(OperationAllowedSpecs.hasGroupId(group.getId()));
523+
if (operationAllowedCount > 0) {
524+
throw new NotAllowedException(messages.getMessage("api.groups.set_system.has_privileges",
525+
new Object[]{group.getName()}, locale));
493526
}
527+
}
494528

495-
// Rebuild translation pack cache if there are changes in the translations
496-
boolean clearTranslationPackCache =
497-
!existing.get().getLabelTranslations().equals(group.getLabelTranslations());
529+
// If the group is a system group, it cannot have a minimum profile for privileges
530+
if (newGroupType == GroupType.SystemPrivilege && group.getMinimumProfileForPrivileges() != null) {
531+
throw new IllegalArgumentException(messages.getMessage("api.groups.system_group_has_minimum_profile",
532+
new Object[]{group.getName()}, locale));
533+
}
498534

499-
try {
500-
groupRepository.saveAndFlush(group);
501-
} catch (Exception ex) {
502-
Log.error(API.LOG_MODULE_NAME, ExceptionUtils.getStackTrace(ex));
503-
throw new RuntimeException(ex.getMessage());
504-
}
535+
// Rebuild translation pack cache if there are changes in the translations
536+
boolean clearTranslationPackCache =
537+
!existingGroup.getLabelTranslations().equals(group.getLabelTranslations());
538+
539+
try {
540+
groupRepository.saveAndFlush(group);
541+
} catch (Exception ex) {
542+
Log.error(API.LOG_MODULE_NAME, ExceptionUtils.getStackTrace(ex));
543+
throw new RuntimeException(ex.getMessage());
544+
}
505545

506546

507-
if (clearTranslationPackCache) {
508-
translationPackBuilder.clearCache();
509-
}
547+
if (clearTranslationPackCache) {
548+
translationPackBuilder.clearCache();
510549
}
511550
}
512551

@@ -545,14 +584,14 @@ public void deleteGroup(
545584
) throws Exception {
546585
Optional<Group> group = groupRepository.findById(groupIdentifier);
547586

587+
Locale locale = languageUtils.parseAcceptLanguage(request.getLocales());
588+
548589
if (group.isPresent()) {
549590
final long metadataCount = metadataRepository.count(where((Specification<Metadata>)
550591
MetadataSpecs.isOwnedByOneOfFollowingGroups(Arrays.asList(group.get().getId()))));
551592
if (metadataCount > 0) {
552-
throw new NotAllowedException(String.format(
553-
"Group %s owns metadata. To remove the group you should transfer first the metadata to another group.",
554-
group.get().getName()
555-
));
593+
throw new NotAllowedException(messages.getMessage("api.groups.delete.owns_metadata", new
594+
Object[]{group.get().getName()}, locale));
556595
}
557596

558597
List<Integer> reindex = operationAllowedRepo.findAllIds(OperationAllowedSpecs.hasGroupId(groupIdentifier),

0 commit comments

Comments
 (0)