CVE-2021-41189: Incorrect Authorization (CWE-863)
value: DSpace is an open source turnkey repository application. In version 7.0, any community or collection administrator can escalate their permission up to become system administrator. This vulnerability only exists in 7.0 and does not impact 6.x or below. This issue is patched in version 7.1. As a workaround, users of 7.0 may temporarily disable the ability for community or collection administrators to manage permissions or workflows settings.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 7.2
Published: 2021-10-29T18:15Z
Repository: https://github.com/DSpace/DSpace
Language: Java
Files changed: dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java, dspace-api/src/main/java/org/dspace/content/service/CollectionService.java, dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java
Commit date: 2021-10-25 19:01:36 +0200
Commit message: Fix for GHSA-cf2j-vf36-c6w8

Diff: diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
index 58085ef0d..de29b8026 100644
--- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
@@ -918,22 +918,27 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
     @Override
     public Group createDefaultReadGroup(Context context, Collection collection, String typeOfGroupString,
                                         int defaultRead)
         throws SQLException, AuthorizeException {
         Group role = groupService.create(context);
-        groupService.setName(role, "COLLECTION_" + collection.getID().toString() + "_" + typeOfGroupString +
-            "_DEFAULT_READ");
+        groupService.setName(role, getDefaultReadGroupName(collection, typeOfGroupString));
 
         // Remove existing privileges from the anonymous group.
         authorizeService.removePoliciesActionFilter(context, collection, defaultRead);
 
         // Grant our new role the default privileges.
         authorizeService.addPolicy(context, collection, defaultRead, role);
         groupService.update(context, role);
         return role;
     }
 
+    @Override
+    public String getDefaultReadGroupName(Collection collection, String typeOfGroupString) {
+        return "COLLECTION_" + collection.getID().toString() + "_" + typeOfGroupString +
+            "_DEFAULT_READ";
+    }
+
     @Override
     public List<Collection> findCollectionsWithSubmit(String q, Context context, Community community,
         int offset, int limit) throws SQLException, SearchServiceException {
 
         List<Collection> collections = new ArrayList<>();
diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java
index 3a4806579..83bf9cf57 100644
--- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java
+++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java
@@ -358,10 +358,20 @@ public interface CollectionService
      * @throws AuthorizeException   If something goes wrong
      */
     Group createDefaultReadGroup(Context context, Collection collection, String typeOfGroupString, int defaultRead)
         throws SQLException, AuthorizeException;
 
+    /**
+     * This method will return the name to give to the group created by the
+     * {@link #createDefaultReadGroup(Context, Collection, String, int)} method
+     * 
+     * @param collection        The DSpace collection to use in the name generation
+     * @param typeOfGroupString The type of group to use in the name generation
+     * @return the name to give to the group that hold default read for the collection
+     */
+    String getDefaultReadGroupName(Collection collection, String typeOfGroupString);
+
     /**
      * Returns Collections for which the current user has 'submit' privileges.
      * NOTE: for better performance, this method retrieves its results from an
      *       index (cache) and does not query the database directly.
      *       This means that results may be stale or outdated until https://github.com/DSpace/DSpace/issues/2853 is resolved"
diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java
index 4c98ed20c..be81cd9bd 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java
@@ -13,10 +13,11 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -733,17 +734,28 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
                             || AuthorizeConfiguration.canCommunityAdminManageCollectionWorkflows()) {
                             List<Group> groups = new ArrayList<>();
                             groups.add(group);
                             List<ResourcePolicy> policies = resourcePolicyService.find(context, null, groups,
                                                             Constants.DEFAULT_ITEM_READ, Constants.COLLECTION);
-                            if (policies.size() > 0) {
-                                return policies.get(0).getdSpaceObject();
+
+                            Optional<ResourcePolicy> defaultPolicy = policies.stream().filter(p -> StringUtils.equals(
+                                    collectionService.getDefaultReadGroupName((Collection) p.getdSpaceObject(), "ITEM"),
+                                    group.getName())).findFirst();
+
+                            if (defaultPolicy.isPresent()) {
+                                return defaultPolicy.get().getdSpaceObject();
                             }
                             policies = resourcePolicyService.find(context, null, groups,
                                                              Constants.DEFAULT_BITSTREAM_READ, Constants.COLLECTION);
-                            if (policies.size() > 0) {
-                                return policies.get(0).getdSpaceObject();
+
+                            defaultPolicy = policies.stream()
+                                    .filter(p -> StringUtils.equals(collectionService.getDefaultReadGroupName(
+                                            (Collection) p.getdSpaceObject(), "BITSTREAM"), group.getName()))
+                                    .findFirst();
+
+                            if (defaultPolicy.isPresent()) {
+                                return defaultPolicy.get().getdSpaceObject();
                             }
                         }
                     }
                 }
                 if (AuthorizeConfiguration.canCommunityAdminManageAdminGroup()) {

Vulnerable code:         groupService.setName(role, "COLLECTION_" + collection.getID().toString() + "_" + typeOfGroupString +
            "_DEFAULT_READ");
                            if (policies.size() > 0) {
                                return policies.get(0).getdSpaceObject();
                            if (policies.size() > 0) {
                                return policies.get(0).getdSpaceObject();
Fixed code:         groupService.setName(role, getDefaultReadGroupName(collection, typeOfGroupString));
    @Override
    public String getDefaultReadGroupName(Collection collection, String typeOfGroupString) {
        return "COLLECTION_" + collection.getID().toString() + "_" + typeOfGroupString +
            "_DEFAULT_READ";
    }

    /**
     * This method will return the name to give to the group created by the
     * {@link #createDefaultReadGroup(Context, Collection, String, int)} method
     * 
     * @param collection        The DSpace collection to use in the name generation
     * @param typeOfGroupString The type of group to use in the name generation
     * @return the name to give to the group that hold default read for the collection
     */
    String getDefaultReadGroupName(Collection collection, String typeOfGroupString);

import java.util.Optional;

                            Optional<ResourcePolicy> defaultPolicy = policies.stream().filter(p -> StringUtils.equals(
                                    collectionService.getDefaultReadGroupName((Collection) p.getdSpaceObject(), "ITEM"),
                                    group.getName())).findFirst();

                            if (defaultPolicy.isPresent()) {
                                return defaultPolicy.get().getdSpaceObject();

                            defaultPolicy = policies.stream()
                                    .filter(p -> StringUtils.equals(collectionService.getDefaultReadGroupName(
                                            (Collection) p.getdSpaceObject(), "BITSTREAM"), group.getName()))
                                    .findFirst();

                            if (defaultPolicy.isPresent()) {
                                return defaultPolicy.get().getdSpaceObject();