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..91a3a83efa5 --- /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 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 04aaf59de98..b73d3d86c34 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -7869,3 +7869,19 @@ 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. +Param userIdentifier={userName} is used by a superuser to get the collections for a specific user. + +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" + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/mydata/retrieve/collectionList?userIdentifier=anotherUser" + diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index f1099c0a439..e00e303356e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -95,6 +95,10 @@ public class PermissionServiceBean { @Inject DatasetVersionFilesServiceBean datasetVersionFilesServiceBean; + private static final String LIST_ALL_DATAVERSES_SUPERUSER_HAS_PERMISSION = """ + SELECT id, name, alias 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 @@ -928,6 +932,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(); @@ -950,11 +955,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/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 c4888c8c99c..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 @@ -3,29 +3,44 @@ import edu.harvard.iq.dataverse.Dataverse; import edu.harvard.iq.dataverse.DvObject; import edu.harvard.iq.dataverse.authorization.Permission; -import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IpAddress; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.engine.command.AbstractCommand; import edu.harvard.iq.dataverse.engine.command.CommandContext; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import edu.harvard.iq.dataverse.engine.command.RequiredPermissions; import edu.harvard.iq.dataverse.engine.command.exception.CommandException; -import jakarta.json.Json; -import jakarta.json.JsonArrayBuilder; -import jakarta.json.JsonObjectBuilder; +import edu.harvard.iq.dataverse.engine.command.exception.InvalidCommandArgumentsException; +import edu.harvard.iq.dataverse.util.BundleUtil; import java.util.List; -import java.util.logging.Logger; - -import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json; +/** + * 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()); +public class GetUserPermittedCollectionsCommand extends AbstractCommand> { + + public static final String ANY_PERMISSION = "any"; + + private final DataverseRequest request; + private final AuthenticatedUser user; + private final String permission; - private DataverseRequest request; - private AuthenticatedUser user; - private String permission; public GetUserPermittedCollectionsCommand(DataverseRequest request, AuthenticatedUser user, String permission) { super(request, (DvObject) null); this.request = request; @@ -34,28 +49,16 @@ public GetUserPermittedCollectionsCommand(DataverseRequest request, Authenticate } @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); + 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); - } - List collections = ctxt.permissions().findPermittedCollections(request, user, permissionBit); - if (collections != null) { - JsonObjectBuilder job = Json.createObjectBuilder(); - JsonArrayBuilder jab = Json.createArrayBuilder(); - for (Dataverse dv : collections) { - jab.add(json(dv)); - } - job.add("count", collections.size()); - job.add("items", jab); - return job; + throw new InvalidCommandArgumentsException(BundleUtil.getStringFromBundle("getUserPermittedCollectionsCommand.errors.permissionNotValid"), this); } - 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 e8605da09bc..f9f1379e2af 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -3,21 +3,18 @@ */ 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.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; import edu.harvard.iq.dataverse.api.AbstractApiBean; 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; @@ -29,8 +26,10 @@ import java.util.Arrays; import java.util.List; -import java.util.Map; 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; import jakarta.json.Json; @@ -44,7 +43,8 @@ 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; +import org.json.JSONObject; /** * @@ -78,18 +78,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; + private AuthenticatedUser searchUser = null; public static final String JSON_SUCCESS_FIELD_NAME = "success"; public static final String JSON_ERROR_MSG_FIELD_NAME = "error_message"; @@ -102,49 +99,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())){ @@ -152,100 +107,9 @@ 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"); } @@ -253,14 +117,35 @@ private String getJSONErrorString(String jsonMsg, String optionalLoggerMsg){ 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 { + // Handle calls from JSF where the User is in the session + User requestUser = getRequestUser(crc); + boolean checkSession = !FeatureFlags.API_SESSION_AUTH.enabled() && (requestUser instanceof GuestUser); + if (checkSession && session != null && session.getUser() != null) { + 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())) { + searchUser = getUserFromIdentifier(userIdentifier); + if (searchUser == null) { + throw new WrappedResponse(error(Response.Status.NOT_FOUND, BundleUtil.getStringFromBundle("dataretrieverAPI.user.not.found", Arrays.asList(userIdentifier)))); + } + } + } @GET @AuthRequired @@ -276,42 +161,20 @@ 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"); - 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); + otherUser = !authUser.equals(searchUser); + } catch (WrappedResponse wr) { + 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; @@ -358,13 +221,6 @@ 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()); @@ -373,9 +229,6 @@ public String retrieveMyDataAsJsonString( logger.fine("No ids found for this search"); return this.getJSONErrorString(noMsgResultsFound, null); } - //msgt("myDataFinder.getSolrFilterQueries(): " + myDataFinder.getSolrFilterQueries().toString()); - - //msg("Selected paginationStart: " + paginationStart); try { solrQueryResponse = searchService.getDefaultSearchService().search( @@ -389,14 +242,11 @@ 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 this.getJSONErrorString(noMsgResultsFound, null); + } + } catch (SearchException ex) { solrQueryResponse = null; logger.severe("Solr SearchException: " + ex.getMessage()); @@ -404,8 +254,8 @@ public String retrieveMyDataAsJsonString( if (solrQueryResponse == null) { return this.getJSONErrorString( - BundleUtil.getStringFromBundle("dataretrieverAPI.solr.error"), - BundleUtil.getStringFromBundle("dataretrieverAPI.solr.error.opt") + BundleUtil.getStringFromBundle("dataretrieverAPI.solr.error"), + BundleUtil.getStringFromBundle("dataretrieverAPI.solr.error.opt") ); } @@ -421,16 +271,16 @@ public String 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(DataRetrieverAPI.JSON_SUCCESS_FIELD_NAME, true) - .add(DataRetrieverAPI.JSON_DATA_FIELD_NAME, + .add(DataRetrieverAPI.JSON_DATA_FIELD_NAME, Json.createObjectBuilder() .add("pagination", pager.asJsonObjectBuilderUsingCardTerms()) //.add(SearchConstants.SEARCH_API_ITEMS, this.formatSolrDocs(solrQueryResponse, filterParams, this.myDataFinder)) @@ -441,7 +291,7 @@ public String retrieveMyDataAsJsonString( .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 @@ -449,14 +299,28 @@ public String retrieveMyDataAsJsonString( // --------------------------------------------------------- //jsonData.add("total_dvobject_counts", getTotalCountsFromSolrAsJSON(searchUser, this.myDataFinder)); - - if (OTHER_USER){ + + if (otherUser){ jsonData.add("other_user", searchUser.getIdentifier()); } - + return jsonData.build().toString(); } - + + @GET + @AuthRequired + @Path(retrieveDataPartialAPIPath + "/collectionList") + @Produces("application/json") + public Response retrieveMyCollectionList(@Context ContainerRequestContext crc, @QueryParam("userIdentifier") String userIdentifier) { + try { + verifyAuth(crc, userIdentifier); + List collections = execCommand(new GetUserPermittedCollectionsCommand(createDataverseRequest(getRequestUser(crc)), searchUser, Permission.AddDataset.name())); + return ok(JsonPrinter.jsonArray(collections)); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } + } + private JsonObjectBuilder getDvObjectTypeCounts(SolrQueryResponse solrResponse) { if (solrQueryResponse == null) { @@ -528,4 +392,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/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 47b1f2cc77b..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,6 +344,21 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re return bld; } + public static JsonObjectBuilder jsonArray(List dataverses) { + JsonObjectBuilder job = Json.createObjectBuilder(); + job.add("count", dataverses.size()); + JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder(); + for (Dataverse dataverse : dataverses) { + NullSafeJsonBuilder jsonObject = NullSafeJsonBuilder.jsonObjectBuilder(); + jsonObject.add("id", dataverse.getId()); + jsonObject.add("name", dataverse.getDisplayName()); + jsonObject.add("alias", dataverse.getAlias()); + jsonArrayBuilder.add(jsonObject); + } + job.add("items", jsonArrayBuilder); + return job; + } + public static JsonArrayBuilder json(List dataverseContacts) { JsonArrayBuilder jsonArrayOfContacts = Json.createArrayBuilder(); for (DataverseContact dataverseContact : dataverseContacts) { 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/api/DataRetrieverApiIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java index c3bfdb80e6e..72f8fa638e1 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java @@ -13,11 +13,12 @@ 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.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.jupiter.api.Assertions.*; public class DataRetrieverApiIT { @@ -85,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")); @@ -104,6 +107,132 @@ 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 { + 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); + String User2ApiToken = UtilIT.getApiTokenFromResponse(createUserResponse); + // Create User3 + createUserResponse = UtilIT.createRandomUser(); + String User3Username = UtilIT.getUsernameFromResponse(createUserResponse); + String User3ApiToken = UtilIT.getApiTokenFromResponse(createUserResponse); + + // 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 < user1DataverseCount; i++) { + createDataverseResponse = UtilIT.createRandomDataverse(User1ApiToken); + String alias = UtilIT.getAliasFromResponse(createDataverseResponse); + dataverses.add(alias); + UtilIT.grantRoleOnDataverse(alias, DataverseRole.CURATOR.toString(), + "@" + User2Username, User1ApiToken); + } + // User2 adds their own Dataverse + int user2DataverseCount = 1; + createDataverseResponse = UtilIT.createRandomDataverse(User2ApiToken); + String alias = UtilIT.getAliasFromResponse(createDataverseResponse); + dataverses.add(alias); + + // Sleep for indexing + Thread.sleep(4000); + + // User1 gets the list of Dataverses/Collections it has access to + 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("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("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("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("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("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("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("status", equalTo("ERROR")) + .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); + assertEquals(200, deleteDataverseResponse.getStatusCode()); + }); + Response deleteUserResponse = UtilIT.deleteUser(User1Username); + deleteUserResponse.prettyPrint(); + deleteUserResponse = UtilIT.deleteUser(User2Username); + deleteUserResponse.prettyPrint(); + assertEquals(200, deleteUserResponse.getStatusCode()); + deleteUserResponse = UtilIT.deleteUser(User3Username); + 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/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"); 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 2f7b3d94990..df6cee7979d 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,6 @@ package edu.harvard.iq.dataverse.api; -import edu.harvard.iq.dataverse.Dataverse; +import edu.harvard.iq.dataverse.*; import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; import io.restassured.http.ContentType; import io.restassured.path.json.JsonPath; @@ -47,9 +47,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; @@ -4112,6 +4110,18 @@ static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifie return response; } + static Response retrieveMyCollectionList(String apiToken, String userIdentifier) { + RequestSpecification requestSpecification = given(); + if (apiToken != null) { + requestSpecification.header(API_TOKEN_HTTP_HEADER, apiToken); + } + if (userIdentifier != null) { + requestSpecification.queryParam("userIdentifier", userIdentifier); + } + + return requestSpecification.get("/api/mydata/retrieve/collectionList"); + } + static Response createSignedUrl(String apiToken, String apiPath, String username) { Response response = given() .header(API_TOKEN_HTTP_HEADER, apiToken) 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()); + } +} 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 b0bb3e453a4..c17529fb6ac 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 @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import edu.harvard.iq.dataverse.util.template.TemplateBuilder; + import jakarta.json.*; import edu.harvard.iq.dataverse.util.BundleUtil; @@ -479,6 +480,27 @@ 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); + } + JsonObjectBuilder job = JsonPrinter.jsonArray(collections); + JsonObject result = job.build(); + assertNotNull(result); + assertEquals(10, result.getInt("count")); + JsonArray items = result.getJsonArray("items"); + JsonObject item6 = items.getJsonObject(6); + assertEquals(6, item6.getInt("id")); + assertEquals("Alias6", item6.getString("name")); + assertEquals("alias6", item6.getString("alias")); + } + @Test public void testJsonTermsOfUseAndAccess() { // Setup a test TermsOfUseAndAccess