diff --git a/doc/release-notes/6467-optimize-permission-lookups-for-a-user.md b/doc/release-notes/6467-optimize-permission-lookups-for-a-user.md new file mode 100644 index 00000000000..395c6b05c0a --- /dev/null +++ b/doc/release-notes/6467-optimize-permission-lookups-for-a-user.md @@ -0,0 +1,9 @@ +The following API have been added: + +/api/users/{identifier}/allowedCollections/{permission} + +This API lists the dataverses/collections that the user has access to via the permission passed. +By passing "any" as the permission the list will return all dataverse/collections that the user can access regardless of which permission is used. +This API can be executed only by the User requesting their own list of accessible collections or by an Administrator. +Valid Permissions are: AddDataverse, AddDataset, ViewUnpublishedDataverse, ViewUnpublishedDataset, DownloadFile, EditDataverse, EditDataset, ManageDataversePermissions, +ManageDatasetPermissions, ManageFilePermissions, PublishDataverse, PublishDataset, DeleteDataverse, DeleteDatasetDraft, and "any" as a wildcard option. diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index b8070299511..e29e972852c 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -6501,6 +6501,27 @@ Example: List permissions a user (based on API Token used) has on a dataset whos curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/admin/permissions/:persistentId?persistentId=$PERSISTENT_IDENTIFIER" +List Dataverse collections a user can act on based on their permissions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +List Dataverse collections a user can act on based on a particular permission :: + + GET http://$SERVER/api/users/$identifier/allowedCollections/$permission + +.. note:: This API can only be called by an Administrator or by a User requesting their own list of accessible collections. + +The ``$identifier`` is the username of the requested user. +The ``$permission`` is the permission (tied to the roles) that gives the user access to the collection. +Passing ``$permission`` as 'any' will return the collection as long as the user has any access/permission on the collection + +.. code-block:: bash + + export SERVER_URL=https://demo.dataverse.org + export $USERNAME=jsmith + export PERMISSION=PublishDataverse + + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/users/$USERNAME/allowedCollections/$PERMISSION" + Show Role Assignee ~~~~~~~~~~~~~~~~~~ diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index a389cbc735b..2f727987537 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -2,13 +2,14 @@ import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.authorization.DataverseRole; +import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IPv4Address; +import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IPv6Address; +import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IpAddress; import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean; import edu.harvard.iq.dataverse.authorization.users.GuestUser; import edu.harvard.iq.dataverse.authorization.Permission; import edu.harvard.iq.dataverse.authorization.RoleAssignee; -import edu.harvard.iq.dataverse.authorization.groups.Group; import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean; -import edu.harvard.iq.dataverse.authorization.groups.GroupUtil; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.engine.command.Command; @@ -37,7 +38,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; -import java.util.logging.Level; import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; import jakarta.persistence.Query; @@ -100,6 +100,70 @@ public class PermissionServiceBean { @Inject DatasetVersionFilesServiceBean datasetVersionFilesServiceBean; + private static final String LIST_ALL_DATAVERSES_USER_HAS_PERMISSION = """ + WITH grouplist AS ( + SELECT explicitgroup_authenticateduser.explicitgroup_id as id FROM explicitgroup_authenticateduser + WHERE explicitgroup_authenticateduser.containedauthenticatedusers_id = @USERID + ) + + SELECT * FROM DATAVERSE WHERE id IN ( + SELECT definitionpoint_id + FROM roleassignment + WHERE roleassignment.assigneeidentifier IN ( + SELECT CONCAT('&explicit/', explicitgroup.groupalias) as assignee + FROM explicitgroup + WHERE explicitgroup.id IN ( + ( + SELECT explicitgroup.id id + FROM explicitgroup + WHERE EXISTS (SELECT id FROM grouplist WHERE id = explicitgroup.id) + ) UNION ( + SELECT explicitgroup_explicitgroup.containedexplicitgroups_id id + FROM explicitgroup_explicitgroup + WHERE EXISTS (SELECT id FROM grouplist WHERE id = explicitgroup_explicitgroup.explicitgroup_id) + AND EXISTS (SELECT id FROM dataverserole + WHERE dataverserole.id = roleassignment.role_id AND (dataverserole.permissionbits & @PERMISSIONBIT !=0)) + ) + ) + ) UNION ( + SELECT definitionpoint_id + FROM roleassignment + WHERE roleassignment.assigneeidentifier = ( + SELECT CONCAT('@', authenticateduser.useridentifier) + FROM authenticateduser + WHERE authenticateduser.id = @USERID) + AND EXISTS (SELECT id FROM dataverserole + WHERE dataverserole.id = roleassignment.role_id AND (dataverserole.permissionbits & @PERMISSIONBIT !=0)) + ) UNION ( + SELECT definitionpoint_id + FROM roleassignment + WHERE roleassignment.assigneeidentifier = ':authenticated-users' + AND EXISTS (SELECT id FROM dataverserole + WHERE dataverserole.id = roleassignment.role_id AND (dataverserole.permissionbits & @PERMISSIONBIT !=0)) + ) UNION ( + SELECT definitionpoint_id + FROM roleassignment + WHERE roleassignment.assigneeidentifier IN ( + SELECT CONCAT('&shib/', persistedglobalgroup.persistedgroupalias) as assignee + FROM persistedglobalgroup + WHERE dtype = 'ShibGroup' + AND EXISTS (SELECT id FROM dataverserole WHERE dataverserole.id = roleassignment.role_id AND (dataverserole.permissionbits & @PERMISSIONBIT !=0)) + ) + ) UNION ( + SELECT definitionpoint_id + FROM roleassignment + WHERE roleassignment.assigneeidentifier IN ( + SELECT CONCAT('&ip/', persistedglobalgroup.persistedgroupalias) as assignee + FROM persistedglobalgroup + LEFT OUTER JOIN ipv4range ON persistedglobalgroup.id = ipv4range.owner_id + LEFT OUTER JOIN ipv6range ON persistedglobalgroup.id = ipv6range.owner_id + WHERE dtype = 'IpGroup' + AND EXISTS (SELECT id FROM dataverserole WHERE dataverserole.id = roleassignment.role_id AND (dataverserole.permissionbits & @PERMISSIONBIT !=0)) + AND @IPRANGESQL + ) + ) + ) + """; /** * A request-level permission query (e.g includes IP ras). */ @@ -553,36 +617,6 @@ public RequestPermissionQuery request(DataverseRequest req) { return new RequestPermissionQuery(null, req); } - /** - * Go from (User, Permission) to a list of Dataverse objects that the user - has the permission on. - * - * @param user - * @param permission - * @return The list of dataverses {@code user} has permission - {@code permission} on. - */ - public List getDataversesUserHasPermissionOn(AuthenticatedUser user, Permission permission) { - Set groups = groupService.groupsFor(user); - String identifiers = GroupUtil.getAllIdentifiersForUser(user, groups); - /** - * @todo Are there any strings in identifiers that would break this SQL - * query? - */ - String query = "SELECT id FROM dvobject WHERE dtype = 'Dataverse' and id in (select definitionpoint_id from roleassignment where assigneeidentifier in (" + identifiers + "));"; - logger.log(Level.FINE, "query: {0}", query); - Query nativeQuery = em.createNativeQuery(query); - List dataverseIdsToCheck = nativeQuery.getResultList(); - List dataversesUserHasPermissionOn = new LinkedList<>(); - for (int dvIdAsInt : dataverseIdsToCheck) { - Dataverse dataverse = dataverseService.find(Long.valueOf(dvIdAsInt)); - if (userOn(user, dataverse).has(permission)) { - dataversesUserHasPermissionOn.add(dataverse); - } - } - return dataversesUserHasPermissionOn; - } - public List getUsersWithPermissionOn(Permission permission, DvObject dvo) { List usersHasPermissionOn = new LinkedList<>(); Set ras = roleService.rolesAssignments(dvo); @@ -888,4 +922,46 @@ private boolean hasUnrestrictedReleasedFiles(DatasetVersion targetDatasetVersion Long result = em.createQuery(criteriaQuery).getSingleResult(); return result > 0; } + + public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, Permission permission) { + return findPermittedCollections(request, user, 1 << permission.ordinal()); + } + public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, int permissionBit) { + if (user != null) { + // IP Group - Only check IP if a User is calling for themself + String ipRangeSQL = "FALSE"; + if (request != null + && request.getAuthenticatedUser() != null + && request.getSourceAddress() != null + && request.getAuthenticatedUser().getUserIdentifier().equalsIgnoreCase(user.getUserIdentifier())) { + IpAddress ip = request.getSourceAddress(); + if (ip instanceof IPv4Address) { + IPv4Address ipv4 = (IPv4Address) ip; + ipRangeSQL = ipv4.toBigInteger() + " BETWEEN ipv4range.bottomaslong AND ipv4range.topaslong"; + } else if (ip instanceof IPv6Address) { + IPv6Address ipv6 = (IPv6Address) ip; + long[] vals = ipv6.toLongArray(); + if (vals.length == 4) { + ipRangeSQL = """ + (@0 BETWEEN ipv6range.bottoma AND ipv6range.topa + AND @1 BETWEEN ipv6range.bottomb AND ipv6range.topb + AND @2 BETWEEN ipv6range.bottomc AND ipv6range.topc + AND @3 BETWEEN ipv6range.bottomd AND ipv6range.topd) + """; + for (int i = 0; i < vals.length; i++) { + ipRangeSQL = ipRangeSQL.replace("@" + i, String.valueOf(vals[i])); + } + } + } + } + + String sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION + .replace("@USERID", String.valueOf(user.getId())) + .replace("@PERMISSIONBIT", String.valueOf(permissionBit)) + .replace("@IPRANGESQL", ipRangeSQL); + return em.createNativeQuery(sqlCode, Dataverse.class).getResultList(); + } + return null; + } } + diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Users.java b/src/main/java/edu/harvard/iq/dataverse/api/Users.java index 166465115c8..77e08bf6ceb 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Users.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Users.java @@ -270,6 +270,29 @@ public Response getTracesElement(@Context ContainerRequestContext crc, @Context } } + @GET + @AuthRequired + @Path("{identifier}/allowedCollections/{permission}") + @Produces("application/json") + public Response getUserPermittedCollections(@Context ContainerRequestContext crc, @Context Request req, @PathParam("identifier") String identifier, @PathParam("permission") String permission) { + AuthenticatedUser authenticatedUser = null; + try { + authenticatedUser = getRequestAuthenticatedUserOrDie(crc); + if (!authenticatedUser.getUserIdentifier().equalsIgnoreCase(identifier) && !authenticatedUser.isSuperuser()) { + return error(Response.Status.FORBIDDEN, "This API call can be used by Users getting there own permitted collections or by superusers."); + } + } catch (WrappedResponse ex) { + return error(Response.Status.UNAUTHORIZED, "Authentication is required."); + } + try { + AuthenticatedUser userToQuery = authSvc.getAuthenticatedUser(identifier); + JsonObjectBuilder jsonObj = execCommand(new GetUserPermittedCollectionsCommand(createDataverseRequest(getRequestUser(crc)), userToQuery, permission)); + return ok(jsonObj); + } catch (WrappedResponse ex) { + return ex.getResponse(); + } + } + @POST @Path("register") public Response registerOIDCUser(String body) { diff --git a/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/SWORDv2ServiceDocumentServlet.java b/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/SWORDv2ServiceDocumentServlet.java index eab005d87fa..a32e97bcee2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/SWORDv2ServiceDocumentServlet.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/SWORDv2ServiceDocumentServlet.java @@ -1,6 +1,8 @@ package edu.harvard.iq.dataverse.api.datadeposit; import java.io.IOException; + +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import jakarta.inject.Inject; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -29,6 +31,7 @@ public void init() throws ServletException { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + serviceDocumentManagerImpl.setIpAddress((new DataverseRequest(null, req)).getSourceAddress()); this.api.get(req, resp); } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/ServiceDocumentManagerImpl.java b/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/ServiceDocumentManagerImpl.java index 134d54aef88..62f23e97af9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/ServiceDocumentManagerImpl.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/datadeposit/ServiceDocumentManagerImpl.java @@ -4,7 +4,9 @@ import edu.harvard.iq.dataverse.DataverseServiceBean; import edu.harvard.iq.dataverse.PermissionServiceBean; import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IpAddress; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import edu.harvard.iq.dataverse.util.SystemConfig; import java.util.List; import java.util.logging.Logger; @@ -37,6 +39,8 @@ public class ServiceDocumentManagerImpl implements ServiceDocumentManager { @Inject UrlManager urlManager; + private IpAddress ipAddress = null; + @Override public ServiceDocument getServiceDocument(String sdUri, AuthCredentials authCredentials, SwordConfiguration config) throws SwordError, SwordServerException, SwordAuthException { @@ -65,7 +69,7 @@ public ServiceDocument getServiceDocument(String sdUri, AuthCredentials authCred * shibIdentityProvider String on AuthenticatedUser is only set when a * SAML assertion is made at runtime via the browser. */ - List dataverses = permissionService.getDataversesUserHasPermissionOn(user, Permission.AddDataset); + List dataverses = permissionService.findPermittedCollections(new DataverseRequest(user, ipAddress), user, Permission.AddDataset); for (Dataverse dataverse : dataverses) { String dvAlias = dataverse.getAlias(); if (dvAlias != null && !dvAlias.isEmpty()) { @@ -82,4 +86,7 @@ public ServiceDocument getServiceDocument(String sdUri, AuthCredentials authCred return service; } + public void setIpAddress(IpAddress ipAddress) { + this.ipAddress = ipAddress; + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetUserPermittedCollectionsCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetUserPermittedCollectionsCommand.java new file mode 100644 index 00000000000..c4888c8c99c --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetUserPermittedCollectionsCommand.java @@ -0,0 +1,61 @@ +package edu.harvard.iq.dataverse.engine.command.impl; + +import edu.harvard.iq.dataverse.Dataverse; +import edu.harvard.iq.dataverse.DvObject; +import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IpAddress; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.engine.command.AbstractCommand; +import edu.harvard.iq.dataverse.engine.command.CommandContext; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.engine.command.RequiredPermissions; +import edu.harvard.iq.dataverse.engine.command.exception.CommandException; +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObjectBuilder; + +import java.util.List; +import java.util.logging.Logger; + +import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json; + +@RequiredPermissions({}) +public class GetUserPermittedCollectionsCommand extends AbstractCommand { + private static final Logger logger = Logger.getLogger(GetUserPermittedCollectionsCommand.class.getCanonicalName()); + + private DataverseRequest request; + private AuthenticatedUser user; + private String permission; + public GetUserPermittedCollectionsCommand(DataverseRequest request, AuthenticatedUser user, String permission) { + super(request, (DvObject) null); + this.request = request; + this.user = user; + this.permission = permission; + } + + @Override + public JsonObjectBuilder execute(CommandContext ctxt) throws CommandException { + if (user == null) { + throw new CommandException("User not found.", this); + } + int permissionBit; + try { + permissionBit = permission.equalsIgnoreCase("any") ? + Integer.MAX_VALUE : (1 << Permission.valueOf(permission).ordinal()); + } catch (IllegalArgumentException e) { + throw new CommandException("Permission not valid.", this); + } + List collections = ctxt.permissions().findPermittedCollections(request, user, permissionBit); + if (collections != null) { + JsonObjectBuilder job = Json.createObjectBuilder(); + JsonArrayBuilder jab = Json.createArrayBuilder(); + for (Dataverse dv : collections) { + jab.add(json(dv)); + } + job.add("count", collections.size()); + job.add("items", jab); + return job; + } + return null; + } +} diff --git a/src/test/java/edu/harvard/iq/dataverse/api/IpGroupsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/IpGroupsIT.java index 1c7e7b05650..67ab63bead6 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/IpGroupsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/IpGroupsIT.java @@ -18,7 +18,11 @@ import org.junit.jupiter.api.Test; public class IpGroupsIT { - +/* + WARNING: Running this test will creat IP Groups that give access based on any IP address. + This will cause other tests that count the number of Collections a user can access to be higher than expected. + Since this Test Class is not being run in an automated test suite it isn't an issue. + */ private static final Logger logger = Logger.getLogger(IpGroupsIT.class.getCanonicalName()); @BeforeAll @@ -48,12 +52,12 @@ public void testDownloadFile() { Response userWithNoRoles = UtilIT.createRandomUser(); String userWithNoRolesApiToken = UtilIT.getApiTokenFromResponse(userWithNoRoles); - String pathToFile = "src/main/webapp/resources/images/favicondataverse.png"; + String pathToFile = "src/main/webapp/resources/images/dataverseproject.png"; Response addResponse = UtilIT.uploadFileViaNative(datasetId.toString(), pathToFile, apiToken); addResponse.then().assertThat() .body("data.files[0].dataFile.contentType", equalTo("image/png")) - .body("data.files[0].label", equalTo("favicondataverse.png")) + .body("data.files[0].label", equalTo("dataverseproject.png")) .statusCode(OK.getStatusCode()); Long fileId = JsonPath.from(addResponse.body().asString()).getLong("data.files[0].dataFile.id"); @@ -62,7 +66,7 @@ public void testDownloadFile() { Response restrictResponse = UtilIT.restrictFile(fileId.toString(), restrict, apiToken); restrictResponse.prettyPrint(); restrictResponse.then().assertThat() - .body("data.message", equalTo("File favicondataverse.png restricted.")) + .body("data.message", equalTo("File dataverseproject.png restricted.")) .statusCode(OK.getStatusCode()); Response publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken); @@ -146,6 +150,17 @@ public void testDownloadFile() { // Should get an OK response (able to download file) based on IP Group membership. No API token. assertEquals(OK.getStatusCode(), anonDownload.getStatusCode()); + grantIpAll = UtilIT.grantRoleOnDataverse(dataverseAlias, DataverseRole.CURATOR.toString(), ipGroupIdentifierString, apiToken); + grantIpAll.prettyPrint(); + grantIpAll.then().assertThat() + .body("data.assignee", equalTo(ipGroupIdentifierString)) + .body("data._roleAlias", equalTo("curator")) + .statusCode(OK.getStatusCode()); + + Response collectionsResp = UtilIT.getUserPermittedCollections(username, apiToken, "PublishDataset"); + collectionsResp.prettyPrint(); + collectionsResp.then().assertThat() + .statusCode(OK.getStatusCode()); } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UsersIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UsersIT.java index eb78a216626..dce4871dc16 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UsersIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UsersIT.java @@ -518,6 +518,85 @@ public void testDeleteAuthenticatedUser() { } @Test + public void testUserPermittedDataverses() { + Response createSuperuser = UtilIT.createRandomUser(); + String superuserUsername = UtilIT.getUsernameFromResponse(createSuperuser); + String superuserApiToken = UtilIT.getApiTokenFromResponse(createSuperuser); + Response toggleSuperuser = UtilIT.makeSuperUser(superuserUsername); + toggleSuperuser.then().assertThat() + .statusCode(OK.getStatusCode()); + + Response createUser = UtilIT.createRandomUser(); + createUser.prettyPrint(); + assertEquals(200, createUser.getStatusCode()); + String usernameOfUser = UtilIT.getUsernameFromResponse(createUser); + String userApiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverse1 = UtilIT.createRandomDataverse(superuserApiToken); + createDataverse1.prettyPrint(); + createDataverse1.then().assertThat() + .statusCode(CREATED.getStatusCode()); + String dataverseAlias1 = UtilIT.getAliasFromResponse(createDataverse1); + + // create a second Dataverse and add a Group with permissions + Response createDataverse2 = UtilIT.createRandomDataverse(superuserApiToken); + createDataverse2.prettyPrint(); + createDataverse2.then().assertThat() + .statusCode(CREATED.getStatusCode()); + String dataverseAlias2 = UtilIT.getAliasFromResponse(createDataverse2); + String aliasInOwner = "groupFor" + dataverseAlias2; + String displayName = "Group for " + dataverseAlias2; + Response createGroup = UtilIT.createGroup(dataverseAlias2, aliasInOwner, displayName, superuserApiToken); + String groupIdentifier = JsonPath.from(createGroup.asString()).getString("data.identifier"); + Response grantRoleResponse = UtilIT.grantRoleOnDataverse(dataverseAlias2, DataverseRole.EDITOR.toString(), groupIdentifier, superuserApiToken); + grantRoleResponse.prettyPrint(); + grantRoleResponse.then().assertThat() + .statusCode(OK.getStatusCode()); + + Response collectionsResp = UtilIT.getUserPermittedCollections(superuserUsername, userApiToken, "ViewUnpublishedDataset"); + collectionsResp.prettyPrint(); + assertEquals(403, collectionsResp.getStatusCode()); + collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, userApiToken, "ViewUnpublishedDataset"); + collectionsResp.prettyPrint(); + assertEquals(200, collectionsResp.getStatusCode()); + collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, "", "ViewUnpublishedDataset"); + assertEquals(401, collectionsResp.getStatusCode()); + collectionsResp = UtilIT.getUserPermittedCollections("fakeUser", superuserApiToken, "ViewUnpublishedDataset"); + assertEquals(500, collectionsResp.getStatusCode()); + collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, superuserApiToken, "bad"); + assertEquals(500, collectionsResp.getStatusCode()); + + // Testing adding an explicit permission/role to one dataverse + collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, userApiToken, "DownloadFile"); + collectionsResp.prettyPrint(); + collectionsResp.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.count", equalTo(0)); + + Response assignRole = UtilIT.grantRoleOnDataverse(dataverseAlias1, DataverseRole.EDITOR.toString(), + "@" + usernameOfUser, superuserApiToken); + assignRole.prettyPrint(); + assertEquals(200, assignRole.getStatusCode()); + + collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, userApiToken, "DownloadFile"); + collectionsResp.prettyPrint(); + collectionsResp.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.count", equalTo(1)); + + // Add user to group and test with both explicit and group permissions + Response addToGroup = UtilIT.addToGroup(dataverseAlias2, aliasInOwner, List.of("@" + usernameOfUser), superuserApiToken); + addToGroup.prettyPrint(); + addToGroup.then().assertThat() + .statusCode(OK.getStatusCode()); + + collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, userApiToken, "DownloadFile"); + collectionsResp.prettyPrint(); + collectionsResp.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.count", equalTo(2)); + } + // This test is disabled because it is only compatible with the containerized development environment and would cause the Jenkins job to fail. @Disabled public void testRegisterOIDCUser() { diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 50bda95e5a8..e3719ea5955 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -1449,6 +1449,15 @@ public static Response getUserTraces(String username, String apiToken) { return response; } + public static Response getUserPermittedCollections(String username, String apiToken, String permission) { + RequestSpecification requestSpecification = given(); + if (!StringUtil.isEmpty(apiToken)) { + requestSpecification.header(API_TOKEN_HTTP_HEADER, apiToken); + } + Response response = requestSpecification.get("/api/users/" + username + "/allowedCollections/" + permission); + return response; + } + public static Response reingestFile(Long fileId, String apiToken) { Response response = given() .header(API_TOKEN_HTTP_HEADER, apiToken)