From 5ee23602760d3d68c53cb554994080953b2d5bd8 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:31:27 -0400 Subject: [PATCH 01/30] DRAFT using mydata/retrieve API --- .../iq/dataverse/mydata/DataRetrieverAPI.java | 11 +++++---- .../iq/dataverse/api/DataRetrieverApiIT.java | 23 +++++++++++++++++++ .../edu/harvard/iq/dataverse/api/UtilIT.java | 18 +++++++++++---- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 336e7735659..a1c7de54c4b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -274,8 +274,10 @@ public String retrieveMyDataAsJsonString( @QueryParam("role_ids") List roleIds, @QueryParam("userIdentifier") String userIdentifier, @QueryParam("filter_validities") Boolean filterValidities, - @QueryParam("dataset_valid") List datasetValidities) { + @QueryParam("dataset_valid") List datasetValidities, + @QueryParam("my_data") Boolean my_data) { boolean OTHER_USER = false; + boolean dataRelatedToMe = my_data != null ? my_data : true; String noMsgResultsFound = BundleUtil.getStringFromBundle("dataretrieverAPI.noMsgResultsFound"); @@ -385,10 +387,11 @@ public String retrieveMyDataAsJsonString( //SearchFields.NAME_SORT, SortBy.ASCENDING, SearchFields.RELEASE_OR_CREATE_DATE, SortBy.DESCENDING, solrCardStart, //paginationStart, - true, // dataRelatedToMe + dataRelatedToMe, // dataRelatedToMe SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE //10 // SearchFields.NUM_SOLR_DOCS_TO_RETRIEVE ); - + logger.severe(">>>> dataRelatedToMe " + dataRelatedToMe); + logger.severe(">>>> this.solrQueryResponse.getNumResultsFound() " + this.solrQueryResponse.getNumResultsFound()); //msgt("getResultsStart: " + this.solrQueryResponse.getResultsStart()); //msgt("getNumResultsFound: " + this.solrQueryResponse.getNumResultsFound()); //msgt("getSolrSearchResults: " + this.solrQueryResponse.getSolrSearchResults().toString()); @@ -527,4 +530,4 @@ private JsonArrayBuilder formatSolrDocs(SolrQueryResponse solrResponse, RoleTagR private boolean isValid(SolrSearchResult result) { return result.isValid(x -> true); } -} \ No newline at end of file +} diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index c3bfdb80e6e..f7fb73e3161 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -1,5 +1,6 @@ package edu.harvard.iq.dataverse.api; +import edu.harvard.iq.dataverse.DvObject; import io.restassured.RestAssured; import io.restassured.response.Response; import edu.harvard.iq.dataverse.api.auth.ApiKeyAuthMechanism; @@ -90,14 +91,36 @@ public void testRetrieveMyDataAsJsonString() { assertEquals(1, jsonPathOneDataverse.getInt("data.total_count")); assertEquals(dataverseAlias, jsonPathOneDataverse.getString("data.items[0].name")); + //url = "/api/mydata/retrieve?role_ids=1&role_ids=3&role_ids=5&role_ids=7&dvobject_types=Dataverse&start=#{start}&per_page=#{per_page}&published_states=Published&published_states=Unpublished" + createDataverseResponse = UtilIT.createRandomDataverse(superUserApiToken); + createDataverseResponse.prettyPrint(); + String dataverseAlias2 = UtilIT.getAliasFromResponse(createDataverseResponse); + //UtilIT.publishDataverseViaNativeApi(dataverseAlias2, superUserApiToken); + UtilIT.grantRoleOnDataverse(dataverseAlias2, DataverseRole.ADMIN.toString(), + "@" + normalUserUsername, superUserApiToken); + createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias2, normalUserApiToken); + createDatasetResponse.prettyPrint(); + assertEquals(201, createDatasetResponse.getStatusCode()); + Integer datasetId2 = UtilIT.getDatasetIdFromResponse(createDatasetResponse); + UtilIT.sleepForReindex(datasetId2.toString(), normalUserApiToken, 4); + + Response response = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", Arrays.asList(3L , 5L, 7L), Arrays.asList(DvObject.DType.Dataverse), false); + response.prettyPrint(); + // Clean up Response deleteDatasetResponse = UtilIT.deleteDatasetViaNativeApi(datasetId, normalUserApiToken); deleteDatasetResponse.prettyPrint(); assertEquals(200, deleteDatasetResponse.getStatusCode()); + deleteDatasetResponse = UtilIT.deleteDatasetViaNativeApi(datasetId2, normalUserApiToken); + deleteDatasetResponse.prettyPrint(); + assertEquals(200, deleteDatasetResponse.getStatusCode()); Response deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias, normalUserApiToken); deleteDataverseResponse.prettyPrint(); assertEquals(200, deleteDataverseResponse.getStatusCode()); + deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias2, superUserApiToken); + deleteDataverseResponse.prettyPrint(); + assertEquals(200, deleteDataverseResponse.getStatusCode()); Response deleteUserResponse = UtilIT.deleteUser(normalUserUsername); deleteUserResponse.prettyPrint(); 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 e594751297d..32ce3e5ce40 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -1,6 +1,7 @@ package edu.harvard.iq.dataverse.api; -import edu.harvard.iq.dataverse.Dataverse; +import edu.harvard.iq.dataverse.*; +import edu.harvard.iq.dataverse.search.IndexServiceBean; import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; import io.restassured.http.ContentType; import io.restassured.path.json.JsonPath; @@ -47,9 +48,7 @@ import static edu.harvard.iq.dataverse.api.ApiConstants.*; import static io.restassured.path.xml.XmlPath.from; import static io.restassured.RestAssured.given; -import edu.harvard.iq.dataverse.DatasetField; -import edu.harvard.iq.dataverse.DatasetFieldType; -import edu.harvard.iq.dataverse.DatasetFieldValue; + import edu.harvard.iq.dataverse.settings.FeatureFlags; import edu.harvard.iq.dataverse.util.StringUtil; @@ -4121,6 +4120,17 @@ static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifie .get("/api/mydata/retrieve?userIdentifier=" + userIdentifier); return response; } + static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifier, List roleIds, List objectTypes, boolean myDataOnly) { + Response response = given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .contentType("application/json; charset=utf-8") + .queryParam("role_ids", roleIds) + .queryParam("dvobject_types", objectTypes) + .queryParam("published_states", MyDataFilterParams.allPublishedStates) + .queryParam("my_data", myDataOnly) + .get("/api/mydata/retrieve?userIdentifier=" + userIdentifier); + return response; + } static Response createSignedUrl(String apiToken, String apiPath, String username) { Response response = given() From 2b8d4eed5ac700f9b7ab10b9ce01c4f332fa0dc5 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:37:54 -0400 Subject: [PATCH 02/30] DRAFT using mydata/retrieve API --- .../java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index f7fb73e3161..2bd0e350e2d 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -96,7 +96,7 @@ public void testRetrieveMyDataAsJsonString() { createDataverseResponse.prettyPrint(); String dataverseAlias2 = UtilIT.getAliasFromResponse(createDataverseResponse); //UtilIT.publishDataverseViaNativeApi(dataverseAlias2, superUserApiToken); - UtilIT.grantRoleOnDataverse(dataverseAlias2, DataverseRole.ADMIN.toString(), + UtilIT.grantRoleOnDataverse(dataverseAlias2, DataverseRole.FULL_CONTRIBUTOR.toString(), "@" + normalUserUsername, superUserApiToken); createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias2, normalUserApiToken); createDatasetResponse.prettyPrint(); @@ -106,6 +106,7 @@ public void testRetrieveMyDataAsJsonString() { Response response = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", Arrays.asList(3L , 5L, 7L), Arrays.asList(DvObject.DType.Dataverse), false); response.prettyPrint(); + assertEquals(2, jsonPathOneDataverse.getInt("data.total_count")); // Clean up Response deleteDatasetResponse = UtilIT.deleteDatasetViaNativeApi(datasetId, normalUserApiToken); From f4d017ce24ce61c6c2c75893ebeb20de3fc33762 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:52:49 -0400 Subject: [PATCH 03/30] DRAFT using mydata/retrieve API --- .../java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java | 2 -- src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java | 1 - 2 files changed, 3 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index a1c7de54c4b..b153c1e4f6f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -390,8 +390,6 @@ public String retrieveMyDataAsJsonString( dataRelatedToMe, // dataRelatedToMe SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE //10 // SearchFields.NUM_SOLR_DOCS_TO_RETRIEVE ); - logger.severe(">>>> dataRelatedToMe " + dataRelatedToMe); - logger.severe(">>>> this.solrQueryResponse.getNumResultsFound() " + this.solrQueryResponse.getNumResultsFound()); //msgt("getResultsStart: " + this.solrQueryResponse.getResultsStart()); //msgt("getNumResultsFound: " + this.solrQueryResponse.getNumResultsFound()); //msgt("getSolrSearchResults: " + this.solrQueryResponse.getSolrSearchResults().toString()); 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 32ce3e5ce40..14cc758e50a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -1,7 +1,6 @@ package edu.harvard.iq.dataverse.api; import edu.harvard.iq.dataverse.*; -import edu.harvard.iq.dataverse.search.IndexServiceBean; import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; import io.restassured.http.ContentType; import io.restassured.path.json.JsonPath; From 2dee3f73820ba58e40013f80aae7192c4ccedd15 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:41:24 -0400 Subject: [PATCH 04/30] adding new api endpoint --- .../iq/dataverse/mydata/DataRetrieverAPI.java | 132 +++++++++++++++--- .../iq/dataverse/api/DataRetrieverApiIT.java | 80 +++++++++-- .../edu/harvard/iq/dataverse/api/UtilIT.java | 9 +- 3 files changed, 185 insertions(+), 36 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index b153c1e4f6f..8e54f7dd047 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -3,13 +3,7 @@ */ package edu.harvard.iq.dataverse.mydata; -import edu.harvard.iq.dataverse.DatasetServiceBean; -import edu.harvard.iq.dataverse.DataverseRoleServiceBean; -import edu.harvard.iq.dataverse.DataverseServiceBean; -import edu.harvard.iq.dataverse.DataverseSession; -import edu.harvard.iq.dataverse.DvObject; -import edu.harvard.iq.dataverse.DvObjectServiceBean; -import edu.harvard.iq.dataverse.RoleAssigneeServiceBean; +import edu.harvard.iq.dataverse.*; import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.search.SolrQueryResponse; import edu.harvard.iq.dataverse.search.SolrSearchResult; @@ -17,7 +11,6 @@ import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.authorization.DataverseRole; import edu.harvard.iq.dataverse.authorization.DataverseRolePermissionHelper; -//import edu.harvard.iq.dataverse.authorization.MyDataQueryHelperServiceBean; import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; @@ -27,15 +20,13 @@ import edu.harvard.iq.dataverse.search.SearchServiceFactory; import edu.harvard.iq.dataverse.search.SortBy; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.logging.Logger; + +import edu.harvard.iq.dataverse.util.json.JsonPrinter; import jakarta.ejb.EJB; import jakarta.inject.Inject; -import jakarta.json.Json; -import jakarta.json.JsonArrayBuilder; -import jakarta.json.JsonObjectBuilder; +import jakarta.json.*; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; @@ -274,10 +265,8 @@ public String retrieveMyDataAsJsonString( @QueryParam("role_ids") List roleIds, @QueryParam("userIdentifier") String userIdentifier, @QueryParam("filter_validities") Boolean filterValidities, - @QueryParam("dataset_valid") List datasetValidities, - @QueryParam("my_data") Boolean my_data) { + @QueryParam("dataset_valid") List datasetValidities) { boolean OTHER_USER = false; - boolean dataRelatedToMe = my_data != null ? my_data : true; String noMsgResultsFound = BundleUtil.getStringFromBundle("dataretrieverAPI.noMsgResultsFound"); @@ -387,7 +376,7 @@ public String retrieveMyDataAsJsonString( //SearchFields.NAME_SORT, SortBy.ASCENDING, SearchFields.RELEASE_OR_CREATE_DATE, SortBy.DESCENDING, solrCardStart, //paginationStart, - dataRelatedToMe, // dataRelatedToMe + true, // dataRelatedToMe SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE //10 // SearchFields.NUM_SOLR_DOCS_TO_RETRIEVE ); //msgt("getResultsStart: " + this.solrQueryResponse.getResultsStart()); @@ -456,7 +445,112 @@ public String retrieveMyDataAsJsonString( return jsonData.build().toString(); } - + + @GET + @AuthRequired + @Path(retrieveDataPartialAPIPath + "/collectionList") + @Produces({"application/json"}) + public String retrieveMyCollectionList(@Context ContainerRequestContext crc, @QueryParam("userIdentifier") String userIdentifier) { + String noMsgResultsFound = BundleUtil.getStringFromBundle("dataretrieverAPI.noMsgResultsFound"); + if ((session.getUser() != null) && (session.getUser().isAuthenticated())) { + authUser = (AuthenticatedUser) session.getUser(); + } else { + try { + authUser = getRequestAuthenticatedUserOrDie(crc); + } catch (WrappedResponse e) { + return this.getJSONErrorString( + BundleUtil.getStringFromBundle("dataretrieverAPI.authentication.required"), + BundleUtil.getStringFromBundle("dataretrieverAPI.authentication.required.opt") + ); + } + } + // If the user is a superuser, see if a userIdentifier has been specified and use that instead + AuthenticatedUser searchUser = null; + if ((authUser.isSuperuser()) && (userIdentifier != null) && (!userIdentifier.isEmpty())) { + searchUser = getUserFromIdentifier(userIdentifier); + if (searchUser != null) { + authUser = searchUser; + } else { + return this.getJSONErrorString( + BundleUtil.getStringFromBundle("dataretrieverAPI.user.not.found", Arrays.asList(userIdentifier)), + null); + } + } + roleList = dataverseRoleService.findAll(); + rolePermissionHelper = new DataverseRolePermissionHelper(roleList); + + // --------------------------------- + // (1) Initialize filterParams and check for Errors + // --------------------------------- + DataverseRequest dataverseRequest = createDataverseRequest(authUser); + + MyDataFilterParams filterParams = new MyDataFilterParams(dataverseRequest, Arrays.asList(DvObject.DType.Dataverse), MyDataFilterParams.allPublishedStates, Arrays.asList(1L, 3L, 5L, 7L), null, null); + if (filterParams.hasError()){ + return this.getJSONErrorString(filterParams.getErrorMessage(), filterParams.getErrorMessage()); + } + + // --------------------------------- + // (2) Initialize MyDataFinder and check for Errors + // --------------------------------- + myDataFinder = new MyDataFinder(rolePermissionHelper, + roleAssigneeService, + dvObjectServiceBean, + groupService); + this.myDataFinder.runFindDataSteps(filterParams); + if (myDataFinder.hasError()){ + return this.getJSONErrorString(myDataFinder.getErrorMessage(), myDataFinder.getErrorMessage()); + } + + // --------------------------------- + // (3) Make Solr Query + // --------------------------------- + int paginationStart = 0; + int totalCount; + List collections = new ArrayList<>(); + JsonArrayBuilder jsonDataArray = Json.createArrayBuilder(); + + List filterQueries = this.myDataFinder.getSolrFilterQueries(); + if (filterQueries==null){ + logger.fine("No ids found for this search"); + return this.getJSONErrorString(noMsgResultsFound, null); + } + try { + do { + solrQueryResponse = searchService.getDefaultSearchService().search( + dataverseRequest, + null, // subtree, default it to Dataverse for now + filterParams.getSearchTerm(), //"*", // + filterQueries,//filterQueries, + SearchFields.RELEASE_OR_CREATE_DATE, SortBy.DESCENDING, + paginationStart, //paginationStart, + true, // dataRelatedToMe + SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE //10 // SearchFields.NUM_SOLR_DOCS_TO_RETRIEVE + ); + if (this.solrQueryResponse.getNumResultsFound() == 0) { + return this.getJSONErrorString(noMsgResultsFound, null); + } + totalCount = solrQueryResponse.getNumResultsFound().intValue(); + + for (SolrSearchResult result : solrQueryResponse.getSolrSearchResults()) { + if (result.getEntity() instanceof Dataverse) { + collections.add((Dataverse) result.getEntity()); + } + paginationStart++; + } + + } while (totalCount > paginationStart); + + } catch (SearchException ex) { + solrQueryResponse = null; + logger.severe("Solr SearchException: " + ex.getMessage()); + } + for (Dataverse dv : collections) { + jsonDataArray.add(JsonPrinter.json(dv)); + } + + return jsonDataArray.build().toString(); + } + private JsonObjectBuilder getDvObjectTypeCounts(SolrQueryResponse solrResponse) { if (solrQueryResponse == null) { diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index 2bd0e350e2d..9e818d97b65 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -30,7 +30,7 @@ public static void setUpClass() { } @Test - public void testRetrieveMyDataAsJsonString() { + public void testRetrieveMyDataAsJsonString() throws InterruptedException { // Call with bad API token ArrayList emptyRoleIdsList = new ArrayList<>(); Response badApiTokenResponse = UtilIT.retrieveMyDataAsJsonString("bad-token", "dummy-user-identifier", emptyRoleIdsList); @@ -91,30 +91,28 @@ public void testRetrieveMyDataAsJsonString() { assertEquals(1, jsonPathOneDataverse.getInt("data.total_count")); assertEquals(dataverseAlias, jsonPathOneDataverse.getString("data.items[0].name")); - //url = "/api/mydata/retrieve?role_ids=1&role_ids=3&role_ids=5&role_ids=7&dvobject_types=Dataverse&start=#{start}&per_page=#{per_page}&published_states=Published&published_states=Unpublished" + // Use retrieve mydata endpoint to get a list of collections that this user can add Datasets to createDataverseResponse = UtilIT.createRandomDataverse(superUserApiToken); createDataverseResponse.prettyPrint(); String dataverseAlias2 = UtilIT.getAliasFromResponse(createDataverseResponse); - //UtilIT.publishDataverseViaNativeApi(dataverseAlias2, superUserApiToken); - UtilIT.grantRoleOnDataverse(dataverseAlias2, DataverseRole.FULL_CONTRIBUTOR.toString(), + // Add Curator role so this collection will show up in the list + UtilIT.grantRoleOnDataverse(dataverseAlias2, DataverseRole.CURATOR.toString(), "@" + normalUserUsername, superUserApiToken); - createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias2, normalUserApiToken); - createDatasetResponse.prettyPrint(); - assertEquals(201, createDatasetResponse.getStatusCode()); - Integer datasetId2 = UtilIT.getDatasetIdFromResponse(createDatasetResponse); - UtilIT.sleepForReindex(datasetId2.toString(), normalUserApiToken, 4); + // Sleep for indexing + Thread.sleep(4000); + + Response retrieveMyDataAsJsonResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", Arrays.asList(1L, 3L , 5L, 7L), Arrays.asList(DvObject.DType.Dataverse)); + retrieveMyDataAsJsonResponse.prettyPrint(); + JsonPath jsonPath = retrieveMyDataAsJsonResponse.getBody().jsonPath(); + assertEquals(2, jsonPath.getInt("data.total_count")); - Response response = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", Arrays.asList(3L , 5L, 7L), Arrays.asList(DvObject.DType.Dataverse), false); - response.prettyPrint(); - assertEquals(2, jsonPathOneDataverse.getInt("data.total_count")); + retrieveMyDataAsJsonResponse = UtilIT.retrieveMyCollectionList(normalUserApiToken, ""); + retrieveMyDataAsJsonResponse.prettyPrint(); // Clean up Response deleteDatasetResponse = UtilIT.deleteDatasetViaNativeApi(datasetId, normalUserApiToken); deleteDatasetResponse.prettyPrint(); assertEquals(200, deleteDatasetResponse.getStatusCode()); - deleteDatasetResponse = UtilIT.deleteDatasetViaNativeApi(datasetId2, normalUserApiToken); - deleteDatasetResponse.prettyPrint(); - assertEquals(200, deleteDatasetResponse.getStatusCode()); Response deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias, normalUserApiToken); deleteDataverseResponse.prettyPrint(); @@ -128,6 +126,58 @@ public void testRetrieveMyDataAsJsonString() { assertEquals(200, deleteUserResponse.getStatusCode()); } + // Test getting a list of collections that the user can add datasets to + @Test + public void testRetrieveMyDataCollections() throws InterruptedException { + // Create User1 + Response createUserResponse = UtilIT.createRandomUser(); + Response makeSuperUserResponse = UtilIT.makeSuperUser(UtilIT.getUsernameFromResponse(createUserResponse)); + assertEquals(OK.getStatusCode(), makeSuperUserResponse.getStatusCode()); + String superUserUsername = UtilIT.getUsernameFromResponse(createUserResponse); + String superUserApiToken = UtilIT.getApiTokenFromResponse(createUserResponse); + + // Create User2 + Response createNormalUserResponse = UtilIT.createRandomUser(); + String normalUserUsername = UtilIT.getUsernameFromResponse(createNormalUserResponse); + String normalUserApiToken = UtilIT.getApiTokenFromResponse(createNormalUserResponse); + + // User1 creates a number of Dataverses and adds a role allowing User2 access + List dataverses = new ArrayList<>(); + for (int i = 0; i <15; i++) { + Response createDataverseResponse = UtilIT.createRandomDataverse(superUserApiToken); + String alias = UtilIT.getAliasFromResponse(createDataverseResponse); + //createDataverseResponse.prettyPrint(); + dataverses.add(alias); + UtilIT.grantRoleOnDataverse(alias, DataverseRole.CURATOR.toString(), + "@" + normalUserUsername, superUserApiToken); + } + // User2 adds their own Dataverse + Response createDataverseResponse = UtilIT.createRandomDataverse(normalUserApiToken); + String alias = UtilIT.getAliasFromResponse(createDataverseResponse); + dataverses.add(alias); + // Sleep for indexing + Thread.sleep(4000); + + // User2 gets the list of Dataverses/Collections it has access to + Response retrieveMyDataAsJsonResponse = UtilIT.retrieveMyCollectionList(normalUserApiToken, ""); + retrieveMyDataAsJsonResponse.prettyPrint(); + // The count should show the list size to be User1's + User2's Dataverse count + JsonPath jsonPath = retrieveMyDataAsJsonResponse.getBody().jsonPath(); + assertEquals(dataverses.size(), jsonPath.getList("").size()); + + // Clean up + dataverses.forEach(dv -> { + Response deleteDataverseResponse = UtilIT.deleteDataverse(dv, superUserApiToken); + assertEquals(200, deleteDataverseResponse.getStatusCode()); + }); + Response deleteUserResponse = UtilIT.deleteUser(normalUserUsername); + deleteUserResponse.prettyPrint(); + assertEquals(200, deleteUserResponse.getStatusCode()); + deleteUserResponse = UtilIT.deleteUser(superUserUsername); + deleteUserResponse.prettyPrint(); + assertEquals(200, deleteUserResponse.getStatusCode()); + } + @Test public void testRetrieveMyDataAsJsonStringSortOrder() { // Create superuser 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 14cc758e50a..8ff74625988 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4119,17 +4119,22 @@ static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifie .get("/api/mydata/retrieve?userIdentifier=" + userIdentifier); return response; } - static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifier, List roleIds, List objectTypes, boolean myDataOnly) { + static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifier, List roleIds, List objectTypes) { Response response = given() .header(API_TOKEN_HTTP_HEADER, apiToken) .contentType("application/json; charset=utf-8") .queryParam("role_ids", roleIds) .queryParam("dvobject_types", objectTypes) .queryParam("published_states", MyDataFilterParams.allPublishedStates) - .queryParam("my_data", myDataOnly) .get("/api/mydata/retrieve?userIdentifier=" + userIdentifier); return response; } + static Response retrieveMyCollectionList(String apiToken, String userIdentifier) { + Response response = given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .get("/api/mydata/retrieve/collectionList?userIdentifier=" + userIdentifier); + return response; + } static Response createSignedUrl(String apiToken, String apiPath, String username) { Response response = given() From 6004421908d6f81b730f8768f3ba200218e343ec Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:12:23 -0400 Subject: [PATCH 05/30] update docs --- ...ve-collections-a-user-can-create-datasets-in.md | 5 +++++ doc/sphinx-guides/source/api/native-api.rst | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md diff --git a/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md b/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md new file mode 100644 index 00000000000..26898d09412 --- /dev/null +++ b/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md @@ -0,0 +1,5 @@ +### New API to retrieve a list of collections that an authenticated user can create a dataset in + +The API GET /api/mydata/retrieve/collectionList will return all the dataverse objects that the user select from in the SPA UI + +See also [the guides](https://guides.dataverse.org/en/latest/api/native-api.html#mydata) and #11525. diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 7ca91edf51d..0a12440cd64 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -7752,3 +7752,17 @@ Parameters: ``per_page`` Number of results returned per page. +MyData Collection List +---------------------- + +The MyData Collection List API is used to get a list of the collections an authenticated user can create a Dataset in. + +A curl example listing collections + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/mydata/retrieve/collectionList" + From d3d835815997b9b0e35b62b882975bf10031d608 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:48:19 -0400 Subject: [PATCH 06/30] new endpoint --- doc/sphinx-guides/source/api/native-api.rst | 4 +- .../iq/dataverse/mydata/DataRetrieverAPI.java | 14 ++--- .../iq/dataverse/util/json/JsonPrinter.java | 14 ++++- .../iq/dataverse/api/DataRetrieverApiIT.java | 57 +++++++++++++++---- .../edu/harvard/iq/dataverse/api/UtilIT.java | 20 +++++-- 5 files changed, 83 insertions(+), 26 deletions(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 0a12440cd64..478add8f165 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -7756,6 +7756,8 @@ MyData Collection List ---------------------- The MyData Collection List API is used to get a list of the collections an authenticated user can create a Dataset in. +Param identifier={userName} is used by a superuser to get the collections for a specific user. +Param includeDescriptions=true will include the dataverse description in the list for each dataverse. False of missing will only include the name and alias of the dataverse. A curl example listing collections @@ -7764,5 +7766,5 @@ A curl example listing collections export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx export SERVER_URL=https://demo.dataverse.org - curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/mydata/retrieve/collectionList" + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/mydata/retrieve/collectionList?includeDescriptions=true&identifier=" diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 8e54f7dd047..580c11e9863 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -450,7 +450,8 @@ public String retrieveMyDataAsJsonString( @AuthRequired @Path(retrieveDataPartialAPIPath + "/collectionList") @Produces({"application/json"}) - public String retrieveMyCollectionList(@Context ContainerRequestContext crc, @QueryParam("userIdentifier") String userIdentifier) { + public String retrieveMyCollectionList(@Context ContainerRequestContext crc, @QueryParam("userIdentifier") String userIdentifier, + @QueryParam("includeDescriptions") Boolean includeDescriptions) { String noMsgResultsFound = BundleUtil.getStringFromBundle("dataretrieverAPI.noMsgResultsFound"); if ((session.getUser() != null) && (session.getUser().isAuthenticated())) { authUser = (AuthenticatedUser) session.getUser(); @@ -478,13 +479,14 @@ public String retrieveMyCollectionList(@Context ContainerRequestContext crc, @Qu } roleList = dataverseRoleService.findAll(); rolePermissionHelper = new DataverseRolePermissionHelper(roleList); + List roleIdList = Arrays.asList(1L, 3L, 5L, 7L); // --------------------------------- // (1) Initialize filterParams and check for Errors // --------------------------------- DataverseRequest dataverseRequest = createDataverseRequest(authUser); - MyDataFilterParams filterParams = new MyDataFilterParams(dataverseRequest, Arrays.asList(DvObject.DType.Dataverse), MyDataFilterParams.allPublishedStates, Arrays.asList(1L, 3L, 5L, 7L), null, null); + MyDataFilterParams filterParams = new MyDataFilterParams(dataverseRequest, Arrays.asList(DvObject.DType.Dataverse), MyDataFilterParams.allPublishedStates, roleIdList, null, null); if (filterParams.hasError()){ return this.getJSONErrorString(filterParams.getErrorMessage(), filterParams.getErrorMessage()); } @@ -507,7 +509,6 @@ public String retrieveMyCollectionList(@Context ContainerRequestContext crc, @Qu int paginationStart = 0; int totalCount; List collections = new ArrayList<>(); - JsonArrayBuilder jsonDataArray = Json.createArrayBuilder(); List filterQueries = this.myDataFinder.getSolrFilterQueries(); if (filterQueries==null){ @@ -544,11 +545,10 @@ public String retrieveMyCollectionList(@Context ContainerRequestContext crc, @Qu solrQueryResponse = null; logger.severe("Solr SearchException: " + ex.getMessage()); } - for (Dataverse dv : collections) { - jsonDataArray.add(JsonPrinter.json(dv)); - } - return jsonDataArray.build().toString(); + JsonArrayBuilder jsonArrayBuilder = JsonPrinter.json(collections, includeDescriptions); + + return jsonArrayBuilder.build().toString(); } private JsonObjectBuilder getDvObjectTypeCounts(SolrQueryResponse solrResponse) { diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 592a893083c..85bc117daa7 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -322,7 +322,19 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re return bld; } - + public static JsonArrayBuilder json(List collections, Boolean includeDescription) { + JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder(); + for (Dataverse dataverse : collections) { + NullSafeJsonBuilder jsonObject = NullSafeJsonBuilder.jsonObjectBuilder(); + jsonObject.add("name", dataverse.getName()); + jsonObject.add("alias", dataverse.getAlias()); + if (includeDescription != null && includeDescription) { + jsonObject.add("description", dataverse.getDescription()); + } + jsonArrayBuilder.add(jsonObject); + } + return jsonArrayBuilder; + } public static JsonArrayBuilder json(List dataverseContacts) { JsonArrayBuilder jsonArrayOfContacts = Json.createArrayBuilder(); for (DataverseContact dataverseContact : dataverseContacts) { diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index 9e818d97b65..728e0f7d77a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -18,6 +18,7 @@ import static jakarta.ws.rs.core.Response.Status.OK; import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; public class DataRetrieverApiIT { @@ -106,7 +107,7 @@ public void testRetrieveMyDataAsJsonString() throws InterruptedException { JsonPath jsonPath = retrieveMyDataAsJsonResponse.getBody().jsonPath(); assertEquals(2, jsonPath.getInt("data.total_count")); - retrieveMyDataAsJsonResponse = UtilIT.retrieveMyCollectionList(normalUserApiToken, ""); + retrieveMyDataAsJsonResponse = UtilIT.retrieveMyCollectionList(normalUserApiToken, null, null); retrieveMyDataAsJsonResponse.prettyPrint(); // Clean up @@ -137,40 +138,72 @@ public void testRetrieveMyDataCollections() throws InterruptedException { String superUserApiToken = UtilIT.getApiTokenFromResponse(createUserResponse); // Create User2 - Response createNormalUserResponse = UtilIT.createRandomUser(); - String normalUserUsername = UtilIT.getUsernameFromResponse(createNormalUserResponse); - String normalUserApiToken = UtilIT.getApiTokenFromResponse(createNormalUserResponse); + createUserResponse = UtilIT.createRandomUser(); + String User2Username = UtilIT.getUsernameFromResponse(createUserResponse); + String User2ApiToken = UtilIT.getApiTokenFromResponse(createUserResponse); + // Create User3 + createUserResponse = UtilIT.createRandomUser(); + String User3Username = UtilIT.getUsernameFromResponse(createUserResponse); + String User3ApiToken = UtilIT.getApiTokenFromResponse(createUserResponse); // User1 creates a number of Dataverses and adds a role allowing User2 access List dataverses = new ArrayList<>(); - for (int i = 0; i <15; i++) { + int user1DataverseCount = 15; + for (int i = 0; i { Response deleteDataverseResponse = UtilIT.deleteDataverse(dv, superUserApiToken); assertEquals(200, deleteDataverseResponse.getStatusCode()); }); - Response deleteUserResponse = UtilIT.deleteUser(normalUserUsername); + Response deleteUserResponse = UtilIT.deleteUser(User2Username); + deleteUserResponse.prettyPrint(); + assertEquals(200, deleteUserResponse.getStatusCode()); + deleteUserResponse = UtilIT.deleteUser(User3Username); deleteUserResponse.prettyPrint(); assertEquals(200, deleteUserResponse.getStatusCode()); deleteUserResponse = UtilIT.deleteUser(superUserUsername); 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 8ff74625988..889785c4a83 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -408,6 +408,7 @@ static Response createSubDataverse(String alias, String category, String apiToke JsonObjectBuilder objectBuilder = Json.createObjectBuilder() .add("alias", alias) .add("name", alias) + .add("description", "Description for " + alias) .add("dataverseContacts", contactArrayBuilder) .add("dataverseSubjects", subjectArrayBuilder) // don't send "dataverseType" if category is null, must be a better way @@ -4129,11 +4130,20 @@ static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifie .get("/api/mydata/retrieve?userIdentifier=" + userIdentifier); return response; } - static Response retrieveMyCollectionList(String apiToken, String userIdentifier) { - Response response = given() - .header(API_TOKEN_HTTP_HEADER, apiToken) - .get("/api/mydata/retrieve/collectionList?userIdentifier=" + userIdentifier); - return response; + + static Response retrieveMyCollectionList(String apiToken, String userIdentifier, Boolean includeDescriptions) { + RequestSpecification requestSpecification = given(); + if (apiToken != null) { + requestSpecification.header(API_TOKEN_HTTP_HEADER, apiToken); + } + if (userIdentifier != null) { + requestSpecification.queryParam("userIdentifier", userIdentifier); + } + if (includeDescriptions != null) { + requestSpecification.queryParam("includeDescriptions", includeDescriptions); + } + + return requestSpecification.get("/api/mydata/retrieve/collectionList"); } static Response createSignedUrl(String apiToken, String apiPath, String username) { From ec0bdcfab81d169e8090a4df84722833b899da1d Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:15:15 -0400 Subject: [PATCH 07/30] switch to GetUserPermittedCollectionsCommand --- .../GetUserPermittedCollectionsCommand.java | 23 +++- .../iq/dataverse/mydata/DataRetrieverAPI.java | 89 ++------------ .../iq/dataverse/util/json/JsonPrinter.java | 10 +- .../iq/dataverse/api/DataRetrieverApiIT.java | 116 ++++++++++-------- .../edu/harvard/iq/dataverse/api/UtilIT.java | 6 +- 5 files changed, 96 insertions(+), 148 deletions(-) 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 index c4888c8c99c..499472df849 100644 --- 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 @@ -3,7 +3,6 @@ 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; @@ -18,6 +17,7 @@ import java.util.logging.Logger; import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json; +import static edu.harvard.iq.dataverse.util.json.JsonPrinter.jsonArray; @RequiredPermissions({}) public class GetUserPermittedCollectionsCommand extends AbstractCommand { @@ -26,11 +26,17 @@ public class GetUserPermittedCollectionsCommand extends AbstractCommand 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); + if (minimalOutput != null && minimalOutput) { + job.add("items", jsonArray(collections)); + } else { + JsonArrayBuilder jab = Json.createArrayBuilder(); + for (Dataverse dv : collections) { + jab.add(json(dv)); + } + job.add("items", jab); + } return job; } return null; diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 580c11e9863..beca1ebf9f0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -5,6 +5,8 @@ import edu.harvard.iq.dataverse.*; import edu.harvard.iq.dataverse.api.auth.AuthRequired; +import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.engine.command.impl.GetUserPermittedCollectionsCommand; import edu.harvard.iq.dataverse.search.SolrQueryResponse; import edu.harvard.iq.dataverse.search.SolrSearchResult; import edu.harvard.iq.dataverse.api.AbstractApiBean; @@ -23,7 +25,6 @@ import java.util.*; import java.util.logging.Logger; -import edu.harvard.iq.dataverse.util.json.JsonPrinter; import jakarta.ejb.EJB; import jakarta.inject.Inject; import jakarta.json.*; @@ -449,10 +450,8 @@ public String retrieveMyDataAsJsonString( @GET @AuthRequired @Path(retrieveDataPartialAPIPath + "/collectionList") - @Produces({"application/json"}) - public String retrieveMyCollectionList(@Context ContainerRequestContext crc, @QueryParam("userIdentifier") String userIdentifier, - @QueryParam("includeDescriptions") Boolean includeDescriptions) { - String noMsgResultsFound = BundleUtil.getStringFromBundle("dataretrieverAPI.noMsgResultsFound"); + @Produces("application/json") + public String retrieveMyCollectionList(@Context ContainerRequestContext crc, @QueryParam("userIdentifier") String userIdentifier) { if ((session.getUser() != null) && (session.getUser().isAuthenticated())) { authUser = (AuthenticatedUser) session.getUser(); } else { @@ -466,89 +465,21 @@ public String retrieveMyCollectionList(@Context ContainerRequestContext crc, @Qu } } // If the user is a superuser, see if a userIdentifier has been specified and use that instead - AuthenticatedUser searchUser = null; + AuthenticatedUser searchUser = authUser; if ((authUser.isSuperuser()) && (userIdentifier != null) && (!userIdentifier.isEmpty())) { searchUser = getUserFromIdentifier(userIdentifier); - if (searchUser != null) { - authUser = searchUser; - } else { + if (searchUser == null) { return this.getJSONErrorString( BundleUtil.getStringFromBundle("dataretrieverAPI.user.not.found", Arrays.asList(userIdentifier)), null); } } - roleList = dataverseRoleService.findAll(); - rolePermissionHelper = new DataverseRolePermissionHelper(roleList); - List roleIdList = Arrays.asList(1L, 3L, 5L, 7L); - - // --------------------------------- - // (1) Initialize filterParams and check for Errors - // --------------------------------- - DataverseRequest dataverseRequest = createDataverseRequest(authUser); - - MyDataFilterParams filterParams = new MyDataFilterParams(dataverseRequest, Arrays.asList(DvObject.DType.Dataverse), MyDataFilterParams.allPublishedStates, roleIdList, null, null); - if (filterParams.hasError()){ - return this.getJSONErrorString(filterParams.getErrorMessage(), filterParams.getErrorMessage()); - } - - // --------------------------------- - // (2) Initialize MyDataFinder and check for Errors - // --------------------------------- - myDataFinder = new MyDataFinder(rolePermissionHelper, - roleAssigneeService, - dvObjectServiceBean, - groupService); - this.myDataFinder.runFindDataSteps(filterParams); - if (myDataFinder.hasError()){ - return this.getJSONErrorString(myDataFinder.getErrorMessage(), myDataFinder.getErrorMessage()); - } - - // --------------------------------- - // (3) Make Solr Query - // --------------------------------- - int paginationStart = 0; - int totalCount; - List collections = new ArrayList<>(); - - List filterQueries = this.myDataFinder.getSolrFilterQueries(); - if (filterQueries==null){ - logger.fine("No ids found for this search"); - return this.getJSONErrorString(noMsgResultsFound, null); - } try { - do { - solrQueryResponse = searchService.getDefaultSearchService().search( - dataverseRequest, - null, // subtree, default it to Dataverse for now - filterParams.getSearchTerm(), //"*", // - filterQueries,//filterQueries, - SearchFields.RELEASE_OR_CREATE_DATE, SortBy.DESCENDING, - paginationStart, //paginationStart, - true, // dataRelatedToMe - SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE //10 // SearchFields.NUM_SOLR_DOCS_TO_RETRIEVE - ); - if (this.solrQueryResponse.getNumResultsFound() == 0) { - return this.getJSONErrorString(noMsgResultsFound, null); - } - totalCount = solrQueryResponse.getNumResultsFound().intValue(); - - for (SolrSearchResult result : solrQueryResponse.getSolrSearchResults()) { - if (result.getEntity() instanceof Dataverse) { - collections.add((Dataverse) result.getEntity()); - } - paginationStart++; - } - - } while (totalCount > paginationStart); - - } catch (SearchException ex) { - solrQueryResponse = null; - logger.severe("Solr SearchException: " + ex.getMessage()); + JsonObjectBuilder jsonObjBuilder = execCommand(new GetUserPermittedCollectionsCommand(createDataverseRequest(getRequestUser(crc)), searchUser, Permission.AddDataset.name(), true)); + return jsonObjBuilder.build().toString(); + } catch (WrappedResponse ex) { + return null; } - - JsonArrayBuilder jsonArrayBuilder = JsonPrinter.json(collections, includeDescriptions); - - return jsonArrayBuilder.build().toString(); } private JsonObjectBuilder getDvObjectTypeCounts(SolrQueryResponse solrResponse) { diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 85bc117daa7..34c480d0e9b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -322,19 +322,19 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re return bld; } - public static JsonArrayBuilder json(List collections, Boolean includeDescription) { + + // For UI drop down list. Only needing display name and identifier + public static JsonArrayBuilder jsonArray(List collections) { JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder(); for (Dataverse dataverse : collections) { NullSafeJsonBuilder jsonObject = NullSafeJsonBuilder.jsonObjectBuilder(); - jsonObject.add("name", dataverse.getName()); + jsonObject.add("name", dataverse.getDisplayName()); jsonObject.add("alias", dataverse.getAlias()); - if (includeDescription != null && includeDescription) { - jsonObject.add("description", dataverse.getDescription()); - } jsonArrayBuilder.add(jsonObject); } return jsonArrayBuilder; } + public static JsonArrayBuilder json(List dataverseContacts) { JsonArrayBuilder jsonArrayOfContacts = Json.createArrayBuilder(); for (DataverseContact dataverseContact : dataverseContacts) { diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index 728e0f7d77a..4c1d2777f49 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class DataRetrieverApiIT { @@ -31,7 +32,7 @@ public static void setUpClass() { } @Test - public void testRetrieveMyDataAsJsonString() throws InterruptedException { + public void testRetrieveMyDataAsJsonString() { // Call with bad API token ArrayList emptyRoleIdsList = new ArrayList<>(); Response badApiTokenResponse = UtilIT.retrieveMyDataAsJsonString("bad-token", "dummy-user-identifier", emptyRoleIdsList); @@ -92,24 +93,6 @@ public void testRetrieveMyDataAsJsonString() throws InterruptedException { assertEquals(1, jsonPathOneDataverse.getInt("data.total_count")); assertEquals(dataverseAlias, jsonPathOneDataverse.getString("data.items[0].name")); - // Use retrieve mydata endpoint to get a list of collections that this user can add Datasets to - createDataverseResponse = UtilIT.createRandomDataverse(superUserApiToken); - createDataverseResponse.prettyPrint(); - String dataverseAlias2 = UtilIT.getAliasFromResponse(createDataverseResponse); - // Add Curator role so this collection will show up in the list - UtilIT.grantRoleOnDataverse(dataverseAlias2, DataverseRole.CURATOR.toString(), - "@" + normalUserUsername, superUserApiToken); - // Sleep for indexing - Thread.sleep(4000); - - Response retrieveMyDataAsJsonResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", Arrays.asList(1L, 3L , 5L, 7L), Arrays.asList(DvObject.DType.Dataverse)); - retrieveMyDataAsJsonResponse.prettyPrint(); - JsonPath jsonPath = retrieveMyDataAsJsonResponse.getBody().jsonPath(); - assertEquals(2, jsonPath.getInt("data.total_count")); - - retrieveMyDataAsJsonResponse = UtilIT.retrieveMyCollectionList(normalUserApiToken, null, null); - retrieveMyDataAsJsonResponse.prettyPrint(); - // Clean up Response deleteDatasetResponse = UtilIT.deleteDatasetViaNativeApi(datasetId, normalUserApiToken); deleteDatasetResponse.prettyPrint(); @@ -118,9 +101,6 @@ public void testRetrieveMyDataAsJsonString() throws InterruptedException { Response deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias, normalUserApiToken); deleteDataverseResponse.prettyPrint(); assertEquals(200, deleteDataverseResponse.getStatusCode()); - deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias2, superUserApiToken); - deleteDataverseResponse.prettyPrint(); - assertEquals(200, deleteDataverseResponse.getStatusCode()); Response deleteUserResponse = UtilIT.deleteUser(normalUserUsername); deleteUserResponse.prettyPrint(); @@ -130,13 +110,21 @@ public void testRetrieveMyDataAsJsonString() throws InterruptedException { // Test getting a list of collections that the user can add datasets to @Test public void testRetrieveMyDataCollections() throws InterruptedException { - // Create User1 + int rootCount = 1; // everyone has access to this dataverse + List items; + Response createDataverseResponse; + Response retrieveMyCollectionListResponse; + // Create Superuser Response createUserResponse = UtilIT.createRandomUser(); Response makeSuperUserResponse = UtilIT.makeSuperUser(UtilIT.getUsernameFromResponse(createUserResponse)); assertEquals(OK.getStatusCode(), makeSuperUserResponse.getStatusCode()); String superUserUsername = UtilIT.getUsernameFromResponse(createUserResponse); String superUserApiToken = UtilIT.getApiTokenFromResponse(createUserResponse); - + // Create User1 + createUserResponse = UtilIT.createRandomUser(); + assertEquals(OK.getStatusCode(), makeSuperUserResponse.getStatusCode()); + String User1Username = UtilIT.getUsernameFromResponse(createUserResponse); + String User1ApiToken = UtilIT.getApiTokenFromResponse(createUserResponse); // Create User2 createUserResponse = UtilIT.createRandomUser(); String User2Username = UtilIT.getUsernameFromResponse(createUserResponse); @@ -146,53 +134,73 @@ public void testRetrieveMyDataCollections() throws InterruptedException { String User3Username = UtilIT.getUsernameFromResponse(createUserResponse); String User3ApiToken = UtilIT.getApiTokenFromResponse(createUserResponse); - // User1 creates a number of Dataverses and adds a role allowing User2 access + // User1 creates 15 Dataverses and adds a role to each allowing User2 access List dataverses = new ArrayList<>(); int user1DataverseCount = 15; - for (int i = 0; i Date: Tue, 29 Jul 2025 10:22:28 -0400 Subject: [PATCH 08/30] fix doc --- doc/sphinx-guides/source/api/native-api.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 478add8f165..931ebb3274d 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -7757,7 +7757,6 @@ MyData Collection List The MyData Collection List API is used to get a list of the collections an authenticated user can create a Dataset in. Param identifier={userName} is used by a superuser to get the collections for a specific user. -Param includeDescriptions=true will include the dataverse description in the list for each dataverse. False of missing will only include the name and alias of the dataverse. A curl example listing collections @@ -7766,5 +7765,5 @@ A curl example listing collections export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx export SERVER_URL=https://demo.dataverse.org - curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/mydata/retrieve/collectionList?includeDescriptions=true&identifier=" + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/mydata/retrieve/collectionList?identifier=" From d4d2db33b4e6afadb4e9d405be2abf92c1a98f87 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:24:21 -0400 Subject: [PATCH 09/30] remove unused includes from test --- .../java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index 4c1d2777f49..4a4984a7b98 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -1,6 +1,5 @@ package edu.harvard.iq.dataverse.api; -import edu.harvard.iq.dataverse.DvObject; import io.restassured.RestAssured; import io.restassured.response.Response; import edu.harvard.iq.dataverse.api.auth.ApiKeyAuthMechanism; @@ -20,7 +19,6 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; public class DataRetrieverApiIT { From 639923aab3b3adb68837a51c708993a6c9a64d8b Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:26:43 -0400 Subject: [PATCH 10/30] remove unused method from test --- src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java | 10 ---------- 1 file changed, 10 deletions(-) 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 dafd0c82aae..6323ae5ec7a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4119,16 +4119,6 @@ static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifie .get("/api/mydata/retrieve?userIdentifier=" + userIdentifier); return response; } - static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifier, List roleIds, List objectTypes) { - Response response = given() - .header(API_TOKEN_HTTP_HEADER, apiToken) - .contentType("application/json; charset=utf-8") - .queryParam("role_ids", roleIds) - .queryParam("dvobject_types", objectTypes) - .queryParam("published_states", MyDataFilterParams.allPublishedStates) - .get("/api/mydata/retrieve?userIdentifier=" + userIdentifier); - return response; - } static Response retrieveMyCollectionList(String apiToken, String userIdentifier) { RequestSpecification requestSpecification = given(); From 9688bc054de4f25e615142f31af6d672a845c2c0 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:11:00 -0400 Subject: [PATCH 11/30] adding superuser bypass --- .../iq/dataverse/PermissionServiceBean.java | 19 ++++++++++++++----- .../iq/dataverse/api/DataRetrieverApiIT.java | 5 +++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index 2f727987537..343d6fddc33 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -100,6 +100,10 @@ public class PermissionServiceBean { @Inject DatasetVersionFilesServiceBean datasetVersionFilesServiceBean; + private static final String LIST_ALL_DATAVERSES_SUPERUSER_HAS_PERMISSION = """ + SELECT * FROM DATAVERSE + """; + private static final String LIST_ALL_DATAVERSES_USER_HAS_PERMISSION = """ WITH grouplist AS ( SELECT explicitgroup_authenticateduser.explicitgroup_id as id FROM explicitgroup_authenticateduser @@ -932,6 +936,7 @@ public List findPermittedCollections(DataverseRequest request, Authen String ipRangeSQL = "FALSE"; if (request != null && request.getAuthenticatedUser() != null + && !request.getAuthenticatedUser().isSuperuser() && request.getSourceAddress() != null && request.getAuthenticatedUser().getUserIdentifier().equalsIgnoreCase(user.getUserIdentifier())) { IpAddress ip = request.getSourceAddress(); @@ -954,11 +959,15 @@ public List findPermittedCollections(DataverseRequest request, Authen } } } - - String sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION - .replace("@USERID", String.valueOf(user.getId())) - .replace("@PERMISSIONBIT", String.valueOf(permissionBit)) - .replace("@IPRANGESQL", ipRangeSQL); + String sqlCode; + if (user.isSuperuser()) { + sqlCode = LIST_ALL_DATAVERSES_SUPERUSER_HAS_PERMISSION; + } else { + 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/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index 4a4984a7b98..8c6c05a032a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -19,6 +19,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class DataRetrieverApiIT { @@ -175,9 +176,9 @@ public void testRetrieveMyDataCollections() throws InterruptedException { // Superuser gets the list of Dataverses/Collections it has access to retrieveMyCollectionListResponse = UtilIT.retrieveMyCollectionList(superUserApiToken, null); retrieveMyCollectionListResponse.prettyPrint(); - // The count should show the list size to be only Root Dataverse count + // The count should show the list size of all Dataverses (including any Dataverses created by other tests) items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("items"); - assertEquals(rootCount, items.size()); + assertTrue(items.size() >= rootCount + user1DataverseCount + user2DataverseCount); // Superuser gets the list of Dataverses/Collections User1 has access to retrieveMyCollectionListResponse = UtilIT.retrieveMyCollectionList(superUserApiToken, User1Username); From ebbb223ec144de5d6fd9729bc4b38e5ef4525b49 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:21:35 -0400 Subject: [PATCH 12/30] adding id to json response --- .../java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 34c480d0e9b..2e7ae02ab09 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -328,6 +328,7 @@ public static JsonArrayBuilder jsonArray(List collections) { JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder(); for (Dataverse dataverse : collections) { NullSafeJsonBuilder jsonObject = NullSafeJsonBuilder.jsonObjectBuilder(); + jsonObject.add("id", dataverse.getId()); jsonObject.add("name", dataverse.getDisplayName()); jsonObject.add("alias", dataverse.getAlias()); jsonArrayBuilder.add(jsonObject); From 2b51bbab7032caa7e50ccc7f5b4d2cb3bca204f4 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Mon, 25 Aug 2025 15:28:55 -0400 Subject: [PATCH 13/30] Update doc/sphinx-guides/source/api/native-api.rst Co-authored-by: Guillermo Portas --- doc/sphinx-guides/source/api/native-api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index f360cf2f161..adc751fb740 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -7875,7 +7875,7 @@ MyData Collection List The MyData Collection List API is used to get a list of the collections an authenticated user can create a Dataset in. Param identifier={userName} is used by a superuser to get the collections for a specific user. -A curl example listing collections +A curl example listing collections: .. code-block:: bash From 65fb72c744709c89559fb39e87b7db896b29a939 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Mon, 25 Aug 2025 15:40:33 -0400 Subject: [PATCH 14/30] review comments and refactor --- ...llections-a-user-can-create-datasets-in.md | 2 +- .../iq/dataverse/PermissionServiceBean.java | 2 +- .../iq/dataverse/mydata/DataRetrieverAPI.java | 297 +++--------------- .../iq/dataverse/api/DataRetrieverApiIT.java | 79 ++--- 4 files changed, 87 insertions(+), 293 deletions(-) diff --git a/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md b/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md index 26898d09412..51099039826 100644 --- a/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md +++ b/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md @@ -1,5 +1,5 @@ ### New API to retrieve a list of collections that an authenticated user can create a dataset in -The API GET /api/mydata/retrieve/collectionList will return all the dataverse objects that the user select from in the SPA UI +The API GET /api/mydata/retrieve/collectionList will return all the dataverse objects that the user add to See also [the guides](https://guides.dataverse.org/en/latest/api/native-api.html#mydata) and #11525. diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index ee863f2dbb1..e00e303356e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -96,7 +96,7 @@ public class PermissionServiceBean { DatasetVersionFilesServiceBean datasetVersionFilesServiceBean; private static final String LIST_ALL_DATAVERSES_SUPERUSER_HAS_PERMISSION = """ - SELECT * FROM DATAVERSE + SELECT id, name, alias FROM DATAVERSE """; private static final String LIST_ALL_DATAVERSES_USER_HAS_PERMISSION = """ diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 593d2bf799e..55d5f8cf63c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -26,7 +26,6 @@ import java.util.logging.Logger; import jakarta.ejb.EJB; -import jakarta.inject.Inject; import jakarta.json.*; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -36,7 +35,7 @@ import jakarta.ws.rs.core.Context; import edu.harvard.iq.dataverse.util.BundleUtil; -import org.apache.commons.lang3.StringUtils; +import jakarta.ws.rs.core.Response; /** * @@ -55,9 +54,6 @@ public class DataRetrieverAPI extends AbstractApiBean { public static final String retrieveDataFullAPIPath = "/api/v1/mydata/retrieve"; private static final String retrieveDataPartialAPIPath = "retrieve"; - @Inject - DataverseSession session; - @EJB DataverseRoleServiceBean dataverseRoleService; @EJB @@ -70,22 +66,15 @@ public class DataRetrieverAPI extends AbstractApiBean { AuthenticationServiceBean authenticationService; @EJB DataverseServiceBean dataverseService; - //@EJB - //MyDataQueryHelperServiceBean myDataQueryHelperServiceBean; @EJB GroupServiceBean groupService; - @EJB - DatasetServiceBean datasetService; private List roleList; private DataverseRolePermissionHelper rolePermissionHelper; private MyDataFinder myDataFinder; private SolrQueryResponse solrQueryResponse; private AuthenticatedUser authUser = null; - - public static final String JSON_SUCCESS_FIELD_NAME = "success"; - public static final String JSON_ERROR_MSG_FIELD_NAME = "error_message"; - public static final String JSON_DATA_FIELD_NAME = "data"; + private AuthenticatedUser searchUser = null; /** * Constructor @@ -94,49 +83,7 @@ public class DataRetrieverAPI extends AbstractApiBean { public DataRetrieverAPI(){ } - - public String getRetrieveDataFullAPIPath(){ - return DataRetrieverAPI.retrieveDataFullAPIPath; - } - - public Pager getRandomPagerPager(Integer selectedPage){ - if (selectedPage == null){ - selectedPage = 1; - } - - int itemsPerPage = 10; - int numResults = 108;//randInt(1,200); - int numPages = numResults / itemsPerPage; - if ((numResults % itemsPerPage) > 0){ - numPages++; - } - int chosenPage = 1; - if ((selectedPage > numPages)||(selectedPage < 1)){ - chosenPage = 1; - }else{ - chosenPage = selectedPage; - } - //int chosenPage = max(randInt(0, numPages), 1); - return new Pager(numResults, itemsPerPage, chosenPage); - - } - - /* - @Path("test-it2") - @GET - @Produces({"application/json"}) - public String retrieveTestPager(@QueryParam("selectedPage") int selectedPage){ - - return this.getRandomPagerPager(selectedPage).asJSONString(); - } - */ - //private String getUserIdentifier() - - - public boolean isSuperuser(){ - return (session.getUser() != null) && session.getUser().isSuperuser(); - } - + private AuthenticatedUser getUserFromIdentifier(String userIdentifier){ if ((userIdentifier==null)||(userIdentifier.isEmpty())){ @@ -144,121 +91,24 @@ private AuthenticatedUser getUserFromIdentifier(String userIdentifier){ } return authenticationService.getAuthenticatedUser(userIdentifier); } - - public Map getTotalCountsFromSolrAsJavaMap(DataverseRequest dataverseRequest, MyDataFinder myDataFinder ){ - //msgt("getTotalCountsFromSolrAsJavaMap: " + searchUser.getIdentifier()); - SolrQueryResponse solrQueryResponseForCounts = getTotalCountsFromSolr(dataverseRequest, myDataFinder); - if (solrQueryResponseForCounts == null){ - logger.severe("DataRetrieverAPI.getTotalCountsFromSolrAsJSON: solrQueryResponseForCounts should not be null"); - return null; - } - return solrQueryResponseForCounts.getDvObjectCounts(); - } - - public JsonObjectBuilder getTotalCountsFromSolrAsJSON(DataverseRequest dataverseRequest, MyDataFinder myDataFinder ){ - - SolrQueryResponse solrQueryResponseForCounts = getTotalCountsFromSolr(dataverseRequest, myDataFinder); - if (solrQueryResponseForCounts == null){ - logger.severe("DataRetrieverAPI.getTotalCountsFromSolrAsJSON: solrQueryResponseForCounts should not be null"); - return null; - } - return solrQueryResponseForCounts.getDvObjectCountsAsJSON(); - } - - - private SolrQueryResponse getTotalCountsFromSolr(DataverseRequest dataverseRequest, MyDataFinder myDataFinder){ - //msgt("getTotalCountsFromSolr: " + searchUser.getIdentifier()); - if (myDataFinder == null){ - throw new NullPointerException("myDataFinder cannot be null"); - } - if (dataverseRequest == null){ - throw new NullPointerException("dataverseRequest cannot be null"); - } - - // ------------------------------------------------------- - // Create new filter params that only check by the User - // ------------------------------------------------------- - MyDataFilterParams filterParams = new MyDataFilterParams(dataverseRequest, myDataFinder.getRolePermissionHelper()); - if (filterParams.hasError()){ - logger.severe("getTotalCountsFromSolr. filterParams error: " + filterParams.getErrorMessage()); - return null; - } - - // ------------------------------------------------------- - // Re-run all of the entity queries (sigh) - // ------------------------------------------------------- - myDataFinder.initFields(); - myDataFinder.runFindDataSteps(filterParams); - if (myDataFinder.hasError()){ - logger.severe("getTotalCountsFromSolr. myDataFinder error: " + myDataFinder.getErrorMessage()); - return null; - } - - // ------------------------------------------------------- - // Generate filterQueries for total counts - // ------------------------------------------------------- - List filterQueries = myDataFinder.getSolrFilterQueriesForTotalCounts(); - if (filterQueries==null){ - logger.severe("getTotalCountsFromSolr. filterQueries was null!"); - return null; - } - //msgt("getTotalCountsFromSolr"); - //msgt(StringUtils.join(filterQueries, " AND ")); - - // ------------------------------------------------------- - // Run Solr - // ------------------------------------------------------- - SolrQueryResponse solrQueryResponseForCounts; - try { - solrQueryResponseForCounts = searchService.getDefaultSearchService().search( - dataverseRequest, - null, // subtree, default it to Dataverse for now - "*", // Get everything--always - filterQueries,//filterQueries, - SearchFields.NAME_SORT, SortBy.ASCENDING, - //SearchFields.RELEASE_OR_CREATE_DATE, SortBy.DESCENDING, - 0, //paginationStart, - true, // dataRelatedToMe - SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE, //10 // SearchFields.NUM_SOLR_DOCS_TO_RETRIEVE - true, - null, - null, - false, // no need to request facets here ... - false, // ... same for highlights - false // ... same for collections - ); - } catch (SearchException ex) { - logger.severe("Search for total counts failed with filter query"); - logger.severe("filterQueries: " + StringUtils.join(filterQueries, "(separator)")); - return null; - } - return solrQueryResponseForCounts; - } - - private String getJSONErrorString(String jsonMsg, String optionalLoggerMsg){ - - if (jsonMsg == null){ - throw new NullPointerException("jsonMsg cannot be null"); - } - if (optionalLoggerMsg != null){ - logger.severe(optionalLoggerMsg); + private void verifyAuth (ContainerRequestContext crc, String userIdentifier) throws WrappedResponse { + searchUser = authUser = getRequestAuthenticatedUserOrDie(crc); + + // If the user is a superuser, see if a userIdentifier has been specified and use that instead + if ((authUser.isSuperuser()) && (userIdentifier != null) && (!userIdentifier.isEmpty())) { + searchUser = getUserFromIdentifier(userIdentifier); + if (searchUser == null) { + throw new WrappedResponse(error(Response.Status.NOT_FOUND, BundleUtil.getStringFromBundle("dataretrieverAPI.user.not.found", Arrays.asList(userIdentifier)))); + } } - JsonObjectBuilder jsonData = Json.createObjectBuilder(); - - jsonData.add(DataRetrieverAPI.JSON_SUCCESS_FIELD_NAME, false); - jsonData.add(DataRetrieverAPI.JSON_ERROR_MSG_FIELD_NAME, jsonMsg); - - return jsonData.build().toString(); - } - @GET @AuthRequired @Path(retrieveDataPartialAPIPath) @Produces({"application/json"}) - public String retrieveMyDataAsJsonString( + public Response retrieveMyDataAsJsonString( @Context ContainerRequestContext crc, @QueryParam("dvobject_types") List dvobject_types, @QueryParam("published_states") List published_states, @@ -268,36 +118,13 @@ public String retrieveMyDataAsJsonString( @QueryParam("userIdentifier") String userIdentifier, @QueryParam("filter_validities") Boolean filterValidities, @QueryParam("dataset_valid") List datasetValidities) { - boolean OTHER_USER = false; String noMsgResultsFound = BundleUtil.getStringFromBundle("dataretrieverAPI.noMsgResultsFound"); - if ((session.getUser() != null) && (session.getUser().isAuthenticated())) { - authUser = (AuthenticatedUser) session.getUser(); - } else { - try { - authUser = getRequestAuthenticatedUserOrDie(crc); - } catch (WrappedResponse e) { - return this.getJSONErrorString( - BundleUtil.getStringFromBundle("dataretrieverAPI.authentication.required"), - BundleUtil.getStringFromBundle("dataretrieverAPI.authentication.required.opt") - ); - } - } - - // For superusers, the searchUser may differ from the authUser - AuthenticatedUser searchUser = null; - // If the user is a superuser, see if a userIdentifier has been specified and use that instead - if ((authUser.isSuperuser()) && (userIdentifier != null) && (!userIdentifier.isEmpty())) { - searchUser = getUserFromIdentifier(userIdentifier); - if (searchUser != null) { - authUser = searchUser; - OTHER_USER = true; - } else { - return this.getJSONErrorString( - BundleUtil.getStringFromBundle("dataretrieverAPI.user.not.found", Arrays.asList(userIdentifier)), - null); - } + try { + verifyAuth(crc, userIdentifier); + } catch (WrappedResponse wr) { + return wr.getResponse(); } roleList = dataverseRoleService.findAll(); @@ -326,8 +153,8 @@ public String retrieveMyDataAsJsonString( MyDataFilterParams filterParams = new MyDataFilterParams(dataverseRequest, dtypes, pub_states, roleIds, searchTerm, validities); - if (filterParams.hasError()){ - return this.getJSONErrorString(filterParams.getErrorMessage(), filterParams.getErrorMessage()); + if (filterParams.hasError()) { + return error(Response.Status.BAD_REQUEST, filterParams.getErrorMessage()); } // --------------------------------- @@ -338,8 +165,8 @@ public String retrieveMyDataAsJsonString( dvObjectServiceBean, groupService); this.myDataFinder.runFindDataSteps(filterParams); - if (myDataFinder.hasError()){ - return this.getJSONErrorString(myDataFinder.getErrorMessage(), myDataFinder.getErrorMessage()); + if (myDataFinder.hasError()) { + return error(Response.Status.BAD_REQUEST, myDataFinder.getErrorMessage()); } // --------------------------------- @@ -350,24 +177,14 @@ public String retrieveMyDataAsJsonString( paginationStart = selectedPage; } int solrCardStart = (paginationStart - 1) * SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE; - - // Default the searchUser to the authUser. - // The exception: for logged-in superusers, the searchUser may differ from the authUser - // - if (searchUser == null){ - searchUser = authUser; - } //msg("search with user: " + searchUser.getIdentifier()); List filterQueries = this.myDataFinder.getSolrFilterQueries(); if (filterQueries==null){ logger.fine("No ids found for this search"); - return this.getJSONErrorString(noMsgResultsFound, null); + return error(Response.Status.NOT_FOUND, noMsgResultsFound); } - //msgt("myDataFinder.getSolrFilterQueries(): " + myDataFinder.getSolrFilterQueries().toString()); - - //msg("Selected paginationStart: " + paginationStart); try { solrQueryResponse = searchService.getDefaultSearchService().search( @@ -381,11 +198,9 @@ public String retrieveMyDataAsJsonString( true, // dataRelatedToMe SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE //10 // SearchFields.NUM_SOLR_DOCS_TO_RETRIEVE ); - //msgt("getResultsStart: " + this.solrQueryResponse.getResultsStart()); - //msgt("getNumResultsFound: " + this.solrQueryResponse.getNumResultsFound()); - //msgt("getSolrSearchResults: " + this.solrQueryResponse.getSolrSearchResults().toString()); - if (this.solrQueryResponse.getNumResultsFound()==0){ - return this.getJSONErrorString(noMsgResultsFound, null); + + if (this.solrQueryResponse.getNumResultsFound()==0) { + return error(Response.Status.NOT_FOUND, noMsgResultsFound); } } catch (SearchException ex) { @@ -394,10 +209,7 @@ public String retrieveMyDataAsJsonString( } if (solrQueryResponse == null) { - return this.getJSONErrorString( - BundleUtil.getStringFromBundle("dataretrieverAPI.solr.error"), - BundleUtil.getStringFromBundle("dataretrieverAPI.solr.error.opt") - ); + return error(Response.Status.BAD_REQUEST, BundleUtil.getStringFromBundle("dataretrieverAPI.solr.error")); } // --------------------------------- @@ -420,19 +232,15 @@ public String retrieveMyDataAsJsonString( roleTagRetriever.loadRoles(dataverseRequest, solrQueryResponse); - jsonData.add(DataRetrieverAPI.JSON_SUCCESS_FIELD_NAME, true) - .add(DataRetrieverAPI.JSON_DATA_FIELD_NAME, - Json.createObjectBuilder() - .add("pagination", pager.asJsonObjectBuilderUsingCardTerms()) - //.add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, filterParams, this.myDataFinder)) - .add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, roleTagRetriever)) - .add(SearchConstants.SEARCH_API_TOTAL_COUNT, solrQueryResponse.getNumResultsFound()) - .add(SearchConstants.SEARCH_API_START, solrQueryResponse.getResultsStart()) - .add("search_term", filterParams.getSearchTerm()) - .add("dvobject_counts", this.getDvObjectTypeCounts(solrQueryResponse)) - .add("pubstatus_counts", this.getPublicationStatusCounts(solrQueryResponse)) - .add("selected_filters", this.myDataFinder.getSelectedFilterParamsAsJSON()) - ); + jsonData.add("pagination", pager.asJsonObjectBuilderUsingCardTerms()) + .add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, roleTagRetriever)) + .add(SearchConstants.SEARCH_API_TOTAL_COUNT, solrQueryResponse.getNumResultsFound()) + .add(SearchConstants.SEARCH_API_START, solrQueryResponse.getResultsStart()) + .add("search_term", filterParams.getSearchTerm()) + .add("dvobject_counts", this.getDvObjectTypeCounts(solrQueryResponse)) + .add("pubstatus_counts", this.getPublicationStatusCounts(solrQueryResponse)) + .add("selected_filters", this.myDataFinder.getSelectedFilterParamsAsJSON()) + ; // --------------------------------------------------------- // We're doing ~another~ solr query here @@ -441,45 +249,24 @@ public String retrieveMyDataAsJsonString( //jsonData.add("total_dvobject_counts", getTotalCountsFromSolrAsJSON(searchUser, this.myDataFinder)); - if (OTHER_USER){ + if (searchUser != null && !authUser.equals(searchUser)) { jsonData.add("other_user", searchUser.getIdentifier()); } - return jsonData.build().toString(); + return ok(jsonData); } @GET @AuthRequired @Path(retrieveDataPartialAPIPath + "/collectionList") @Produces("application/json") - public String retrieveMyCollectionList(@Context ContainerRequestContext crc, @QueryParam("userIdentifier") String userIdentifier) { - if ((session.getUser() != null) && (session.getUser().isAuthenticated())) { - authUser = (AuthenticatedUser) session.getUser(); - } else { - try { - authUser = getRequestAuthenticatedUserOrDie(crc); - } catch (WrappedResponse e) { - return this.getJSONErrorString( - BundleUtil.getStringFromBundle("dataretrieverAPI.authentication.required"), - BundleUtil.getStringFromBundle("dataretrieverAPI.authentication.required.opt") - ); - } - } - // If the user is a superuser, see if a userIdentifier has been specified and use that instead - AuthenticatedUser searchUser = authUser; - if ((authUser.isSuperuser()) && (userIdentifier != null) && (!userIdentifier.isEmpty())) { - searchUser = getUserFromIdentifier(userIdentifier); - if (searchUser == null) { - return this.getJSONErrorString( - BundleUtil.getStringFromBundle("dataretrieverAPI.user.not.found", Arrays.asList(userIdentifier)), - null); - } - } + public Response retrieveMyCollectionList(@Context ContainerRequestContext crc, @QueryParam("userIdentifier") String userIdentifier) { try { + verifyAuth(crc, userIdentifier); JsonObjectBuilder jsonObjBuilder = execCommand(new GetUserPermittedCollectionsCommand(createDataverseRequest(getRequestUser(crc)), searchUser, Permission.AddDataset.name(), true)); - return jsonObjBuilder.build().toString(); - } catch (WrappedResponse ex) { - return null; + return ok(jsonObjBuilder); + } catch (WrappedResponse wr) { + return wr.getResponse(); } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index 8c6c05a032a..1b7244e62f0 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -13,18 +13,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; -import static jakarta.ws.rs.core.Response.Status.OK; -import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED; +import static jakarta.ws.rs.core.Response.Status.*; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.startsWith; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class DataRetrieverApiIT { - private static final String ERR_MSG_FORMAT = "{\n \"success\": false,\n \"error_message\": \"%s\"\n}"; - @BeforeAll public static void setUpClass() { RestAssured.baseURI = UtilIT.getRestAssuredBaseUri(); @@ -45,28 +42,40 @@ public void testRetrieveMyDataAsJsonString() { String badUserIdentifier = "bad-identifier"; Response invalidUserIdentifierResponse = UtilIT.retrieveMyDataAsJsonString(superUserApiToken, badUserIdentifier, emptyRoleIdsList); - assertEquals(prettyPrintError("dataretrieverAPI.user.not.found", Arrays.asList(badUserIdentifier)), invalidUserIdentifierResponse.prettyPrint()); - assertEquals(OK.getStatusCode(), invalidUserIdentifierResponse.getStatusCode()); + invalidUserIdentifierResponse.prettyPrint(); + invalidUserIdentifierResponse.then().assertThat() + .body("status", equalTo("ERROR")) + .body("message", equalTo(BundleUtil.getStringFromBundle("dataretrieverAPI.user.not.found", Arrays.asList(badUserIdentifier)))) + .statusCode(NOT_FOUND.getStatusCode()); // Call as superuser with valid user identifier and no roles Response createSecondUserResponse = UtilIT.createRandomUser(); String userIdentifier = UtilIT.getUsernameFromResponse(createSecondUserResponse); Response validUserIdentifierResponse = UtilIT.retrieveMyDataAsJsonString(superUserApiToken, userIdentifier, emptyRoleIdsList); - assertEquals(prettyPrintError("myDataFinder.error.result.no.role", null), validUserIdentifierResponse.prettyPrint()); - assertEquals(OK.getStatusCode(), validUserIdentifierResponse.getStatusCode()); + validUserIdentifierResponse.prettyPrint(); + validUserIdentifierResponse.then().assertThat() + .body("status", equalTo("ERROR")) + .body("message", equalTo(BundleUtil.getStringFromBundle("myDataFinder.error.result.no.role"))) + .statusCode(BAD_REQUEST.getStatusCode()); // Call as normal user with one valid role and no results Response createNormalUserResponse = UtilIT.createRandomUser(); String normalUserUsername = UtilIT.getUsernameFromResponse(createNormalUserResponse); String normalUserApiToken = UtilIT.getApiTokenFromResponse(createNormalUserResponse); Response noResultwithOneRoleResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", new ArrayList<>(Arrays.asList(5L))); - assertEquals(prettyPrintError("myDataFinder.error.result.role.empty", Arrays.asList("Dataset Creator")), noResultwithOneRoleResponse.prettyPrint()); - assertEquals(OK.getStatusCode(), noResultwithOneRoleResponse.getStatusCode()); + noResultwithOneRoleResponse.prettyPrint(); + noResultwithOneRoleResponse.then().assertThat() + .body("status", equalTo("ERROR")) + .body("message", equalTo(BundleUtil.getStringFromBundle("myDataFinder.error.result.role.empty", Arrays.asList("Dataset Creator")))) + .statusCode(BAD_REQUEST.getStatusCode()); // Call as normal user with multiple valid roles and no results Response noResultWithMultipleRoleResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", new ArrayList<>(Arrays.asList(5L, 6L))); - assertEquals(prettyPrintError("myDataFinder.error.result.roles.empty", Arrays.asList("Dataset Creator, Contributor")), noResultWithMultipleRoleResponse.prettyPrint()); - assertEquals(OK.getStatusCode(), noResultWithMultipleRoleResponse.getStatusCode()); + noResultWithMultipleRoleResponse.prettyPrint(); + noResultWithMultipleRoleResponse.then().assertThat() + .body("status", equalTo("ERROR")) + .body("message", equalTo(BundleUtil.getStringFromBundle("myDataFinder.error.result.roles.empty", Arrays.asList("Dataset Creator, Contributor")))) + .statusCode(BAD_REQUEST.getStatusCode()); // Call as normal user with one valid dataset role and one dataset result Response createDataverseResponse = UtilIT.createRandomDataverse(normalUserApiToken); @@ -78,6 +87,7 @@ public void testRetrieveMyDataAsJsonString() { Integer datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse); UtilIT.sleepForReindex(datasetId.toString(), normalUserApiToken, 4); Response oneDatasetResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", new ArrayList<>(Arrays.asList(6L))); + oneDatasetResponse.prettyPrint(); assertEquals(OK.getStatusCode(), oneDatasetResponse.getStatusCode()); JsonPath jsonPathOneDataset = oneDatasetResponse.getBody().jsonPath(); assertEquals(1, jsonPathOneDataset.getInt("data.total_count")); @@ -110,7 +120,7 @@ public void testRetrieveMyDataAsJsonString() { @Test public void testRetrieveMyDataCollections() throws InterruptedException { int rootCount = 1; // everyone has access to this dataverse - List items; + List> items; Response createDataverseResponse; Response retrieveMyCollectionListResponse; // Create Superuser @@ -156,51 +166,55 @@ public void testRetrieveMyDataCollections() throws InterruptedException { retrieveMyCollectionListResponse = UtilIT.retrieveMyCollectionList(User1ApiToken, null); retrieveMyCollectionListResponse.prettyPrint(); // The count should show the list size to be User1's + Root Dataverse count - items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("items"); + items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("data.items"); assertEquals(rootCount + user1DataverseCount, items.size()); // User2 gets the list of Dataverses/Collections it has access to retrieveMyCollectionListResponse = UtilIT.retrieveMyCollectionList(User2ApiToken, null); retrieveMyCollectionListResponse.prettyPrint(); // The count should show the list size to be User1's + User2's + Root Dataverse count - items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("items"); + items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("data.items"); assertEquals(rootCount + user1DataverseCount + user2DataverseCount, items.size()); // User3 gets the list of Dataverses/Collections it has access to retrieveMyCollectionListResponse = UtilIT.retrieveMyCollectionList(User3ApiToken, null); retrieveMyCollectionListResponse.prettyPrint(); // The count should show the list size to be only Root Dataverse count - items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("items"); + items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("data.items"); assertEquals(rootCount, items.size()); + // Verify the name and alias of the Root Dataverse. We don't know the id so just make sure it's in the response + assertNotNull(items.get(0).get("id")); + assertEquals("Root", items.get(0).get("name")); + assertEquals("root", items.get(0).get("alias")); // Superuser gets the list of Dataverses/Collections it has access to retrieveMyCollectionListResponse = UtilIT.retrieveMyCollectionList(superUserApiToken, null); retrieveMyCollectionListResponse.prettyPrint(); // The count should show the list size of all Dataverses (including any Dataverses created by other tests) - items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("items"); + items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("data.items"); assertTrue(items.size() >= rootCount + user1DataverseCount + user2DataverseCount); // Superuser gets the list of Dataverses/Collections User1 has access to retrieveMyCollectionListResponse = UtilIT.retrieveMyCollectionList(superUserApiToken, User1Username); retrieveMyCollectionListResponse.prettyPrint(); // The count should show the list size to be User1's + Root Dataverse count - items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("items"); + items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("data.items"); assertEquals(rootCount + user1DataverseCount, items.size()); // Superuser gets the list of Dataverses/Collections User2 has access to retrieveMyCollectionListResponse = UtilIT.retrieveMyCollectionList(superUserApiToken, User2Username); retrieveMyCollectionListResponse.prettyPrint(); // The count should show the list size to be User1's + User2's + Root Dataverse count - items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("items"); + items = retrieveMyCollectionListResponse.getBody().jsonPath().getList("data.items"); assertEquals(rootCount + user1DataverseCount + user2DataverseCount, items.size()); // Superuser gets the list of Dataverses/Collections for bad username retrieveMyCollectionListResponse = UtilIT.retrieveMyCollectionList(superUserApiToken, "badUserName"); retrieveMyCollectionListResponse.prettyPrint(); retrieveMyCollectionListResponse.then().assertThat() - .body("success", equalTo(false)) - .body("error_message", startsWith("No user found for:")) - .statusCode(OK.getStatusCode()); + .body("status", equalTo("ERROR")) + .body("message", startsWith("No user found for:")) + .statusCode(NOT_FOUND.getStatusCode()); // Clean up dataverses.forEach(dv -> { @@ -236,8 +250,11 @@ public void testRetrieveMyDataAsJsonStringSortOrder() { // Call as regular user with no result Response myDataEmptyResponse = UtilIT.retrieveMyDataAsJsonString(userApiToken, "", new ArrayList<>(Arrays.asList(6L))); - assertEquals(prettyPrintError("myDataFinder.error.result.role.empty", Arrays.asList("Contributor")), myDataEmptyResponse.prettyPrint()); - assertEquals(OK.getStatusCode(), myDataEmptyResponse.getStatusCode()); + myDataEmptyResponse.prettyPrint(); + myDataEmptyResponse.then().assertThat() + .body("status", equalTo("ERROR")) + .body("message", equalTo(BundleUtil.getStringFromBundle("myDataFinder.error.result.role.empty", Arrays.asList("Contributor")))) + .statusCode(BAD_REQUEST.getStatusCode()); // Create and publish a dataverse Response createDataverseResponse = UtilIT.createRandomDataverse(superUserApiToken); @@ -393,14 +410,4 @@ public void testRetrieveMyDataAsJsonStringSortOrder() { deleteSuperUserResponse.prettyPrint(); assertEquals(OK.getStatusCode(), deleteSuperUserResponse.getStatusCode()); } - - private static String prettyPrintError(String resourceBundleKey, List params) { - final String errorMessage; - if (params == null || params.isEmpty()) { - errorMessage = BundleUtil.getStringFromBundle(resourceBundleKey); - } else { - errorMessage = BundleUtil.getStringFromBundle(resourceBundleKey, params); - } - return String.format(ERR_MSG_FORMAT, errorMessage.replaceAll("\"", "\\\\\"")); - } } From b752b227f1d85a4e986b4a2732a4dc74d2f36558 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Mon, 25 Aug 2025 15:49:01 -0400 Subject: [PATCH 15/30] review comments and refactor --- .../command/impl/GetUserPermittedCollectionsCommand.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 index 499472df849..1770622846d 100644 --- 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 @@ -26,12 +26,12 @@ public class GetUserPermittedCollectionsCommand extends AbstractCommand Date: Mon, 25 Aug 2025 16:05:22 -0400 Subject: [PATCH 16/30] adding unit test --- .../dataverse/util/json/JsonPrinterTest.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java index 36ff11fe4bb..382bc2d337a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java @@ -24,10 +24,7 @@ import java.util.List; import java.util.Set; -import jakarta.json.JsonArrayBuilder; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; -import jakarta.json.JsonString; +import jakarta.json.*; import edu.harvard.iq.dataverse.util.BundleUtil; import org.assertj.core.util.Lists; @@ -485,6 +482,26 @@ public void testDatasetWithNondefaultType() { assertEquals(sut, result); } + @Test + public void testJsonArrayDataverseCollections() { + List collections = new ArrayList<>(); + for (long i=0; i < 10; i++) { + Dataverse dv = new Dataverse(); + dv.setAlias("alias" + i); + dv.setName("Alias" + i); + dv.setId(i); + collections.add(dv); + } + JsonArrayBuilder jsob = JsonPrinter.jsonArray(collections); + JsonArray result = jsob.build(); + assertNotNull(result); + assertEquals(10, result.size()); + System.out.println(result.get(5).toString()); + assertTrue(result.get(6).toString().contains("name\":\"Alias6")); + assertTrue(result.get(6).toString().contains("alias\":\"alias6")); + assertTrue(result.get(6).toString().contains("id\":6")); + } + private Dataverse createDataverse(long id) { Dataverse dataverse = new Dataverse(); dataverse.setId(id); From 09544db97e90998ced6197612c0bca14497aa82e Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 26 Aug 2025 14:43:27 -0400 Subject: [PATCH 17/30] refactor --- .../edu/harvard/iq/dataverse/api/Users.java | 6 +- .../GetUserPermittedCollectionsCommand.java | 33 +---- .../iq/dataverse/mydata/DataRetrieverAPI.java | 125 ++++++++++++------ .../iq/dataverse/util/json/JsonPrinter.java | 7 +- .../iq/dataverse/api/DataRetrieverApiIT.java | 50 ++++--- .../dataverse/util/json/JsonPrinterTest.java | 17 ++- 6 files changed, 133 insertions(+), 105 deletions(-) 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 77e08bf6ceb..af6b533d46d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Users.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Users.java @@ -5,6 +5,7 @@ */ package edu.harvard.iq.dataverse.api; +import edu.harvard.iq.dataverse.Dataverse; import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.users.ApiToken; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; @@ -26,6 +27,7 @@ import java.util.logging.Logger; import edu.harvard.iq.dataverse.util.json.JsonParseException; +import edu.harvard.iq.dataverse.util.json.JsonPrinter; import edu.harvard.iq.dataverse.util.json.JsonUtil; import jakarta.ejb.Stateless; import jakarta.json.JsonArray; @@ -286,8 +288,8 @@ public Response getUserPermittedCollections(@Context ContainerRequestContext crc } try { AuthenticatedUser userToQuery = authSvc.getAuthenticatedUser(identifier); - JsonObjectBuilder jsonObj = execCommand(new GetUserPermittedCollectionsCommand(createDataverseRequest(getRequestUser(crc)), userToQuery, permission)); - return ok(jsonObj); + List collections = execCommand(new GetUserPermittedCollectionsCommand(createDataverseRequest(getRequestUser(crc)), userToQuery, permission)); + return ok(JsonPrinter.jsonArray(collections)); } catch (WrappedResponse ex) { return ex.getResponse(); } 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 index 1770622846d..851d0d2e327 100644 --- 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 @@ -9,38 +9,27 @@ 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; -import static edu.harvard.iq.dataverse.util.json.JsonPrinter.jsonArray; - @RequiredPermissions({}) -public class GetUserPermittedCollectionsCommand extends AbstractCommand { +public class GetUserPermittedCollectionsCommand extends AbstractCommand> { private static final Logger logger = Logger.getLogger(GetUserPermittedCollectionsCommand.class.getCanonicalName()); private DataverseRequest request; private AuthenticatedUser user; private String permission; - private boolean minimalOutput; public GetUserPermittedCollectionsCommand(DataverseRequest request, AuthenticatedUser user, String permission) { - this(request, user, permission, false); - } - public GetUserPermittedCollectionsCommand(DataverseRequest request, AuthenticatedUser user, String permission, boolean minimalOutput) { super(request, (DvObject) null); this.request = request; this.user = user; this.permission = permission; - this.minimalOutput = minimalOutput; } @Override - public JsonObjectBuilder execute(CommandContext ctxt) throws CommandException { + public List execute(CommandContext ctxt) throws CommandException { if (user == null) { throw new CommandException("User not found.", this); } @@ -51,22 +40,6 @@ public JsonObjectBuilder execute(CommandContext ctxt) throws CommandException { } 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(); - job.add("count", collections.size()); - if (minimalOutput) { - job.add("items", jsonArray(collections)); - } else { - JsonArrayBuilder jab = Json.createArrayBuilder(); - for (Dataverse dv : collections) { - jab.add(json(dv)); - } - job.add("items", jab); - } - return job; - } - return null; + return ctxt.permissions().findPermittedCollections(request, user, permissionBit); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 55d5f8cf63c..6aff3209cf3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -6,6 +6,8 @@ import edu.harvard.iq.dataverse.*; import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.users.GuestUser; +import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.engine.command.impl.GetUserPermittedCollectionsCommand; import edu.harvard.iq.dataverse.search.SolrQueryResponse; import edu.harvard.iq.dataverse.search.SolrSearchResult; @@ -22,11 +24,16 @@ import edu.harvard.iq.dataverse.search.SearchServiceFactory; import edu.harvard.iq.dataverse.search.SortBy; -import java.util.*; +import java.util.Arrays; +import java.util.List; import java.util.logging.Logger; +import edu.harvard.iq.dataverse.util.json.JsonPrinter; import jakarta.ejb.EJB; -import jakarta.json.*; +import jakarta.inject.Inject; +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObjectBuilder; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; @@ -36,6 +43,7 @@ import edu.harvard.iq.dataverse.util.BundleUtil; import jakarta.ws.rs.core.Response; +import org.json.JSONObject; /** * @@ -54,6 +62,9 @@ public class DataRetrieverAPI extends AbstractApiBean { public static final String retrieveDataFullAPIPath = "/api/v1/mydata/retrieve"; private static final String retrieveDataPartialAPIPath = "retrieve"; + @Inject + DataverseSession session; + @EJB DataverseRoleServiceBean dataverseRoleService; @EJB @@ -76,6 +87,10 @@ public class DataRetrieverAPI extends AbstractApiBean { private AuthenticatedUser authUser = null; private AuthenticatedUser searchUser = null; + public static final String JSON_SUCCESS_FIELD_NAME = "success"; + public static final String JSON_ERROR_MSG_FIELD_NAME = "error_message"; + public static final String JSON_DATA_FIELD_NAME = "data"; + /** * Constructor * @@ -92,8 +107,34 @@ private AuthenticatedUser getUserFromIdentifier(String userIdentifier){ return authenticationService.getAuthenticatedUser(userIdentifier); } + private String getJSONErrorString(String jsonMsg, String optionalLoggerMsg){ + + if (jsonMsg == null){ + throw new NullPointerException("jsonMsg cannot be null"); + } + if (optionalLoggerMsg != null){ + logger.severe(optionalLoggerMsg); + } + JsonObjectBuilder jsonData = Json.createObjectBuilder(); + + jsonData.add(DataRetrieverAPI.JSON_SUCCESS_FIELD_NAME, false); + jsonData.add(DataRetrieverAPI.JSON_ERROR_MSG_FIELD_NAME, jsonMsg); + + return jsonData.build().toString(); + + } + private void verifyAuth (ContainerRequestContext crc, String userIdentifier) throws WrappedResponse { - searchUser = authUser = getRequestAuthenticatedUserOrDie(crc); + // Handle calls from JSF where the User is in the session + User requestUser = getRequestUser(crc); + if (session != null && session.getUser() != null && requestUser instanceof GuestUser) { + searchUser = authUser = (AuthenticatedUser) session.getUser(); + if (!authUser.isAuthenticated()) { + throw new WrappedResponse(authenticatedUserRequired()); + } + } else { + searchUser = authUser = getRequestAuthenticatedUserOrDie(crc); + } // If the user is a superuser, see if a userIdentifier has been specified and use that instead if ((authUser.isSuperuser()) && (userIdentifier != null) && (!userIdentifier.isEmpty())) { @@ -108,7 +149,7 @@ private void verifyAuth (ContainerRequestContext crc, String userIdentifier) thr @AuthRequired @Path(retrieveDataPartialAPIPath) @Produces({"application/json"}) - public Response retrieveMyDataAsJsonString( + public String retrieveMyDataAsJsonString( @Context ContainerRequestContext crc, @QueryParam("dvobject_types") List dvobject_types, @QueryParam("published_states") List published_states, @@ -118,19 +159,20 @@ public Response retrieveMyDataAsJsonString( @QueryParam("userIdentifier") String userIdentifier, @QueryParam("filter_validities") Boolean filterValidities, @QueryParam("dataset_valid") List datasetValidities) { + boolean OTHER_USER = false; String noMsgResultsFound = BundleUtil.getStringFromBundle("dataretrieverAPI.noMsgResultsFound"); try { verifyAuth(crc, userIdentifier); + OTHER_USER = !authUser.equals(searchUser); } catch (WrappedResponse wr) { - return wr.getResponse(); + return this.getJSONErrorString((new JSONObject(wr.getResponse().getEntity().toString())).getString("message"), null); } roleList = dataverseRoleService.findAll(); - rolePermissionHelper = new DataverseRolePermissionHelper(roleList); - - + rolePermissionHelper = new DataverseRolePermissionHelper(roleList); + List dtypes; if (dvobject_types != null){ dtypes = dvobject_types; @@ -153,8 +195,8 @@ public Response retrieveMyDataAsJsonString( MyDataFilterParams filterParams = new MyDataFilterParams(dataverseRequest, dtypes, pub_states, roleIds, searchTerm, validities); - if (filterParams.hasError()) { - return error(Response.Status.BAD_REQUEST, filterParams.getErrorMessage()); + if (filterParams.hasError()){ + return this.getJSONErrorString(filterParams.getErrorMessage(), filterParams.getErrorMessage()); } // --------------------------------- @@ -165,8 +207,8 @@ public Response retrieveMyDataAsJsonString( dvObjectServiceBean, groupService); this.myDataFinder.runFindDataSteps(filterParams); - if (myDataFinder.hasError()) { - return error(Response.Status.BAD_REQUEST, myDataFinder.getErrorMessage()); + if (myDataFinder.hasError()){ + return this.getJSONErrorString(myDataFinder.getErrorMessage(), myDataFinder.getErrorMessage()); } // --------------------------------- @@ -183,7 +225,7 @@ public Response retrieveMyDataAsJsonString( List filterQueries = this.myDataFinder.getSolrFilterQueries(); if (filterQueries==null){ logger.fine("No ids found for this search"); - return error(Response.Status.NOT_FOUND, noMsgResultsFound); + return this.getJSONErrorString(noMsgResultsFound, null); } try { @@ -199,17 +241,20 @@ public Response retrieveMyDataAsJsonString( SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE //10 // SearchFields.NUM_SOLR_DOCS_TO_RETRIEVE ); - if (this.solrQueryResponse.getNumResultsFound()==0) { - return error(Response.Status.NOT_FOUND, noMsgResultsFound); - } - + if (this.solrQueryResponse.getNumResultsFound()==0){ + return this.getJSONErrorString(noMsgResultsFound, null); + } + } catch (SearchException ex) { solrQueryResponse = null; logger.severe("Solr SearchException: " + ex.getMessage()); } if (solrQueryResponse == null) { - return error(Response.Status.BAD_REQUEST, BundleUtil.getStringFromBundle("dataretrieverAPI.solr.error")); + return this.getJSONErrorString( + BundleUtil.getStringFromBundle("dataretrieverAPI.solr.error"), + BundleUtil.getStringFromBundle("dataretrieverAPI.solr.error.opt") + ); } // --------------------------------- @@ -224,23 +269,27 @@ public Response retrieveMyDataAsJsonString( // Initialize JSON response JsonObjectBuilder jsonData = Json.createObjectBuilder(); - Pager pager = new Pager(solrQueryResponse.getNumResultsFound().intValue(), - SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE, - paginationStart); - + Pager pager = new Pager(solrQueryResponse.getNumResultsFound().intValue(), + SearchConstants.NUM_SOLR_DOCS_TO_RETRIEVE, + paginationStart); + RoleTagRetriever roleTagRetriever = new RoleTagRetriever(this.rolePermissionHelper, this.roleAssigneeSvc, this.dvObjectServiceBean); roleTagRetriever.loadRoles(dataverseRequest, solrQueryResponse); - - jsonData.add("pagination", pager.asJsonObjectBuilderUsingCardTerms()) - .add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, roleTagRetriever)) - .add(SearchConstants.SEARCH_API_TOTAL_COUNT, solrQueryResponse.getNumResultsFound()) - .add(SearchConstants.SEARCH_API_START, solrQueryResponse.getResultsStart()) - .add("search_term", filterParams.getSearchTerm()) - .add("dvobject_counts", this.getDvObjectTypeCounts(solrQueryResponse)) - .add("pubstatus_counts", this.getPublicationStatusCounts(solrQueryResponse)) - .add("selected_filters", this.myDataFinder.getSelectedFilterParamsAsJSON()) - ; + + jsonData.add(DataRetrieverAPI.JSON_SUCCESS_FIELD_NAME, true) + .add(DataRetrieverAPI.JSON_DATA_FIELD_NAME, + Json.createObjectBuilder() + .add("pagination", pager.asJsonObjectBuilderUsingCardTerms()) + //.add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, filterParams, this.myDataFinder)) + .add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, roleTagRetriever)) + .add(SearchConstants.SEARCH_API_TOTAL_COUNT, solrQueryResponse.getNumResultsFound()) + .add(SearchConstants.SEARCH_API_START, solrQueryResponse.getResultsStart()) + .add("search_term", filterParams.getSearchTerm()) + .add("dvobject_counts", this.getDvObjectTypeCounts(solrQueryResponse)) + .add("pubstatus_counts", this.getPublicationStatusCounts(solrQueryResponse)) + .add("selected_filters", this.myDataFinder.getSelectedFilterParamsAsJSON()) + ); // --------------------------------------------------------- // We're doing ~another~ solr query here @@ -248,12 +297,12 @@ public Response retrieveMyDataAsJsonString( // --------------------------------------------------------- //jsonData.add("total_dvobject_counts", getTotalCountsFromSolrAsJSON(searchUser, this.myDataFinder)); - - if (searchUser != null && !authUser.equals(searchUser)) { + + if (OTHER_USER){ jsonData.add("other_user", searchUser.getIdentifier()); } - - return ok(jsonData); + + return jsonData.build().toString(); } @GET @@ -263,8 +312,8 @@ public Response retrieveMyDataAsJsonString( public Response retrieveMyCollectionList(@Context ContainerRequestContext crc, @QueryParam("userIdentifier") String userIdentifier) { try { verifyAuth(crc, userIdentifier); - JsonObjectBuilder jsonObjBuilder = execCommand(new GetUserPermittedCollectionsCommand(createDataverseRequest(getRequestUser(crc)), searchUser, Permission.AddDataset.name(), true)); - return ok(jsonObjBuilder); + List collections = execCommand(new GetUserPermittedCollectionsCommand(createDataverseRequest(getRequestUser(crc)), searchUser, Permission.AddDataset.name())); + return ok(JsonPrinter.jsonArray(collections)); } catch (WrappedResponse wr) { return wr.getResponse(); } diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 7405926ce36..03b0398e763 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -345,7 +345,9 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re } // For UI drop down list. Only needing display name and identifier - public static JsonArrayBuilder jsonArray(List collections) { + public static JsonObjectBuilder jsonArray(List collections) { + JsonObjectBuilder job = Json.createObjectBuilder(); + job.add("count", collections.size()); JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder(); for (Dataverse dataverse : collections) { NullSafeJsonBuilder jsonObject = NullSafeJsonBuilder.jsonObjectBuilder(); @@ -354,7 +356,8 @@ public static JsonArrayBuilder jsonArray(List collections) { jsonObject.add("alias", dataverse.getAlias()); jsonArrayBuilder.add(jsonObject); } - return jsonArrayBuilder; + job.add("items", jsonArrayBuilder); + return job; } public static JsonArrayBuilder json(List dataverseContacts) { diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index 1b7244e62f0..4c5d8438bd4 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -22,6 +22,8 @@ public class DataRetrieverApiIT { + private static final String ERR_MSG_FORMAT = "{\n \"success\": false,\n \"error_message\": \"%s\"\n}"; + @BeforeAll public static void setUpClass() { RestAssured.baseURI = UtilIT.getRestAssuredBaseUri(); @@ -42,40 +44,28 @@ public void testRetrieveMyDataAsJsonString() { String badUserIdentifier = "bad-identifier"; Response invalidUserIdentifierResponse = UtilIT.retrieveMyDataAsJsonString(superUserApiToken, badUserIdentifier, emptyRoleIdsList); - invalidUserIdentifierResponse.prettyPrint(); - invalidUserIdentifierResponse.then().assertThat() - .body("status", equalTo("ERROR")) - .body("message", equalTo(BundleUtil.getStringFromBundle("dataretrieverAPI.user.not.found", Arrays.asList(badUserIdentifier)))) - .statusCode(NOT_FOUND.getStatusCode()); + assertEquals(prettyPrintError("dataretrieverAPI.user.not.found", Arrays.asList(badUserIdentifier)), invalidUserIdentifierResponse.prettyPrint()); + assertEquals(OK.getStatusCode(), invalidUserIdentifierResponse.getStatusCode()); // Call as superuser with valid user identifier and no roles Response createSecondUserResponse = UtilIT.createRandomUser(); String userIdentifier = UtilIT.getUsernameFromResponse(createSecondUserResponse); Response validUserIdentifierResponse = UtilIT.retrieveMyDataAsJsonString(superUserApiToken, userIdentifier, emptyRoleIdsList); - validUserIdentifierResponse.prettyPrint(); - validUserIdentifierResponse.then().assertThat() - .body("status", equalTo("ERROR")) - .body("message", equalTo(BundleUtil.getStringFromBundle("myDataFinder.error.result.no.role"))) - .statusCode(BAD_REQUEST.getStatusCode()); + assertEquals(prettyPrintError("myDataFinder.error.result.no.role", null), validUserIdentifierResponse.prettyPrint()); + assertEquals(OK.getStatusCode(), validUserIdentifierResponse.getStatusCode()); // Call as normal user with one valid role and no results Response createNormalUserResponse = UtilIT.createRandomUser(); String normalUserUsername = UtilIT.getUsernameFromResponse(createNormalUserResponse); String normalUserApiToken = UtilIT.getApiTokenFromResponse(createNormalUserResponse); Response noResultwithOneRoleResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", new ArrayList<>(Arrays.asList(5L))); - noResultwithOneRoleResponse.prettyPrint(); - noResultwithOneRoleResponse.then().assertThat() - .body("status", equalTo("ERROR")) - .body("message", equalTo(BundleUtil.getStringFromBundle("myDataFinder.error.result.role.empty", Arrays.asList("Dataset Creator")))) - .statusCode(BAD_REQUEST.getStatusCode()); + assertEquals(prettyPrintError("myDataFinder.error.result.role.empty", Arrays.asList("Dataset Creator")), noResultwithOneRoleResponse.prettyPrint()); + assertEquals(OK.getStatusCode(), noResultwithOneRoleResponse.getStatusCode()); // Call as normal user with multiple valid roles and no results Response noResultWithMultipleRoleResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", new ArrayList<>(Arrays.asList(5L, 6L))); - noResultWithMultipleRoleResponse.prettyPrint(); - noResultWithMultipleRoleResponse.then().assertThat() - .body("status", equalTo("ERROR")) - .body("message", equalTo(BundleUtil.getStringFromBundle("myDataFinder.error.result.roles.empty", Arrays.asList("Dataset Creator, Contributor")))) - .statusCode(BAD_REQUEST.getStatusCode()); + assertEquals(prettyPrintError("myDataFinder.error.result.roles.empty", Arrays.asList("Dataset Creator, Contributor")), noResultWithMultipleRoleResponse.prettyPrint()); + assertEquals(OK.getStatusCode(), noResultWithMultipleRoleResponse.getStatusCode()); // Call as normal user with one valid dataset role and one dataset result Response createDataverseResponse = UtilIT.createRandomDataverse(normalUserApiToken); @@ -87,7 +77,6 @@ public void testRetrieveMyDataAsJsonString() { Integer datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse); UtilIT.sleepForReindex(datasetId.toString(), normalUserApiToken, 4); Response oneDatasetResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", new ArrayList<>(Arrays.asList(6L))); - oneDatasetResponse.prettyPrint(); assertEquals(OK.getStatusCode(), oneDatasetResponse.getStatusCode()); JsonPath jsonPathOneDataset = oneDatasetResponse.getBody().jsonPath(); assertEquals(1, jsonPathOneDataset.getInt("data.total_count")); @@ -97,6 +86,8 @@ public void testRetrieveMyDataAsJsonString() { UtilIT.grantRoleOnDataverse(dataverseAlias, DataverseRole.DS_CONTRIBUTOR.toString(), "@" + normalUserUsername, superUserApiToken); Response oneDataverseResponse = UtilIT.retrieveMyDataAsJsonString(normalUserApiToken, "", new ArrayList<>(Arrays.asList(5L))); + oneDataverseResponse.prettyPrint(); + assertEquals(OK.getStatusCode(), oneDataverseResponse.getStatusCode()); JsonPath jsonPathOneDataverse = oneDataverseResponse.getBody().jsonPath(); assertEquals(1, jsonPathOneDataverse.getInt("data.total_count")); @@ -250,11 +241,8 @@ public void testRetrieveMyDataAsJsonStringSortOrder() { // Call as regular user with no result Response myDataEmptyResponse = UtilIT.retrieveMyDataAsJsonString(userApiToken, "", new ArrayList<>(Arrays.asList(6L))); - myDataEmptyResponse.prettyPrint(); - myDataEmptyResponse.then().assertThat() - .body("status", equalTo("ERROR")) - .body("message", equalTo(BundleUtil.getStringFromBundle("myDataFinder.error.result.role.empty", Arrays.asList("Contributor")))) - .statusCode(BAD_REQUEST.getStatusCode()); + assertEquals(prettyPrintError("myDataFinder.error.result.role.empty", Arrays.asList("Contributor")), myDataEmptyResponse.prettyPrint()); + assertEquals(OK.getStatusCode(), myDataEmptyResponse.getStatusCode()); // Create and publish a dataverse Response createDataverseResponse = UtilIT.createRandomDataverse(superUserApiToken); @@ -410,4 +398,14 @@ public void testRetrieveMyDataAsJsonStringSortOrder() { deleteSuperUserResponse.prettyPrint(); assertEquals(OK.getStatusCode(), deleteSuperUserResponse.getStatusCode()); } + + private static String prettyPrintError(String resourceBundleKey, List params) { + final String errorMessage; + if (params == null || params.isEmpty()) { + errorMessage = BundleUtil.getStringFromBundle(resourceBundleKey); + } else { + errorMessage = BundleUtil.getStringFromBundle(resourceBundleKey, params); + } + return String.format(ERR_MSG_FORMAT, errorMessage.replaceAll("\"", "\\\\\"")); + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java index 382bc2d337a..e5cf37d32ca 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java @@ -492,14 +492,17 @@ public void testJsonArrayDataverseCollections() { dv.setId(i); collections.add(dv); } - JsonArrayBuilder jsob = JsonPrinter.jsonArray(collections); - JsonArray result = jsob.build(); + JsonObjectBuilder job = JsonPrinter.jsonArray(collections); + JsonObject result = job.build(); assertNotNull(result); - assertEquals(10, result.size()); - System.out.println(result.get(5).toString()); - assertTrue(result.get(6).toString().contains("name\":\"Alias6")); - assertTrue(result.get(6).toString().contains("alias\":\"alias6")); - assertTrue(result.get(6).toString().contains("id\":6")); + System.out.println(result); + assertEquals(10, result.getInt("count")); + JsonArray items = result.getJsonArray("items"); + JsonObject item6 = items.getJsonObject(6); + System.out.println(item6); + assertEquals(6, item6.getInt("id")); + assertEquals("Alias6", item6.getString("name")); + assertEquals("alias6", item6.getString("alias")); } private Dataverse createDataverse(long id) { From 3813f9bd4f4c43114ef8479bffc86dc28a3530c1 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 26 Aug 2025 14:56:08 -0400 Subject: [PATCH 18/30] review comments --- ...525-retrieve-collections-a-user-can-create-datasets-in.md | 2 +- doc/sphinx-guides/source/api/native-api.rst | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md b/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md index 51099039826..91a3a83efa5 100644 --- a/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md +++ b/doc/release-notes/11525-retrieve-collections-a-user-can-create-datasets-in.md @@ -1,5 +1,5 @@ ### New API to retrieve a list of collections that an authenticated user can create a dataset in -The API GET /api/mydata/retrieve/collectionList will return all the dataverse objects that the user add to +The API GET /api/mydata/retrieve/collectionList will return all the dataverse objects that the user can add to See also [the guides](https://guides.dataverse.org/en/latest/api/native-api.html#mydata) and #11525. diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index adc751fb740..b73d3d86c34 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -7873,7 +7873,7 @@ MyData Collection List ---------------------- The MyData Collection List API is used to get a list of the collections an authenticated user can create a Dataset in. -Param identifier={userName} is used by a superuser to get the collections for a specific user. +Param userIdentifier={userName} is used by a superuser to get the collections for a specific user. A curl example listing collections: @@ -7882,5 +7882,6 @@ A curl example listing collections: export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx export SERVER_URL=https://demo.dataverse.org - curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/mydata/retrieve/collectionList?identifier=" + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/mydata/retrieve/collectionList" + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/mydata/retrieve/collectionList?userIdentifier=anotherUser" From a82b90bfacdfcbffac14d95c42716de18dc9a30c Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:01:54 -0400 Subject: [PATCH 19/30] review comments --- .../java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 03b0398e763..2abda7edafb 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -344,7 +344,6 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re return bld; } - // For UI drop down list. Only needing display name and identifier public static JsonObjectBuilder jsonArray(List collections) { JsonObjectBuilder job = Json.createObjectBuilder(); job.add("count", collections.size()); From baf0b007e0610a3da5d7242d5ec3597dad7a440d Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Wed, 27 Aug 2025 15:04:34 -0400 Subject: [PATCH 20/30] if api key is present do not use session --- .../java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 6aff3209cf3..23088fc23a7 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -126,8 +126,9 @@ private String getJSONErrorString(String jsonMsg, String optionalLoggerMsg){ private void verifyAuth (ContainerRequestContext crc, String userIdentifier) throws WrappedResponse { // Handle calls from JSF where the User is in the session + boolean requiresApiKey = getRequestApiKey() != null; User requestUser = getRequestUser(crc); - if (session != null && session.getUser() != null && requestUser instanceof GuestUser) { + if (!requiresApiKey && session != null && session.getUser() != null && requestUser instanceof GuestUser) { searchUser = authUser = (AuthenticatedUser) session.getUser(); if (!authUser.isAuthenticated()) { throw new WrappedResponse(authenticatedUserRequired()); From 9a977ab604e9f65901984374da81bbbf987f8cf3 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Fri, 29 Aug 2025 10:33:10 -0400 Subject: [PATCH 21/30] review comments --- .../iq/dataverse/mydata/DataRetrieverAPI.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 23088fc23a7..65e91aef24b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -6,8 +6,6 @@ import edu.harvard.iq.dataverse.*; import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.Permission; -import edu.harvard.iq.dataverse.authorization.users.GuestUser; -import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.engine.command.impl.GetUserPermittedCollectionsCommand; import edu.harvard.iq.dataverse.search.SolrQueryResponse; import edu.harvard.iq.dataverse.search.SolrSearchResult; @@ -126,9 +124,9 @@ private String getJSONErrorString(String jsonMsg, String optionalLoggerMsg){ private void verifyAuth (ContainerRequestContext crc, String userIdentifier) throws WrappedResponse { // Handle calls from JSF where the User is in the session + // Must use api key if the token is present boolean requiresApiKey = getRequestApiKey() != null; - User requestUser = getRequestUser(crc); - if (!requiresApiKey && session != null && session.getUser() != null && requestUser instanceof GuestUser) { + if (!requiresApiKey && session != null && session.getUser() != null) { searchUser = authUser = (AuthenticatedUser) session.getUser(); if (!authUser.isAuthenticated()) { throw new WrappedResponse(authenticatedUserRequired()); @@ -160,13 +158,13 @@ public String retrieveMyDataAsJsonString( @QueryParam("userIdentifier") String userIdentifier, @QueryParam("filter_validities") Boolean filterValidities, @QueryParam("dataset_valid") List datasetValidities) { - boolean OTHER_USER = false; + boolean otherUser; String noMsgResultsFound = BundleUtil.getStringFromBundle("dataretrieverAPI.noMsgResultsFound"); try { verifyAuth(crc, userIdentifier); - OTHER_USER = !authUser.equals(searchUser); + otherUser = !authUser.equals(searchUser); } catch (WrappedResponse wr) { return this.getJSONErrorString((new JSONObject(wr.getResponse().getEntity().toString())).getString("message"), null); } @@ -299,7 +297,7 @@ public String retrieveMyDataAsJsonString( //jsonData.add("total_dvobject_counts", getTotalCountsFromSolrAsJSON(searchUser, this.myDataFinder)); - if (OTHER_USER){ + if (otherUser){ jsonData.add("other_user", searchUser.getIdentifier()); } From 9ea5df896eafcb8dc18c1c7c8c043ebed36d25c8 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Fri, 29 Aug 2025 13:45:05 -0400 Subject: [PATCH 22/30] adding debug for testing --- .../edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java | 8 ++++++++ .../harvard/iq/dataverse/util/json/JsonPrinterTest.java | 2 -- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 65e91aef24b..5005ac192d4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -126,13 +126,21 @@ private void verifyAuth (ContainerRequestContext crc, String userIdentifier) thr // Handle calls from JSF where the User is in the session // Must use api key if the token is present boolean requiresApiKey = getRequestApiKey() != null; + logger.severe(">>>> requiresApiKey " + requiresApiKey); + logger.severe(">>>> getRequestApiKey() " + getRequestApiKey()); + logger.severe(">>>> session != null " + (session != null)); + logger.severe(">>>> session.getUser() != null " + (session != null && session.getUser() != null)); if (!requiresApiKey && session != null && session.getUser() != null) { searchUser = authUser = (AuthenticatedUser) session.getUser(); + logger.severe(">>>> session authUser " + authUser); + logger.severe(">>>> session authUser isAuthenticated " + authUser.isAuthenticated()); if (!authUser.isAuthenticated()) { throw new WrappedResponse(authenticatedUserRequired()); } } else { searchUser = authUser = getRequestAuthenticatedUserOrDie(crc); + logger.severe(">>>> API authUser " + authUser); + logger.severe(">>>> API authUser isAuthenticated " + authUser.isAuthenticated()); } // If the user is a superuser, see if a userIdentifier has been specified and use that instead diff --git a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java index e5cf37d32ca..dc8dfc4a342 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java @@ -495,11 +495,9 @@ public void testJsonArrayDataverseCollections() { JsonObjectBuilder job = JsonPrinter.jsonArray(collections); JsonObject result = job.build(); assertNotNull(result); - System.out.println(result); assertEquals(10, result.getInt("count")); JsonArray items = result.getJsonArray("items"); JsonObject item6 = items.getJsonObject(6); - System.out.println(item6); assertEquals(6, item6.getInt("id")); assertEquals("Alias6", item6.getString("name")); assertEquals("alias6", item6.getString("alias")); From 3abf810351375fb29969e54f39a80d8c3a207523 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Fri, 29 Aug 2025 17:17:07 -0400 Subject: [PATCH 23/30] adding debug for testing --- .../iq/dataverse/mydata/DataRetrieverAPI.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index 5005ac192d4..d2cf40d3b0f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -6,6 +6,8 @@ import edu.harvard.iq.dataverse.*; import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.users.GuestUser; +import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.engine.command.impl.GetUserPermittedCollectionsCommand; import edu.harvard.iq.dataverse.search.SolrQueryResponse; import edu.harvard.iq.dataverse.search.SolrSearchResult; @@ -26,6 +28,7 @@ import java.util.List; import java.util.logging.Logger; +import edu.harvard.iq.dataverse.settings.FeatureFlags; import edu.harvard.iq.dataverse.util.json.JsonPrinter; import jakarta.ejb.EJB; import jakarta.inject.Inject; @@ -124,23 +127,19 @@ private String getJSONErrorString(String jsonMsg, String optionalLoggerMsg){ private void verifyAuth (ContainerRequestContext crc, String userIdentifier) throws WrappedResponse { // Handle calls from JSF where the User is in the session - // Must use api key if the token is present - boolean requiresApiKey = getRequestApiKey() != null; - logger.severe(">>>> requiresApiKey " + requiresApiKey); - logger.severe(">>>> getRequestApiKey() " + getRequestApiKey()); - logger.severe(">>>> session != null " + (session != null)); - logger.severe(">>>> session.getUser() != null " + (session != null && session.getUser() != null)); - if (!requiresApiKey && session != null && session.getUser() != null) { + logger.severe(">>>> session.getUser() " + (session != null ? session.getUser() : "null")); + User requestUser = getRequestUser(crc); + logger.severe(">>>> requestUser " + requestUser); + logger.severe(">>>> FeatureFlags.API_SESSION_AUTH.enabled() " + FeatureFlags.API_SESSION_AUTH.enabled()); + boolean checkSession = !FeatureFlags.API_SESSION_AUTH.enabled() && (requestUser instanceof GuestUser); + logger.severe(">>>> checkSession " + checkSession); + if (checkSession && session != null && session.getUser() != null) { searchUser = authUser = (AuthenticatedUser) session.getUser(); - logger.severe(">>>> session authUser " + authUser); - logger.severe(">>>> session authUser isAuthenticated " + authUser.isAuthenticated()); if (!authUser.isAuthenticated()) { throw new WrappedResponse(authenticatedUserRequired()); } } else { searchUser = authUser = getRequestAuthenticatedUserOrDie(crc); - logger.severe(">>>> API authUser " + authUser); - logger.severe(">>>> API authUser isAuthenticated " + authUser.isAuthenticated()); } // If the user is a superuser, see if a userIdentifier has been specified and use that instead From 174c13619c275a307ea1715a976fd7b2379cba14 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:22:52 -0400 Subject: [PATCH 24/30] remove debugging --- .../edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java | 4 ---- .../edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java | 8 ++++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java index d2cf40d3b0f..f9f1379e2af 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -127,12 +127,8 @@ private String getJSONErrorString(String jsonMsg, String optionalLoggerMsg){ private void verifyAuth (ContainerRequestContext crc, String userIdentifier) throws WrappedResponse { // Handle calls from JSF where the User is in the session - logger.severe(">>>> session.getUser() " + (session != null ? session.getUser() : "null")); User requestUser = getRequestUser(crc); - logger.severe(">>>> requestUser " + requestUser); - logger.severe(">>>> FeatureFlags.API_SESSION_AUTH.enabled() " + FeatureFlags.API_SESSION_AUTH.enabled()); boolean checkSession = !FeatureFlags.API_SESSION_AUTH.enabled() && (requestUser instanceof GuestUser); - logger.severe(">>>> checkSession " + checkSession); if (checkSession && session != null && session.getUser() != null) { searchUser = authUser = (AuthenticatedUser) session.getUser(); if (!authUser.isAuthenticated()) { diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index 4c5d8438bd4..72f8fa638e1 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -207,6 +207,14 @@ public void testRetrieveMyDataCollections() throws InterruptedException { .body("message", startsWith("No user found for:")) .statusCode(NOT_FOUND.getStatusCode()); + // Unknown user gets the list of Dataverses/Collections it has access to + retrieveMyCollectionListResponse = UtilIT.retrieveMyCollectionList("badtoken", null); + retrieveMyCollectionListResponse.prettyPrint(); + retrieveMyCollectionListResponse.then().assertThat() + .body("status", equalTo("ERROR")) + .body("message", equalTo(ApiKeyAuthMechanism.RESPONSE_MESSAGE_BAD_API_KEY)) + .statusCode(UNAUTHORIZED.getStatusCode()); + // Clean up dataverses.forEach(dv -> { Response deleteDataverseResponse = UtilIT.deleteDataverse(dv, superUserApiToken); From b368b46939512fbb0b49f0a3db34777a59c23f0b Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Fri, 5 Sep 2025 13:26:54 -0400 Subject: [PATCH 25/30] change collections to dataverses --- .../edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 6d490e22598..bbc834e0cc4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -344,11 +344,11 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re return bld; } - public static JsonObjectBuilder jsonArray(List collections) { + public static JsonObjectBuilder jsonArray(List dataverses) { JsonObjectBuilder job = Json.createObjectBuilder(); - job.add("count", collections.size()); + job.add("count", dataverses.size()); JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder(); - for (Dataverse dataverse : collections) { + for (Dataverse dataverse : dataverses) { NullSafeJsonBuilder jsonObject = NullSafeJsonBuilder.jsonObjectBuilder(); jsonObject.add("id", dataverse.getId()); jsonObject.add("name", dataverse.getDisplayName()); From 2e449f3e65b207e1edab8507dba9785b26827d13 Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Fri, 5 Sep 2025 14:02:48 -0400 Subject: [PATCH 26/30] addressing review comment --- .../engine/command/impl/GetUserPermittedCollectionsCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 851d0d2e327..83974b1eba8 100644 --- 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 @@ -13,7 +13,7 @@ import java.util.List; import java.util.logging.Logger; -@RequiredPermissions({}) +@RequiredPermissions({}) // No specific permission is needed to execute this command. To filter the collection a list of permissions are passed in public class GetUserPermittedCollectionsCommand extends AbstractCommand> { private static final Logger logger = Logger.getLogger(GetUserPermittedCollectionsCommand.class.getCanonicalName()); From c792497ccdaf4466d6e9a085f6e33ef724138e5b Mon Sep 17 00:00:00 2001 From: Steven Winship <39765413+stevenwinship@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:31:26 -0400 Subject: [PATCH 27/30] Update src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetUserPermittedCollectionsCommand.java Co-authored-by: Guillermo Portas --- .../GetUserPermittedCollectionsCommand.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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 index 83974b1eba8..4cfa09d4ef2 100644 --- 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 @@ -13,7 +13,25 @@ import java.util.List; import java.util.logging.Logger; -@RequiredPermissions({}) // No specific permission is needed to execute this command. To filter the collection a list of permissions are passed in +/** + * Command that retrieves all {@link Dataverse} collections for which a given + * {@link AuthenticatedUser} has the specified permission. + *

+ * The permission is provided as a string corresponding to one of the names + * in the {@link Permission} enumeration (e.g. {@code Permission.AddDataset.name()}). + * If the special value {@code "any"} is passed, all collections for which + * the user has at least one permission are returned. + *

+ * + *

+ * Example: + *

+ * new GetUserPermittedCollectionsCommand(request, user, Permission.AddDataset.name());
+ * 
+ * will return the list of collections where the user can add datasets. + *

+ */ +@RequiredPermissions({}) public class GetUserPermittedCollectionsCommand extends AbstractCommand> { private static final Logger logger = Logger.getLogger(GetUserPermittedCollectionsCommand.class.getCanonicalName()); From 2678e69ef6ed50737ffdc9c67965a3b449e1a499 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 8 Sep 2025 15:44:50 +0100 Subject: [PATCH 28/30] Refactor: removed unused logger and applied final keyword in class level variables --- .../command/impl/GetUserPermittedCollectionsCommand.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 index 4cfa09d4ef2..62ade887a19 100644 --- 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 @@ -11,7 +11,6 @@ import edu.harvard.iq.dataverse.engine.command.exception.CommandException; import java.util.List; -import java.util.logging.Logger; /** * Command that retrieves all {@link Dataverse} collections for which a given @@ -33,11 +32,10 @@ */ @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; + private final DataverseRequest request; + private final AuthenticatedUser user; + private final String permission; public GetUserPermittedCollectionsCommand(DataverseRequest request, AuthenticatedUser user, String permission) { super(request, (DvObject) null); From cf7482e61f7d2af108d3ea272290d4e908edb0e1 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 8 Sep 2025 16:17:05 +0100 Subject: [PATCH 29/30] Added: unit tests for GetUserPermittedCollectionsCommand --- .../GetUserPermittedCollectionsCommand.java | 11 +- src/main/java/propertyFiles/Bundle.properties | 4 + ...etUserPermittedCollectionsCommandTest.java | 135 ++++++++++++++++++ 3 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 src/test/java/edu/harvard/iq/dataverse/engine/command/impl/GetUserPermittedCollectionsCommandTest.java 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 index 62ade887a19..28a924bea92 100644 --- 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 @@ -9,6 +9,8 @@ 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 edu.harvard.iq.dataverse.engine.command.exception.InvalidCommandArgumentsException; +import edu.harvard.iq.dataverse.util.BundleUtil; import java.util.List; @@ -33,6 +35,8 @@ @RequiredPermissions({}) public class GetUserPermittedCollectionsCommand extends AbstractCommand> { + public static final String ANY_PERMISSION = "any"; + private final DataverseRequest request; private final AuthenticatedUser user; private final String permission; @@ -47,14 +51,13 @@ public GetUserPermittedCollectionsCommand(DataverseRequest request, Authenticate @Override public List execute(CommandContext ctxt) throws CommandException { if (user == null) { - throw new CommandException("User not found.", this); + throw new CommandException(BundleUtil.getStringFromBundle("getUserPermittedCollectionsCommand.errors.userNotFound"), this); } int permissionBit; try { - permissionBit = permission.equalsIgnoreCase("any") ? - Integer.MAX_VALUE : (1 << Permission.valueOf(permission).ordinal()); + permissionBit = permission.equalsIgnoreCase(ANY_PERMISSION) ? Integer.MAX_VALUE : (1 << Permission.valueOf(permission).ordinal()); } catch (IllegalArgumentException e) { - throw new CommandException("Permission not valid.", this); + throw new InvalidCommandArgumentsException(BundleUtil.getStringFromBundle("getUserPermittedCollectionsCommand.errors.permissionNotValid"), this); } return ctxt.permissions().findPermittedCollections(request, user, permissionBit); } diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties index d527ba3eeeb..a07546284e0 100644 --- a/src/main/java/propertyFiles/Bundle.properties +++ b/src/main/java/propertyFiles/Bundle.properties @@ -3232,3 +3232,7 @@ abstractApiBean.error.internalVersionTimestampIsOutdated=Internal version timest #RoleAssigneeServiceBean.java roleAssigneeServiceBean.error.dataverseRequestCannotBeNull=DataverseRequest cannot be null. + +#GetUserPermittedCollectionsCommand.java +getUserPermittedCollectionsCommand.errors.userNotFound=User not found. +getUserPermittedCollectionsCommand.errors.permissionNotValid=Permission not valid. diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/GetUserPermittedCollectionsCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/GetUserPermittedCollectionsCommandTest.java new file mode 100644 index 00000000000..21b267be39c --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/GetUserPermittedCollectionsCommandTest.java @@ -0,0 +1,135 @@ +package edu.harvard.iq.dataverse.engine.command.impl; + +import edu.harvard.iq.dataverse.Dataverse; +import edu.harvard.iq.dataverse.PermissionServiceBean; +import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.engine.command.CommandContext; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.engine.command.exception.CommandException; +import edu.harvard.iq.dataverse.engine.command.exception.InvalidCommandArgumentsException; +import edu.harvard.iq.dataverse.util.BundleUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class GetUserPermittedCollectionsCommandTest { + + private DataverseRequest dataverseRequest; + private AuthenticatedUser authenticatedUser; + private CommandContext commandContext; + private PermissionServiceBean permissionsServiceBean; + + @BeforeEach + public void setUp() { + dataverseRequest = Mockito.mock(DataverseRequest.class); + authenticatedUser = Mockito.mock(AuthenticatedUser.class); + commandContext = Mockito.mock(CommandContext.class); + permissionsServiceBean = Mockito.mock(PermissionServiceBean.class); + Mockito.when(commandContext.permissions()).thenReturn(permissionsServiceBean); + } + + @Test + public void execute_shouldReturnCollections_whenAnyPermissionIsRequested() throws CommandException { + // Arrange + Dataverse dv1 = new Dataverse(); + Dataverse dv2 = new Dataverse(); + List expectedDataverses = Arrays.asList(dv1, dv2); + + Mockito.when(permissionsServiceBean.findPermittedCollections( + Mockito.any(DataverseRequest.class), + Mockito.any(AuthenticatedUser.class), + Mockito.eq(Integer.MAX_VALUE) + )).thenReturn(expectedDataverses); + + GetUserPermittedCollectionsCommand sut = new GetUserPermittedCollectionsCommand( + dataverseRequest, + authenticatedUser, + GetUserPermittedCollectionsCommand.ANY_PERMISSION + ); + + // Act + List result = sut.execute(commandContext); + + // Assert + assertEquals(expectedDataverses.size(), result.size()); + assertEquals(expectedDataverses, result); + Mockito.verify(permissionsServiceBean).findPermittedCollections( + dataverseRequest, + authenticatedUser, + Integer.MAX_VALUE + ); + } + + @Test + public void execute_shouldReturnCollections_whenSpecificPermissionIsRequested() throws CommandException { + // Arrange + Dataverse dv = new Dataverse(); + List expectedDataverses = Collections.singletonList(dv); + + Mockito.when(permissionsServiceBean.findPermittedCollections( + Mockito.any(DataverseRequest.class), + Mockito.any(AuthenticatedUser.class), + Mockito.eq(1 << Permission.AddDataset.ordinal()) + )).thenReturn(expectedDataverses); + + GetUserPermittedCollectionsCommand sut = new GetUserPermittedCollectionsCommand( + dataverseRequest, + authenticatedUser, + Permission.AddDataset.name() + ); + + // Act + List result = sut.execute(commandContext); + + // Assert + assertEquals(expectedDataverses.size(), result.size()); + assertEquals(expectedDataverses, result); + Mockito.verify(permissionsServiceBean).findPermittedCollections( + dataverseRequest, + authenticatedUser, + 1 << Permission.AddDataset.ordinal() + ); + } + + @Test + public void execute_shouldThrowException_whenUserIsNotFound() { + // Arrange + AuthenticatedUser nullUser = null; + GetUserPermittedCollectionsCommand sut = new GetUserPermittedCollectionsCommand( + dataverseRequest, + nullUser, + GetUserPermittedCollectionsCommand.ANY_PERMISSION + ); + + // Act & Assert + CommandException exception = assertThrows(CommandException.class, () -> { + sut.execute(commandContext); + }); + assertEquals(BundleUtil.getStringFromBundle("getUserPermittedCollectionsCommand.errors.userNotFound"), exception.getMessage()); + } + + @Test + public void execute_shouldThrowException_whenPermissionIsNotValid() { + // Arrange + String invalidPermission = "invalid_permission_name"; + GetUserPermittedCollectionsCommand sut = new GetUserPermittedCollectionsCommand( + dataverseRequest, + authenticatedUser, + invalidPermission + ); + + // Act & Assert + InvalidCommandArgumentsException exception = assertThrows(InvalidCommandArgumentsException.class, () -> { + sut.execute(commandContext); + }); + assertEquals(BundleUtil.getStringFromBundle("getUserPermittedCollectionsCommand.errors.permissionNotValid"), exception.getMessage()); + } +} From 801d8cc445c4c714490e6a15e64d0dbe6269f549 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 8 Sep 2025 16:21:02 +0100 Subject: [PATCH 30/30] Fixed: incorrect response code in testUserPermittedDataverses IT --- src/test/java/edu/harvard/iq/dataverse/api/UsersIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 dce4871dc16..c7ee126193c 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UsersIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UsersIT.java @@ -564,7 +564,7 @@ public void testUserPermittedDataverses() { collectionsResp = UtilIT.getUserPermittedCollections("fakeUser", superuserApiToken, "ViewUnpublishedDataset"); assertEquals(500, collectionsResp.getStatusCode()); collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, superuserApiToken, "bad"); - assertEquals(500, collectionsResp.getStatusCode()); + assertEquals(BAD_REQUEST.getStatusCode(), collectionsResp.getStatusCode()); // Testing adding an explicit permission/role to one dataverse collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, userApiToken, "DownloadFile");