From acabbb40241f6489f3fe3c59bc2ee5b985c0ddf0 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 11:11:01 +0100 Subject: [PATCH 01/30] Refactor: using new filter-based auth on access endpoints --- .../edu/harvard/iq/dataverse/api/Access.java | 64 ++++++++----------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Access.java b/src/main/java/edu/harvard/iq/dataverse/api/Access.java index 3bd0a19672b..aa5682067b5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -32,6 +32,8 @@ import edu.harvard.iq.dataverse.UserNotificationServiceBean; import static edu.harvard.iq.dataverse.api.AbstractApiBean.error; import static edu.harvard.iq.dataverse.api.Datasets.handleVersion; + +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.DataverseRole; import edu.harvard.iq.dataverse.authorization.Permission; import edu.harvard.iq.dataverse.authorization.RoleAssignee; @@ -96,6 +98,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.UriInfo; @@ -444,9 +447,10 @@ public String tabularDatafileMetadata(@PathParam("fileId") String fileId, @Query * which we are going to retire. */ @Path("datafile/{fileId}/metadata/ddi") + @AuthRequired @GET @Produces({"text/xml"}) - public String tabularDatafileMetadataDDI(@PathParam("fileId") String fileId, @QueryParam("fileMetadataId") Long fileMetadataId, @QueryParam("exclude") String exclude, @QueryParam("include") String include, @Context HttpHeaders header, @Context HttpServletResponse response) throws NotFoundException, ServiceUnavailableException /*, PermissionDeniedException, AuthorizationRequiredException*/ { + public String tabularDatafileMetadataDDI(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId, @QueryParam("fileMetadataId") Long fileMetadataId, @QueryParam("exclude") String exclude, @QueryParam("include") String include, @Context HttpHeaders header, @Context HttpServletResponse response) throws NotFoundException, ServiceUnavailableException /*, PermissionDeniedException, AuthorizationRequiredException*/ { String retValue = ""; DataFile dataFile = null; @@ -461,11 +465,7 @@ public String tabularDatafileMetadataDDI(@PathParam("fileId") String fileId, @Q if (dataFile.isRestricted() || FileUtil.isActivelyEmbargoed(dataFile)) { boolean hasPermissionToDownloadFile = false; DataverseRequest dataverseRequest; - try { - dataverseRequest = createDataverseRequest(findUserOrDie()); - } catch (WrappedResponse ex) { - throw new BadRequestException("cannot find user"); - } + dataverseRequest = createDataverseRequest(getRequestUser(crc)); if (dataverseRequest != null && dataverseRequest.getUser() instanceof GuestUser) { // We must be in the UI. Try to get a non-GuestUser from the session. dataverseRequest = dvRequestService.getDataverseRequest(); @@ -656,11 +656,12 @@ public Response postDownloadDatafiles(String fileIds, @QueryParam("gbrecs") bool } @Path("dataset/{id}") + @AuthRequired @GET @Produces({"application/zip"}) - public Response downloadAllFromLatest(@PathParam("id") String datasetIdOrPersistentId, @QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WebApplicationException { + public Response downloadAllFromLatest(@Context ContainerRequestContext crc, @PathParam("id") String datasetIdOrPersistentId, @QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WebApplicationException { try { - User user = findUserOrDie(); + User user = getRequestUser(crc); DataverseRequest req = createDataverseRequest(user); final Dataset retrieved = findDatasetOrDie(datasetIdOrPersistentId); if (!(user instanceof GuestUser)) { @@ -700,11 +701,12 @@ public Response downloadAllFromLatest(@PathParam("id") String datasetIdOrPersist } @Path("dataset/{id}/versions/{versionId}") + @AuthRequired @GET @Produces({"application/zip"}) - public Response downloadAllFromVersion(@PathParam("id") String datasetIdOrPersistentId, @PathParam("versionId") String versionId, @QueryParam("gbrecs") boolean gbrecs, @QueryParam("key") String apiTokenParam, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WebApplicationException { + public Response downloadAllFromVersion(@Context ContainerRequestContext crc, @PathParam("id") String datasetIdOrPersistentId, @PathParam("versionId") String versionId, @QueryParam("gbrecs") boolean gbrecs, @QueryParam("key") String apiTokenParam, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WebApplicationException { try { - DataverseRequest req = createDataverseRequest(findUserOrDie()); + DataverseRequest req = createDataverseRequest(getRequestUser(crc)); final Dataset ds = execCommand(new GetDatasetCommand(req, findDatasetOrDie(datasetIdOrPersistentId))); DatasetVersion dsv = execCommand(handleVersion(versionId, new Datasets.DsVersionHandler>() { @@ -1337,8 +1339,9 @@ public Response deleteAuxiliaryFileWithVersion(@PathParam("fileId") Long fileId, * @return */ @PUT + @AuthRequired @Path("{id}/allowAccessRequest") - public Response allowAccessRequest(@PathParam("id") String datasetToAllowAccessId, String requestStr) { + public Response allowAccessRequest(@Context ContainerRequestContext crc, @PathParam("id") String datasetToAllowAccessId, String requestStr) { DataverseRequest dataverseRequest = null; Dataset dataset; @@ -1352,12 +1355,7 @@ public Response allowAccessRequest(@PathParam("id") String datasetToAllowAccessI boolean allowRequest = Boolean.valueOf(requestStr); - try { - dataverseRequest = createDataverseRequest(findUserOrDie()); - } catch (WrappedResponse wr) { - List args = Arrays.asList(wr.getLocalizedMessage()); - return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.fileAccess.failure.noUser", args)); - } + dataverseRequest = createDataverseRequest(getRequestUser(crc)); dataset.getOrCreateEditVersion().getTermsOfUseAndAccess().setFileAccessRequest(allowRequest); @@ -1495,8 +1493,9 @@ public Response listFileAccessRequests(@PathParam("id") String fileToRequestAcce * @return */ @PUT + @AuthRequired @Path("/datafile/{id}/grantAccess/{identifier}") - public Response grantFileAccess(@PathParam("id") String fileToRequestAccessId, @PathParam("identifier") String identifier, @Context HttpHeaders headers) { + public Response grantFileAccess(@Context ContainerRequestContext crc, @PathParam("id") String fileToRequestAccessId, @PathParam("identifier") String identifier, @Context HttpHeaders headers) { DataverseRequest dataverseRequest; DataFile dataFile; @@ -1515,12 +1514,7 @@ public Response grantFileAccess(@PathParam("id") String fileToRequestAccessId, @ return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.grantAccess.noAssigneeFound", args)); } - try { - dataverseRequest = createDataverseRequest(findUserOrDie()); - } catch (WrappedResponse wr) { - List args = Arrays.asList(identifier); - return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.fileAccess.failure.noUser", args)); - } + dataverseRequest = createDataverseRequest(getRequestUser(crc)); DataverseRole fileDownloaderRole = roleService.findBuiltinRoleByAlias(DataverseRole.FILE_DOWNLOADER); @@ -1559,8 +1553,9 @@ public Response grantFileAccess(@PathParam("id") String fileToRequestAccessId, @ * @return */ @DELETE + @AuthRequired @Path("/datafile/{id}/revokeAccess/{identifier}") - public Response revokeFileAccess(@PathParam("id") String fileToRequestAccessId, @PathParam("identifier") String identifier, @Context HttpHeaders headers) { + public Response revokeFileAccess(@Context ContainerRequestContext crc, @PathParam("id") String fileToRequestAccessId, @PathParam("identifier") String identifier, @Context HttpHeaders headers) { DataverseRequest dataverseRequest; DataFile dataFile; @@ -1572,12 +1567,7 @@ public Response revokeFileAccess(@PathParam("id") String fileToRequestAccessId, return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.requestAccess.fileNotFound", args)); } - try { - dataverseRequest = createDataverseRequest(findUserOrDie()); - } catch (WrappedResponse wr) { - List args = Arrays.asList(wr.getLocalizedMessage()); - return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.fileAccess.failure.noUser", args)); - } + dataverseRequest = createDataverseRequest(getRequestUser(crc)); if (identifier == null || identifier.equals("")) { return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.requestAccess.noKey")); @@ -1629,8 +1619,9 @@ public Response revokeFileAccess(@PathParam("id") String fileToRequestAccessId, * @return */ @PUT + @AuthRequired @Path("/datafile/{id}/rejectAccess/{identifier}") - public Response rejectFileAccess(@PathParam("id") String fileToRequestAccessId, @PathParam("identifier") String identifier, @Context HttpHeaders headers) { + public Response rejectFileAccess(@Context ContainerRequestContext crc, @PathParam("id") String fileToRequestAccessId, @PathParam("identifier") String identifier, @Context HttpHeaders headers) { DataverseRequest dataverseRequest; DataFile dataFile; @@ -1649,13 +1640,8 @@ public Response rejectFileAccess(@PathParam("id") String fileToRequestAccessId, return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.grantAccess.noAssigneeFound", args)); } - try { - dataverseRequest = createDataverseRequest(findUserOrDie()); - } catch (WrappedResponse wr) { - List args = Arrays.asList(identifier); - return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.fileAccess.failure.noUser", args)); - } - + dataverseRequest = createDataverseRequest(getRequestUser(crc)); + if (!(dataverseRequest.getAuthenticatedUser().isSuperuser() || permissionService.requestOn(dataverseRequest, dataFile).has(Permission.ManageFilePermissions))) { return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.rejectAccess.failure.noPermissions")); } From d98e8fcd27ada1a345001bdcba900e21c5ddee3d Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 11:17:02 +0100 Subject: [PATCH 02/30] Refactor: using new filter-based auth on admin endpoints --- .../edu/harvard/iq/dataverse/api/Admin.java | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index 2c147b94243..e0f6fe84eb3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -14,6 +14,7 @@ import edu.harvard.iq.dataverse.DataverseServiceBean; import edu.harvard.iq.dataverse.DataverseSession; import edu.harvard.iq.dataverse.DvObject; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.validation.EMailValidator; import edu.harvard.iq.dataverse.EjbDataverseEngine; @@ -56,6 +57,8 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder; @@ -534,22 +537,18 @@ public Response listAuthenticatedUsers() { } @GET + @AuthRequired @Path(listUsersPartialAPIPath) @Produces({ "application/json" }) public Response filterAuthenticatedUsers( + @Context ContainerRequestContext crc, @QueryParam("searchTerm") String searchTerm, @QueryParam("selectedPage") Integer selectedPage, @QueryParam("itemsPerPage") Integer itemsPerPage, @QueryParam("sortKey") String sortKey ) { - User authUser; - try { - authUser = this.findUserOrDie(); - } catch (AbstractApiBean.WrappedResponse ex) { - return error(Response.Status.FORBIDDEN, - BundleUtil.getStringFromBundle("dashboard.list_users.api.auth.invalid_apikey")); - } + User authUser = getRequestUser(crc); if (!authUser.isSuperuser()) { return error(Response.Status.FORBIDDEN, @@ -1323,23 +1322,20 @@ public Response convertUserFromBcryptToSha1(String json) { } @Path("permissions/{dvo}") + @AuthRequired @GET - public Response findPermissonsOn(@PathParam("dvo") String dvo) { + public Response findPermissonsOn(@Context ContainerRequestContext crc, @PathParam("dvo") String dvo) { try { DvObject dvObj = findDvo(dvo); if (dvObj == null) { return notFound("DvObject " + dvo + " not found"); } - try { - User aUser = findUserOrDie(); - JsonObjectBuilder bld = Json.createObjectBuilder(); - bld.add("user", aUser.getIdentifier()); - bld.add("permissions", json(permissionSvc.permissionsFor(createDataverseRequest(aUser), dvObj))); - return ok(bld); - - } catch (WrappedResponse wr) { - return wr.getResponse(); - } + User aUser = getRequestUser(crc); + JsonObjectBuilder bld = Json.createObjectBuilder(); + bld.add("user", aUser.getIdentifier()); + bld.add("permissions", json(permissionSvc.permissionsFor(createDataverseRequest(aUser), dvObj))); + return ok(bld); + } catch (Exception e) { logger.log(Level.SEVERE, "Error while testing permissions", e); return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()); @@ -1465,8 +1461,9 @@ public Response isOrcidEnabled() { } @POST + @AuthRequired @Path("{id}/reregisterHDLToPID") - public Response reregisterHdlToPID(@PathParam("id") String id) { + public Response reregisterHdlToPID(@Context ContainerRequestContext crc, @PathParam("id") String id) { logger.info("Starting to reregister " + id + " Dataset Id. (from hdl to doi)" + new Date()); try { if (settingsSvc.get(SettingsServiceBean.Key.Protocol.toString()).equals(GlobalId.HDL_PROTOCOL)) { @@ -1474,7 +1471,7 @@ public Response reregisterHdlToPID(@PathParam("id") String id) { return error(Status.BAD_REQUEST, BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure.must.be.set.for.doi")); } - User u = findUserOrDie(); + User u = getRequestUser(crc); if (!u.isSuperuser()) { logger.info("Bad Request Unauthor " ); return error(Status.UNAUTHORIZED, BundleUtil.getStringFromBundle("admin.api.auth.mustBeSuperUser")); @@ -1501,12 +1498,13 @@ public Response reregisterHdlToPID(@PathParam("id") String id) { } @GET + @AuthRequired @Path("{id}/registerDataFile") - public Response registerDataFile(@PathParam("id") String id) { + public Response registerDataFile(@Context ContainerRequestContext crc, @PathParam("id") String id) { logger.info("Starting to register " + id + " file id. " + new Date()); try { - User u = findUserOrDie(); + User u = getRequestUser(crc); DataverseRequest r = createDataverseRequest(u); DataFile df = findDataFileOrDie(id); if (df.getIdentifier() == null || df.getIdentifier().isEmpty()) { From 08cefe392fb8c03ed9407a4d4b5b5828fa03e560 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 11:18:32 +0100 Subject: [PATCH 03/30] Refactor: using new filter-based auth on missing dataset endpoint --- .../java/edu/harvard/iq/dataverse/api/Datasets.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index 50e8ec931bb..f3abe222396 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -2544,17 +2544,11 @@ public Response addFileToDataset(@Context ContainerRequestContext crc, * @return */ @GET + @AuthRequired @Path("{id}/cleanStorage") - public Response cleanStorage(@PathParam("id") String idSupplied, @QueryParam("dryrun") Boolean dryrun) { + public Response cleanStorage(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied, @QueryParam("dryrun") Boolean dryrun) { // get user and dataset - User authUser; - try { - authUser = findUserOrDie(); - } catch (WrappedResponse ex) { - return error(Response.Status.FORBIDDEN, - BundleUtil.getStringFromBundle("file.addreplace.error.auth") - ); - } + User authUser = getRequestUser(crc); Dataset dataset; try { From 306116a8a0fe3e460baec1f9e7264b994059920d Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 11:28:52 +0100 Subject: [PATCH 04/30] Refactor: using new filter-based auth on dataverses endpoints --- .../harvard/iq/dataverse/api/Dataverses.java | 92 +++++++++++-------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java index 90130cb3944..e0a642ef700 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -9,6 +9,7 @@ import edu.harvard.iq.dataverse.DataverseContact; import edu.harvard.iq.dataverse.DataverseMetadataBlockFacet; import edu.harvard.iq.dataverse.DataverseServiceBean; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.api.datadeposit.SwordServiceBean; import edu.harvard.iq.dataverse.api.dto.DataverseMetadataBlockFacetDTO; import edu.harvard.iq.dataverse.authorization.DataverseRole; @@ -104,6 +105,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -224,12 +226,13 @@ public Response addDataverse(String body, @PathParam("identifier") String parent } @POST + @AuthRequired @Path("{identifier}/datasets") @Consumes("application/json") - public Response createDataset(String jsonBody, @PathParam("identifier") String parentIdtf) { + public Response createDataset(@Context ContainerRequestContext crc, String jsonBody, @PathParam("identifier") String parentIdtf) { try { logger.fine("Json is: " + jsonBody); - User u = findUserOrDie(); + User u = getRequestUser(crc); Dataverse owner = findDataverseOrDie(parentIdtf); Dataset ds = parseDataset(jsonBody); ds.setOwner(owner); @@ -292,11 +295,12 @@ public Response createDataset(String jsonBody, @PathParam("identifier") String p } @POST + @AuthRequired @Path("{identifier}/datasets") @Consumes("application/ld+json, application/json-ld") - public Response createDatasetFromJsonLd(String jsonLDBody, @PathParam("identifier") String parentIdtf) { + public Response createDatasetFromJsonLd(@Context ContainerRequestContext crc, String jsonLDBody, @PathParam("identifier") String parentIdtf) { try { - User u = findUserOrDie(); + User u = getRequestUser(crc); Dataverse owner = findDataverseOrDie(parentIdtf); Dataset ds = new Dataset(); @@ -334,10 +338,11 @@ public Response createDatasetFromJsonLd(String jsonLDBody, @PathParam("identifie } @POST + @AuthRequired @Path("{identifier}/datasets/:import") - public Response importDataset(String jsonBody, @PathParam("identifier") String parentIdtf, @QueryParam("pid") String pidParam, @QueryParam("release") String releaseParam) { + public Response importDataset(@Context ContainerRequestContext crc, String jsonBody, @PathParam("identifier") String parentIdtf, @QueryParam("pid") String pidParam, @QueryParam("release") String releaseParam) { try { - User u = findUserOrDie(); + User u = getRequestUser(crc); if (!u.isSuperuser()) { return error(Status.FORBIDDEN, "Not a superuser"); } @@ -408,10 +413,11 @@ public Response importDataset(String jsonBody, @PathParam("identifier") String p // TODO decide if I merge importddi with import just below (xml and json on same api, instead of 2 api) @POST + @AuthRequired @Path("{identifier}/datasets/:importddi") - public Response importDatasetDdi(String xml, @PathParam("identifier") String parentIdtf, @QueryParam("pid") String pidParam, @QueryParam("release") String releaseParam) { + public Response importDatasetDdi(@Context ContainerRequestContext crc, String xml, @PathParam("identifier") String parentIdtf, @QueryParam("pid") String pidParam, @QueryParam("release") String releaseParam) { try { - User u = findUserOrDie(); + User u = getRequestUser(crc); if (!u.isSuperuser()) { return error(Status.FORBIDDEN, "Not a superuser"); } @@ -482,11 +488,12 @@ public Response importDatasetDdi(String xml, @PathParam("identifier") String par } @POST + @AuthRequired @Path("{identifier}/datasets/:startmigration") @Consumes("application/ld+json, application/json-ld") - public Response recreateDataset(String jsonLDBody, @PathParam("identifier") String parentIdtf) { + public Response recreateDataset(@Context ContainerRequestContext crc, String jsonLDBody, @PathParam("identifier") String parentIdtf) { try { - User u = findUserOrDie(); + User u = getRequestUser(crc); if (!u.isSuperuser()) { return error(Status.FORBIDDEN, "Not a superuser"); } @@ -574,11 +581,12 @@ public Response deleteDataverseLinkingDataverse(@PathParam("linkingDataverseId") } @GET + @AuthRequired @Path("{identifier}/metadatablocks") - public Response listMetadataBlocks(@PathParam("identifier") String dvIdtf) { + public Response listMetadataBlocks(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) { try { JsonArrayBuilder arr = Json.createArrayBuilder(); - final List blocks = execCommand(new ListMetadataBlocksCommand(createDataverseRequest(findUserOrDie()), findDataverseOrDie(dvIdtf))); + final List blocks = execCommand(new ListMetadataBlocksCommand(createDataverseRequest(getRequestUser(crc)), findDataverseOrDie(dvIdtf))); for (MetadataBlock mdb : blocks) { arr.add(brief.json(mdb)); } @@ -589,9 +597,10 @@ public Response listMetadataBlocks(@PathParam("identifier") String dvIdtf) { } @POST + @AuthRequired @Path("{identifier}/metadatablocks") @Produces(MediaType.APPLICATION_JSON) - public Response setMetadataBlocks(@PathParam("identifier") String dvIdtf, String blockIds) { + public Response setMetadataBlocks(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String blockIds) { List blocks = new LinkedList<>(); try { @@ -609,7 +618,7 @@ public Response setMetadataBlocks(@PathParam("identifier") String dvIdtf, String } try { - execCommand(new UpdateDataverseMetadataBlocksCommand.SetBlocks(createDataverseRequest(findUserOrDie()), findDataverseOrDie(dvIdtf), blocks)); + execCommand(new UpdateDataverseMetadataBlocksCommand.SetBlocks(createDataverseRequest(getRequestUser(crc)), findDataverseOrDie(dvIdtf), blocks)); return ok("Metadata blocks of dataverse " + dvIdtf + " updated."); } catch (WrappedResponse ex) { @@ -661,13 +670,14 @@ public Response setMetadataRoot(@PathParam("identifier") String dvIdtf, String b } @GET + @AuthRequired @Path("{identifier}/facets/") /** * return list of facets for the dataverse with alias `dvIdtf` */ - public Response listFacets(@PathParam("identifier") String dvIdtf) { + public Response listFacets(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) { try { - User u = findUserOrDie(); + User u = getRequestUser(crc); DataverseRequest r = createDataverseRequest(u); Dataverse dataverse = findDataverseOrDie(dvIdtf); JsonArrayBuilder fs = Json.createArrayBuilder(); @@ -681,6 +691,7 @@ public Response listFacets(@PathParam("identifier") String dvIdtf) { } @POST + @AuthRequired @Path("{identifier}/facets") @Produces(MediaType.APPLICATION_JSON) /** @@ -690,7 +701,7 @@ public Response listFacets(@PathParam("identifier") String dvIdtf) { * where foo.json contains a list of datasetField names, works as expected * (judging by the UI). This triggers a 500 when '-d @foo.json' is used. */ - public Response setFacets(@PathParam("identifier") String dvIdtf, String facetIds) { + public Response setFacets(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String facetIds) { List facets = new LinkedList<>(); for (JsonString facetId : Util.asJsonArray(facetIds).getValuesAs(JsonString.class)) { @@ -706,7 +717,7 @@ public Response setFacets(@PathParam("identifier") String dvIdtf, String facetId try { Dataverse dataverse = findDataverseOrDie(dvIdtf); // by passing null for Featured Dataverses and DataverseFieldTypeInputLevel, those are not changed - execCommand(new UpdateDataverseCommand(dataverse, facets, null, createDataverseRequest(findUserOrDie()), null)); + execCommand(new UpdateDataverseCommand(dataverse, facets, null, createDataverseRequest(getRequestUser(crc)), null)); return ok("Facets of dataverse " + dvIdtf + " updated."); } catch (WrappedResponse ex) { @@ -715,11 +726,12 @@ public Response setFacets(@PathParam("identifier") String dvIdtf, String facetId } @GET + @AuthRequired @Path("{identifier}/metadatablockfacets") @Produces(MediaType.APPLICATION_JSON) - public Response listMetadataBlockFacets(@PathParam("identifier") String dvIdtf) { + public Response listMetadataBlockFacets(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) { try { - User u = findUserOrDie(); + User u = getRequestUser(crc); DataverseRequest request = createDataverseRequest(u); Dataverse dataverse = findDataverseOrDie(dvIdtf); List metadataBlockFacets = Optional.ofNullable(execCommand(new ListMetadataBlockFacetsCommand(request, dataverse))).orElse(Collections.emptyList()); @@ -734,10 +746,11 @@ public Response listMetadataBlockFacets(@PathParam("identifier") String dvIdtf) } @POST + @AuthRequired @Path("{identifier}/metadatablockfacets") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public Response setMetadataBlockFacets(@PathParam("identifier") String dvIdtf, List metadataBlockNames) { + public Response setMetadataBlockFacets(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, List metadataBlockNames) { try { Dataverse dataverse = findDataverseOrDie(dvIdtf); @@ -758,7 +771,7 @@ public Response setMetadataBlockFacets(@PathParam("identifier") String dvIdtf, L metadataBlockFacets.add(metadataBlockFacet); } - execCommand(new UpdateMetadataBlockFacetsCommand(createDataverseRequest(findUserOrDie()), dataverse, metadataBlockFacets)); + execCommand(new UpdateMetadataBlockFacetsCommand(createDataverseRequest(getRequestUser(crc)), dataverse, metadataBlockFacets)); return ok(String.format("Metadata block facets updated. DataverseId: %s blocks: %s", dvIdtf, metadataBlockNames)); } catch (WrappedResponse ex) { @@ -767,10 +780,11 @@ public Response setMetadataBlockFacets(@PathParam("identifier") String dvIdtf, L } @POST + @AuthRequired @Path("{identifier}/metadatablockfacets/isRoot") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public Response updateMetadataBlockFacetsRoot(@PathParam("identifier") String dvIdtf, String body) { + public Response updateMetadataBlockFacetsRoot(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String body) { try { final boolean blockFacetsRoot = parseBooleanOrDie(body); Dataverse dataverse = findDataverseOrDie(dvIdtf); @@ -778,7 +792,7 @@ public Response updateMetadataBlockFacetsRoot(@PathParam("identifier") String dv return ok(String.format("No update needed, dataverse already consistent with new value. DataverseId: %s blockFacetsRoot: %s", dvIdtf, blockFacetsRoot)); } - execCommand(new UpdateMetadataBlockFacetRootCommand(createDataverseRequest(findUserOrDie()), dataverse, blockFacetsRoot)); + execCommand(new UpdateMetadataBlockFacetRootCommand(createDataverseRequest(getRequestUser(crc)), dataverse, blockFacetsRoot)); return ok(String.format("Metadata block facets root updated. DataverseId: %s blockFacetsRoot: %s", dvIdtf, blockFacetsRoot)); } catch (WrappedResponse ex) { @@ -947,11 +961,12 @@ public Response listAssignments(@PathParam("identifier") String dvIdtf) { // } // } @POST + @AuthRequired @Path("{identifier}/assignments") - public Response createAssignment(RoleAssignmentDTO ra, @PathParam("identifier") String dvIdtf, @QueryParam("key") String apiKey) { + public Response createAssignment(@Context ContainerRequestContext crc, RoleAssignmentDTO ra, @PathParam("identifier") String dvIdtf, @QueryParam("key") String apiKey) { try { - final DataverseRequest req = createDataverseRequest(findUserOrDie()); + final DataverseRequest req = createDataverseRequest(getRequestUser(crc)); final Dataverse dataverse = findDataverseOrDie(dvIdtf); RoleAssignee assignee = findAssignee(ra.getAssignee()); @@ -985,13 +1000,14 @@ public Response createAssignment(RoleAssignmentDTO ra, @PathParam("identifier") } @DELETE + @AuthRequired @Path("{identifier}/assignments/{id}") - public Response deleteAssignment(@PathParam("id") long assignmentId, @PathParam("identifier") String dvIdtf) { + public Response deleteAssignment(@Context ContainerRequestContext crc, @PathParam("id") long assignmentId, @PathParam("identifier") String dvIdtf) { RoleAssignment ra = em.find(RoleAssignment.class, assignmentId); if (ra != null) { try { findDataverseOrDie(dvIdtf); - execCommand(new RevokeRoleCommand(ra, createDataverseRequest(findUserOrDie()))); + execCommand(new RevokeRoleCommand(ra, createDataverseRequest(getRequestUser(crc)))); return ok("Role " + ra.getRole().getName() + " revoked for assignee " + ra.getAssigneeIdentifier() + " in " + ra.getDefinitionPoint().accept(DvObject.NamePrinter)); @@ -1049,13 +1065,14 @@ public Response getGroupByOwnerAndAliasInOwner(@PathParam("identifier") String d } @GET + @AuthRequired @Path("{identifier}/guestbookResponses/") - public Response getGuestbookResponsesByDataverse(@PathParam("identifier") String dvIdtf, + public Response getGuestbookResponsesByDataverse(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("guestbookId") Long gbId, @Context HttpServletResponse response) { try { Dataverse dv = findDataverseOrDie(dvIdtf); - User u = findUserOrDie(); + User u = getRequestUser(crc); DataverseRequest req = createDataverseRequest(u); if (permissionSvc.request(req) .on(dv) @@ -1199,10 +1216,11 @@ private ExplicitGroup findExplicitGroupOrDie(DvObject dv, DataverseRequest req, } @GET + @AuthRequired @Path("{identifier}/links") - public Response listLinks(@PathParam("identifier") String dvIdtf) { + public Response listLinks(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) { try { - User u = findUserOrDie(); + User u = getRequestUser(crc); Dataverse dv = findDataverseOrDie(dvIdtf); if (!u.isSuperuser()) { return error(Status.FORBIDDEN, "Not a superuser"); @@ -1238,10 +1256,11 @@ public Response listLinks(@PathParam("identifier") String dvIdtf) { } @POST + @AuthRequired @Path("{id}/move/{targetDataverseAlias}") - public Response moveDataverse(@PathParam("id") String id, @PathParam("targetDataverseAlias") String targetDataverseAlias, @QueryParam("forceMove") Boolean force) { + public Response moveDataverse(@Context ContainerRequestContext crc, @PathParam("id") String id, @PathParam("targetDataverseAlias") String targetDataverseAlias, @QueryParam("forceMove") Boolean force) { try { - User u = findUserOrDie(); + User u = getRequestUser(crc); Dataverse dv = findDataverseOrDie(id); Dataverse target = findDataverseOrDie(targetDataverseAlias); if (target == null) { @@ -1257,10 +1276,11 @@ public Response moveDataverse(@PathParam("id") String id, @PathParam("targetData } @PUT + @AuthRequired @Path("{linkedDataverseAlias}/link/{linkingDataverseAlias}") - public Response linkDataverse(@PathParam("linkedDataverseAlias") String linkedDataverseAlias, @PathParam("linkingDataverseAlias") String linkingDataverseAlias) { + public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam("linkedDataverseAlias") String linkedDataverseAlias, @PathParam("linkingDataverseAlias") String linkingDataverseAlias) { try { - User u = findUserOrDie(); + User u = getRequestUser(crc); Dataverse linked = findDataverseOrDie(linkedDataverseAlias); Dataverse linking = findDataverseOrDie(linkingDataverseAlias); if (linked == null) { From b3921a45b0360906eff808a9f838a4aaa130a26d Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 12:43:54 +0100 Subject: [PATCH 05/30] Refactor: using new filter-based auth on files endpoints --- .../edu/harvard/iq/dataverse/api/Files.java | 108 +++++++++--------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index 6319f87f50a..d224ab5ab5e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -14,6 +14,7 @@ import edu.harvard.iq.dataverse.FileMetadata; import edu.harvard.iq.dataverse.TermsOfUseAndAccessValidator; import edu.harvard.iq.dataverse.UserNotificationServiceBean; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.users.ApiToken; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.User; @@ -63,6 +64,7 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; @@ -120,8 +122,9 @@ private void msgt(String m){ * @return */ @PUT + @AuthRequired @Path("{id}/restrict") - public Response restrictFileInDataset(@PathParam("id") String fileToRestrictId, String restrictStr) { + public Response restrictFileInDataset(@Context ContainerRequestContext crc, @PathParam("id") String fileToRestrictId, String restrictStr) { //create request DataverseRequest dataverseRequest = null; //get the datafile @@ -133,12 +136,8 @@ public Response restrictFileInDataset(@PathParam("id") String fileToRestrictId, } boolean restrict = Boolean.valueOf(restrictStr); - - try { - dataverseRequest = createDataverseRequest(findUserOrDie()); - } catch (WrappedResponse wr) { - return error(BAD_REQUEST, "Couldn't find user to execute command: " + wr.getLocalizedMessage()); - } + + dataverseRequest = createDataverseRequest(getRequestUser(crc)); // try to restrict the datafile try { @@ -177,9 +176,11 @@ public Response restrictFileInDataset(@PathParam("id") String fileToRestrictId, * @return */ @POST + @AuthRequired @Path("{id}/replace") @Consumes(MediaType.MULTIPART_FORM_DATA) public Response replaceFileInDataset( + @Context ContainerRequestContext crc, @PathParam("id") String fileIdOrPersistentId, @FormDataParam("jsonData") String jsonData, @FormDataParam("file") InputStream testFileInputStream, @@ -190,15 +191,8 @@ public Response replaceFileInDataset( if (!systemConfig.isHTTPUpload()) { return error(Response.Status.SERVICE_UNAVAILABLE, BundleUtil.getStringFromBundle("file.api.httpDisabled")); } - // (1) Get the user from the API key - User authUser; - try { - authUser = findUserOrDie(); - } catch (AbstractApiBean.WrappedResponse ex) { - return error(Response.Status.FORBIDDEN, - BundleUtil.getStringFromBundle("file.addreplace.error.auth") - ); - } + // (1) Get the user from the ContainerRequestContext + User authUser = getRequestUser(crc); // (2) Check/Parse the JSON (if uploaded) Boolean forceReplace = false; @@ -321,8 +315,9 @@ public Response replaceFileInDataset( //Much of this code is taken from the replace command, //simplified as we aren't actually switching files @POST + @AuthRequired @Path("{id}/metadata") - public Response updateFileMetadata(@FormDataParam("jsonData") String jsonData, + public Response updateFileMetadata(@Context ContainerRequestContext crc, @FormDataParam("jsonData") String jsonData, @PathParam("id") String fileIdOrPersistentId ) throws DataFileTagException, CommandException { @@ -331,7 +326,7 @@ public Response updateFileMetadata(@FormDataParam("jsonData") String jsonData, try { DataverseRequest req; try { - req = createDataverseRequest(findUserOrDie()); + req = createDataverseRequest(getRequestUser(crc)); } catch (Exception e) { return error(BAD_REQUEST, "Error attempting to request information. Maybe a bad API token?"); } @@ -342,8 +337,6 @@ public Response updateFileMetadata(@FormDataParam("jsonData") String jsonData, return error(BAD_REQUEST, "Error attempting get the requested data file."); } - - User authUser = findUserOrDie(); //You shouldn't be trying to edit a datafile that has been replaced List result = em.createNamedQuery("DataFile.findDataFileThatReplacedId", Long.class) @@ -513,13 +506,14 @@ private Response getFileDataResponse(String fileIdOrPersistentId, UriInfo uriInf .build(); } - @GET + @GET + @AuthRequired @Path("{id}/metadata") - public Response getFileMetadata(@PathParam("id") String fileIdOrPersistentId, @PathParam("versionId") String versionId, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response, Boolean getDraft) throws WrappedResponse, Exception { + public Response getFileMetadata(@Context ContainerRequestContext crc, @PathParam("id") String fileIdOrPersistentId, @PathParam("versionId") String versionId, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response, Boolean getDraft) throws WrappedResponse, Exception { //ToDo - versionId is not used - can't get metadata for earlier versions DataverseRequest req; try { - req = createDataverseRequest(findUserOrDie()); + req = createDataverseRequest(getRequestUser(crc)); } catch (Exception e) { return error(BAD_REQUEST, "Error attempting to request information. Maybe a bad API token?"); } @@ -555,15 +549,17 @@ public Response getFileMetadata(@PathParam("id") String fileIdOrPersistentId, @P .build(); } - @GET + @GET + @AuthRequired @Path("{id}/metadata/draft") - public Response getFileMetadataDraft(@PathParam("id") String fileIdOrPersistentId, @PathParam("versionId") String versionId, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response, Boolean getDraft) throws WrappedResponse, Exception { - return getFileMetadata(fileIdOrPersistentId, versionId, uriInfo, headers, response, true); + public Response getFileMetadataDraft(@Context ContainerRequestContext crc, @PathParam("id") String fileIdOrPersistentId, @PathParam("versionId") String versionId, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response, Boolean getDraft) throws WrappedResponse, Exception { + return getFileMetadata(crc, fileIdOrPersistentId, versionId, uriInfo, headers, response, true); } - @Path("{id}/uningest") @POST - public Response uningestDatafile(@PathParam("id") String id) { + @AuthRequired + @Path("{id}/uningest") + public Response uningestDatafile(@Context ContainerRequestContext crc, @PathParam("id") String id) { DataFile dataFile; try { @@ -580,7 +576,7 @@ public Response uningestDatafile(@PathParam("id") String id) { } try { - DataverseRequest req = createDataverseRequest(findUserOrDie()); + DataverseRequest req = createDataverseRequest(getRequestUser(crc)); execCommand(new UningestFileCommand(req, dataFile)); Long dataFileId = dataFile.getId(); dataFile = fileService.find(dataFileId); @@ -592,7 +588,7 @@ public Response uningestDatafile(@PathParam("id") String id) { } } - + // reingest attempts to queue an *existing* DataFile // for tabular ingest. It can be used on non-tabular datafiles; to try to // ingest a file that has previously failed ingest, or to ingest a file of a @@ -670,13 +666,14 @@ public Response reingest(@PathParam("id") String id) { } - @Path("{id}/redetect") @POST - public Response redetectDatafile(@PathParam("id") String id, @QueryParam("dryRun") boolean dryRun) { + @AuthRequired + @Path("{id}/redetect") + public Response redetectDatafile(@Context ContainerRequestContext crc, @PathParam("id") String id, @QueryParam("dryRun") boolean dryRun) { try { DataFile dataFileIn = findDataFileOrDie(id); String originalContentType = dataFileIn.getContentType(); - DataFile dataFileOut = execCommand(new RedetectFileTypeCommand(createDataverseRequest(findUserOrDie()), dataFileIn, dryRun)); + DataFile dataFileOut = execCommand(new RedetectFileTypeCommand(createDataverseRequest(getRequestUser(crc)), dataFileIn, dryRun)); NullSafeJsonBuilder result = NullSafeJsonBuilder.jsonObjectBuilder() .add("dryRun", dryRun) .add("oldContentType", originalContentType) @@ -734,33 +731,30 @@ private void exportDatasetMetadata(SettingsServiceBean settingsServiceBean, Data // This supports the cases where a tool is accessing a restricted resource (e.g. // preview of a draft file), or public case. @GET + @AuthRequired @Path("{id}/metadata/{fmid}/toolparams/{tid}") - public Response getExternalToolFMParams(@PathParam("tid") long externalToolId, + public Response getExternalToolFMParams(@Context ContainerRequestContext crc, @PathParam("tid") long externalToolId, @PathParam("id") String fileId, @PathParam("fmid") long fmid, @QueryParam(value = "locale") String locale) { - try { - ExternalTool externalTool = externalToolService.findById(externalToolId); - if(externalTool == null) { - return error(BAD_REQUEST, "External tool not found."); - } - if (!ExternalTool.Scope.FILE.equals(externalTool.getScope())) { - return error(BAD_REQUEST, "External tool does not have file scope."); - } - ApiToken apiToken = null; - User u = findUserOrDie(); - if (u instanceof AuthenticatedUser) { - apiToken = authSvc.findApiTokenByUser((AuthenticatedUser) u); - } - FileMetadata target = fileSvc.findFileMetadata(fmid); - if (target == null) { - return error(BAD_REQUEST, "FileMetadata not found."); - } + ExternalTool externalTool = externalToolService.findById(externalToolId); + if(externalTool == null) { + return error(BAD_REQUEST, "External tool not found."); + } + if (!ExternalTool.Scope.FILE.equals(externalTool.getScope())) { + return error(BAD_REQUEST, "External tool does not have file scope."); + } + ApiToken apiToken = null; + User u = getRequestUser(crc); + if (u instanceof AuthenticatedUser) { + apiToken = authSvc.findApiTokenByUser((AuthenticatedUser) u); + } + FileMetadata target = fileSvc.findFileMetadata(fmid); + if (target == null) { + return error(BAD_REQUEST, "FileMetadata not found."); + } - ExternalToolHandler eth = null; + ExternalToolHandler eth = null; - eth = new ExternalToolHandler(externalTool, target.getDataFile(), apiToken, target, locale); - return ok(eth.createPostBody(eth.getParams(JsonUtil.getJsonObject(externalTool.getToolParameters())))); - } catch (WrappedResponse wr) { - return wr.getResponse(); - } + eth = new ExternalToolHandler(externalTool, target.getDataFile(), apiToken, target, locale); + return ok(eth.createPostBody(eth.getParams(JsonUtil.getJsonObject(externalTool.getToolParameters())))); } } From e31b894151a174188c330530e6e962a4092f9250 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 12:45:34 +0100 Subject: [PATCH 06/30] Fixed: access endpoint auth arguments --- src/main/java/edu/harvard/iq/dataverse/api/Access.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Access.java b/src/main/java/edu/harvard/iq/dataverse/api/Access.java index aa5682067b5..87dd427fcf0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -435,11 +435,12 @@ public Response datafile(@PathParam("fileId") String fileId, @QueryParam("gbrecs // Metadata format defaults to DDI: - @Path("datafile/{fileId}/metadata") @GET + @AuthRequired + @Path("datafile/{fileId}/metadata") @Produces({"text/xml"}) - public String tabularDatafileMetadata(@PathParam("fileId") String fileId, @QueryParam("fileMetadataId") Long fileMetadataId, @QueryParam("exclude") String exclude, @QueryParam("include") String include, @Context HttpHeaders header, @Context HttpServletResponse response) throws NotFoundException, ServiceUnavailableException /*, PermissionDeniedException, AuthorizationRequiredException*/ { - return tabularDatafileMetadataDDI(fileId, fileMetadataId, exclude, include, header, response); + public String tabularDatafileMetadata(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId, @QueryParam("fileMetadataId") Long fileMetadataId, @QueryParam("exclude") String exclude, @QueryParam("include") String include, @Context HttpHeaders header, @Context HttpServletResponse response) throws NotFoundException, ServiceUnavailableException /*, PermissionDeniedException, AuthorizationRequiredException*/ { + return tabularDatafileMetadataDDI(crc, fileId, fileMetadataId, exclude, include, header, response); } /* From 1e934b486636d5c155a59b7fa51e56f1f7c86443 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 12:50:10 +0100 Subject: [PATCH 07/30] Refactor: using new filter-based auth on harvesting clients endpoints --- .../iq/dataverse/api/HarvestingClients.java | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/HarvestingClients.java b/src/main/java/edu/harvard/iq/dataverse/api/HarvestingClients.java index 9aea3adab8b..d6dbb5370ad 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/HarvestingClients.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/HarvestingClients.java @@ -2,6 +2,7 @@ import edu.harvard.iq.dataverse.Dataverse; import edu.harvard.iq.dataverse.DataverseServiceBean; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.harvest.client.HarvestingClient; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; @@ -34,6 +35,8 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; @Path("harvest/clients") @@ -55,8 +58,9 @@ public class HarvestingClients extends AbstractApiBean { * optionally, plain text output may be provided as well. */ @GET + @AuthRequired @Path("/") - public Response harvestingClients(@QueryParam("key") String apiKey) throws IOException { + public Response harvestingClients(@Context ContainerRequestContext crc, @QueryParam("key") String apiKey) throws IOException { List harvestingClients = null; try { @@ -80,7 +84,7 @@ public Response harvestingClients(@QueryParam("key") String apiKey) throws IOExc // the permission to view this harvesting client config. -- L.A. 4.4 HarvestingClient retrievedHarvestingClient = null; try { - DataverseRequest req = createDataverseRequest(findUserOrDie()); + DataverseRequest req = createDataverseRequest(getRequestUser(crc)); retrievedHarvestingClient = execCommand( new GetHarvestingClientCommand(req, harvestingClient)); } catch (Exception ex) { // Don't do anything. @@ -97,8 +101,9 @@ public Response harvestingClients(@QueryParam("key") String apiKey) throws IOExc } @GET + @AuthRequired @Path("{nickName}") - public Response harvestingClient(@PathParam("nickName") String nickName, @QueryParam("key") String apiKey) throws IOException { + public Response harvestingClient(@Context ContainerRequestContext crc, @PathParam("nickName") String nickName, @QueryParam("key") String apiKey) throws IOException { HarvestingClient harvestingClient = null; try { @@ -122,7 +127,7 @@ public Response harvestingClient(@PathParam("nickName") String nickName, @QueryP // findUserOrDie() and execCommand() both throw WrappedResponse // exception, that already has a proper HTTP response in it. - retrievedHarvestingClient = execCommand(new GetHarvestingClientCommand(createDataverseRequest(findUserOrDie()), harvestingClient)); + retrievedHarvestingClient = execCommand(new GetHarvestingClientCommand(createDataverseRequest(getRequestUser(crc)), harvestingClient)); logger.fine("retrieved Harvesting Client " + retrievedHarvestingClient.getName() + " with the GetHarvestingClient command."); } catch (WrappedResponse wr) { return wr.getResponse(); @@ -146,12 +151,13 @@ public Response harvestingClient(@PathParam("nickName") String nickName, @QueryP } @POST + @AuthRequired @Path("{nickName}") - public Response createHarvestingClient(String jsonBody, @PathParam("nickName") String nickName, @QueryParam("key") String apiKey) throws IOException, JsonParseException { + public Response createHarvestingClient(@Context ContainerRequestContext crc, String jsonBody, @PathParam("nickName") String nickName, @QueryParam("key") String apiKey) throws IOException, JsonParseException { // Per the discussion during the QA of PR #9174, we decided to make // the create/edit APIs superuser-only (the delete API was already so) try { - User u = findUserOrDie(); + User u = getRequestUser(crc); if ((!(u instanceof AuthenticatedUser) || !u.isSuperuser())) { throw new WrappedResponse(error(Response.Status.UNAUTHORIZED, "Only superusers can create harvesting clients.")); } @@ -215,7 +221,7 @@ public Response createHarvestingClient(String jsonBody, @PathParam("nickName") S } ownerDataverse.getHarvestingClientConfigs().add(harvestingClient); - DataverseRequest req = createDataverseRequest(findUserOrDie()); + DataverseRequest req = createDataverseRequest(getRequestUser(crc)); harvestingClient = execCommand(new CreateHarvestingClientCommand(req, harvestingClient)); return created( "/harvest/clients/" + nickName, JsonPrinter.json(harvestingClient)); @@ -230,10 +236,11 @@ public Response createHarvestingClient(String jsonBody, @PathParam("nickName") S } @PUT + @AuthRequired @Path("{nickName}") - public Response modifyHarvestingClient(String jsonBody, @PathParam("nickName") String nickName, @QueryParam("key") String apiKey) throws IOException, JsonParseException { + public Response modifyHarvestingClient(@Context ContainerRequestContext crc, String jsonBody, @PathParam("nickName") String nickName, @QueryParam("key") String apiKey) throws IOException, JsonParseException { try { - User u = findUserOrDie(); + User u = getRequestUser(crc); if ((!(u instanceof AuthenticatedUser) || !u.isSuperuser())) { throw new WrappedResponse(error(Response.Status.UNAUTHORIZED, "Only superusers can modify harvesting clients.")); } @@ -256,7 +263,7 @@ public Response modifyHarvestingClient(String jsonBody, @PathParam("nickName") S String ownerDataverseAlias = harvestingClient.getDataverse().getAlias(); try ( StringReader rdr = new StringReader(jsonBody) ) { - DataverseRequest req = createDataverseRequest(findUserOrDie()); + DataverseRequest req = createDataverseRequest(getRequestUser(crc)); JsonObject json = Json.createReader(rdr).readObject(); HarvestingClient newHarvestingClient = new HarvestingClient(); @@ -309,15 +316,16 @@ public Response modifyHarvestingClient(String jsonBody, @PathParam("nickName") S } @DELETE + @AuthRequired @Path("{nickName}") - public Response deleteHarvestingClient(@PathParam("nickName") String nickName) throws IOException { + public Response deleteHarvestingClient(@Context ContainerRequestContext crc, @PathParam("nickName") String nickName) throws IOException { // Deleting a client can take a while (if there's a large amnount of // harvested content associated with it). So instead of calling the command // directly, we will be calling an async. service bean method. try { - User u = findUserOrDie(); + User u = getRequestUser(crc); if ((!(u instanceof AuthenticatedUser) || !u.isSuperuser())) { throw new WrappedResponse(error(Response.Status.UNAUTHORIZED, "Only superusers can delete harvesting clients.")); } From 92086b3163dbd98f014775b63973770f524d197c Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 12:58:29 +0100 Subject: [PATCH 08/30] Refactor: using new filter-based auth on notifications endpoints --- .../iq/dataverse/api/Notifications.java | 107 +++++------------- 1 file changed, 27 insertions(+), 80 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Notifications.java b/src/main/java/edu/harvard/iq/dataverse/api/Notifications.java index c477788cae6..006a95d85a5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Notifications.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Notifications.java @@ -3,6 +3,7 @@ import edu.harvard.iq.dataverse.MailServiceBean; import edu.harvard.iq.dataverse.UserNotification; import edu.harvard.iq.dataverse.UserNotification.Type; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.workflows.WorkflowUtil; @@ -20,6 +21,8 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import edu.harvard.iq.dataverse.util.MailUtil; @@ -34,17 +37,10 @@ public class Notifications extends AbstractApiBean { MailServiceBean mailService; @GET + @AuthRequired @Path("/all") - public Response getAllNotificationsForUser() { - User user; - try { - user = findUserOrDie(); - } catch (WrappedResponse ex) { - return error(Response.Status.UNAUTHORIZED, "You must supply an API token."); - } - if (user == null) { - return error(Response.Status.BAD_REQUEST, "A user could not be found based on the API token."); - } + public Response getAllNotificationsForUser(@Context ContainerRequestContext crc) { + User user = getRequestUser(crc); if (!(user instanceof AuthenticatedUser)) { // It's unlikely we'll reach this error. A Guest doesn't have an API token and would have been blocked above. return error(Response.Status.BAD_REQUEST, "Only an AuthenticatedUser can have notifications."); @@ -88,17 +84,10 @@ private JsonArrayBuilder getReasonsForReturn(UserNotification notification) { } @DELETE + @AuthRequired @Path("/{id}") - public Response deleteNotificationForUser(@PathParam("id") long id) { - User user; - try { - user = findUserOrDie(); - } catch (WrappedResponse ex) { - return error(Response.Status.UNAUTHORIZED, "You must supply an API token."); - } - if (user == null) { - return error(Response.Status.BAD_REQUEST, "A user could not be found based on the API token."); - } + public Response deleteNotificationForUser(@Context ContainerRequestContext crc, @PathParam("id") long id) { + User user = getRequestUser(crc); if (!(user instanceof AuthenticatedUser)) { // It's unlikely we'll reach this error. A Guest doesn't have an API token and would have been blocked above. return error(Response.Status.BAD_REQUEST, "Only an AuthenticatedUser can have notifications."); @@ -117,17 +106,10 @@ public Response deleteNotificationForUser(@PathParam("id") long id) { } @GET + @AuthRequired @Path("/mutedEmails") - public Response getMutedEmailsForUser() { - User user; - try { - user = findUserOrDie(); - } catch (WrappedResponse ex) { - return error(Response.Status.UNAUTHORIZED, "You must supply an API token."); - } - if (user == null) { - return error(Response.Status.BAD_REQUEST, "A user could not be found based on the API token."); - } + public Response getMutedEmailsForUser(@Context ContainerRequestContext crc) { + User user = getRequestUser(crc); if (!(user instanceof AuthenticatedUser)) { // It's unlikely we'll reach this error. A Guest doesn't have an API token and would have been blocked above. return error(Response.Status.BAD_REQUEST, "Only an AuthenticatedUser can have notifications."); @@ -143,17 +125,10 @@ public Response getMutedEmailsForUser() { } @PUT + @AuthRequired @Path("/mutedEmails/{typeName}") - public Response muteEmailsForUser(@PathParam("typeName") String typeName) { - User user; - try { - user = findUserOrDie(); - } catch (WrappedResponse ex) { - return error(Response.Status.UNAUTHORIZED, "You must supply an API token."); - } - if (user == null) { - return error(Response.Status.BAD_REQUEST, "A user could not be found based on the API token."); - } + public Response muteEmailsForUser(@Context ContainerRequestContext crc, @PathParam("typeName") String typeName) { + User user = getRequestUser(crc); if (!(user instanceof AuthenticatedUser)) { // It's unlikely we'll reach this error. A Guest doesn't have an API token and would have been blocked above. return error(Response.Status.BAD_REQUEST, "Only an AuthenticatedUser can have notifications."); @@ -174,17 +149,10 @@ public Response muteEmailsForUser(@PathParam("typeName") String typeName) { } @DELETE + @AuthRequired @Path("/mutedEmails/{typeName}") - public Response unmuteEmailsForUser(@PathParam("typeName") String typeName) { - User user; - try { - user = findUserOrDie(); - } catch (WrappedResponse ex) { - return error(Response.Status.UNAUTHORIZED, "You must supply an API token."); - } - if (user == null) { - return error(Response.Status.BAD_REQUEST, "A user could not be found based on the API token."); - } + public Response unmuteEmailsForUser(@Context ContainerRequestContext crc, @PathParam("typeName") String typeName) { + User user = getRequestUser(crc); if (!(user instanceof AuthenticatedUser)) { // It's unlikely we'll reach this error. A Guest doesn't have an API token and would have been blocked above. return error(Response.Status.BAD_REQUEST, "Only an AuthenticatedUser can have notifications."); @@ -205,17 +173,10 @@ public Response unmuteEmailsForUser(@PathParam("typeName") String typeName) { } @GET + @AuthRequired @Path("/mutedNotifications") - public Response getMutedNotificationsForUser() { - User user; - try { - user = findUserOrDie(); - } catch (WrappedResponse ex) { - return error(Response.Status.UNAUTHORIZED, "You must supply an API token."); - } - if (user == null) { - return error(Response.Status.BAD_REQUEST, "A user could not be found based on the API token."); - } + public Response getMutedNotificationsForUser(@Context ContainerRequestContext crc) { + User user = getRequestUser(crc); if (!(user instanceof AuthenticatedUser)) { // It's unlikely we'll reach this error. A Guest doesn't have an API token and would have been blocked above. return error(Response.Status.BAD_REQUEST, "Only an AuthenticatedUser can have notifications."); @@ -231,17 +192,10 @@ public Response getMutedNotificationsForUser() { } @PUT + @AuthRequired @Path("/mutedNotifications/{typeName}") - public Response muteNotificationsForUser(@PathParam("typeName") String typeName) { - User user; - try { - user = findUserOrDie(); - } catch (WrappedResponse ex) { - return error(Response.Status.UNAUTHORIZED, "You must supply an API token."); - } - if (user == null) { - return error(Response.Status.BAD_REQUEST, "A user could not be found based on the API token."); - } + public Response muteNotificationsForUser(@Context ContainerRequestContext crc, @PathParam("typeName") String typeName) { + User user = getRequestUser(crc); if (!(user instanceof AuthenticatedUser)) { // It's unlikely we'll reach this error. A Guest doesn't have an API token and would have been blocked above. return error(Response.Status.BAD_REQUEST, "Only an AuthenticatedUser can have notifications."); @@ -262,17 +216,10 @@ public Response muteNotificationsForUser(@PathParam("typeName") String typeName) } @DELETE + @AuthRequired @Path("/mutedNotifications/{typeName}") - public Response unmuteNotificationsForUser(@PathParam("typeName") String typeName) { - User user; - try { - user = findUserOrDie(); - } catch (WrappedResponse ex) { - return error(Response.Status.UNAUTHORIZED, "You must supply an API token."); - } - if (user == null) { - return error(Response.Status.BAD_REQUEST, "A user could not be found based on the API token."); - } + public Response unmuteNotificationsForUser(@Context ContainerRequestContext crc, @PathParam("typeName") String typeName) { + User user = getRequestUser(crc); if (!(user instanceof AuthenticatedUser)) { // It's unlikely we'll reach this error. A Guest doesn't have an API token and would have been blocked above. return error(Response.Status.BAD_REQUEST, "Only an AuthenticatedUser can have notifications."); From ffab810b7e770e9bbdc2a9fbf1896edc082ffcf6 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 13:03:38 +0100 Subject: [PATCH 09/30] Refactor: using new filter-based auth on pids endpoints --- .../edu/harvard/iq/dataverse/api/Pids.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Pids.java b/src/main/java/edu/harvard/iq/dataverse/api/Pids.java index 5a2acf3209f..7ca011bc42d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Pids.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Pids.java @@ -1,6 +1,7 @@ package edu.harvard.iq.dataverse.api; import edu.harvard.iq.dataverse.Dataset; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.engine.command.impl.DeletePidCommand; import edu.harvard.iq.dataverse.engine.command.impl.ReservePidCommand; @@ -21,6 +22,8 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -36,15 +39,12 @@ public class Pids extends AbstractApiBean { @GET + @AuthRequired @Produces(MediaType.APPLICATION_JSON) - public Response getPid(@QueryParam("persistentId") String persistentId) { - try { - User user = findUserOrDie(); - if (!user.isSuperuser()) { - return error(Response.Status.FORBIDDEN, BundleUtil.getStringFromBundle("admin.api.auth.mustBeSuperUser")); - } - } catch (WrappedResponse ex) { - return error(Response.Status.FORBIDDEN, BundleUtil.getStringFromBundle("api.errors.invalidApiToken")); + public Response getPid(@Context ContainerRequestContext crc, @QueryParam("persistentId") String persistentId) { + User user = getRequestUser(crc); + if (!user.isSuperuser()) { + return error(Response.Status.FORBIDDEN, BundleUtil.getStringFromBundle("admin.api.auth.mustBeSuperUser")); } String baseUrl = systemConfig.getDataCiteRestApiUrlString(); String username = System.getProperty("doi.username"); @@ -60,16 +60,13 @@ public Response getPid(@QueryParam("persistentId") String persistentId) { } @GET + @AuthRequired @Produces(MediaType.APPLICATION_JSON) @Path("unreserved") - public Response getUnreserved(@QueryParam("persistentId") String persistentId) { - try { - User user = findUserOrDie(); - if (!user.isSuperuser()) { - return error(Response.Status.FORBIDDEN, BundleUtil.getStringFromBundle("admin.api.auth.mustBeSuperUser")); - } - } catch (WrappedResponse ex) { - return error(Response.Status.FORBIDDEN, BundleUtil.getStringFromBundle("api.errors.invalidApiToken")); + public Response getUnreserved(@Context ContainerRequestContext crc, @QueryParam("persistentId") String persistentId) { + User user = getRequestUser(crc); + if (!user.isSuperuser()) { + return error(Response.Status.FORBIDDEN, BundleUtil.getStringFromBundle("admin.api.auth.mustBeSuperUser")); } JsonArrayBuilder unreserved = Json.createArrayBuilder(); @@ -93,12 +90,13 @@ public Response getUnreserved(@QueryParam("persistentId") String persistentId) { } @POST + @AuthRequired @Produces(MediaType.APPLICATION_JSON) @Path("{id}/reserve") - public Response reservePid(@PathParam("id") String idSupplied) { + public Response reservePid(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied) { try { Dataset dataset = findDatasetOrDie(idSupplied); - execCommand(new ReservePidCommand(createDataverseRequest(findUserOrDie()), dataset)); + execCommand(new ReservePidCommand(createDataverseRequest(getRequestUser(crc)), dataset)); return ok(BundleUtil.getStringFromBundle("pids.api.reservePid.success", Arrays.asList(dataset.getGlobalId().asString()))); } catch (WrappedResponse ex) { return ex.getResponse(); @@ -106,9 +104,10 @@ public Response reservePid(@PathParam("id") String idSupplied) { } @DELETE + @AuthRequired @Produces(MediaType.APPLICATION_JSON) @Path("{id}/delete") - public Response deletePid(@PathParam("id") String idSupplied) { + public Response deletePid(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied) { try { Dataset dataset = findDatasetOrDie(idSupplied); //Restrict to never-published datasets (that should have draft/nonpublic pids). The underlying code will invalidate @@ -117,7 +116,7 @@ public Response deletePid(@PathParam("id") String idSupplied) { if(dataset.isReleased()) { return badRequest("Not allowed for Datasets that have been published."); } - execCommand(new DeletePidCommand(createDataverseRequest(findUserOrDie()), dataset)); + execCommand(new DeletePidCommand(createDataverseRequest(getRequestUser(crc)), dataset)); return ok(BundleUtil.getStringFromBundle("pids.api.deletePid.success", Arrays.asList(dataset.getGlobalId().asString()))); } catch (WrappedResponse ex) { return ex.getResponse(); From 3eebaf19b4532c58891bbe85df302b104ae3e7b2 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 13:07:17 +0100 Subject: [PATCH 10/30] Refactor: using new filter-based auth on prov endpoints --- .../edu/harvard/iq/dataverse/api/Prov.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Prov.java b/src/main/java/edu/harvard/iq/dataverse/api/Prov.java index bb40c53c1ca..f0f18f781f1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Prov.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Prov.java @@ -1,6 +1,7 @@ package edu.harvard.iq.dataverse.api; import edu.harvard.iq.dataverse.DataFile; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.provenance.ProvEntityFileData; import edu.harvard.iq.dataverse.provenance.ProvInvestigator; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; @@ -26,6 +27,8 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import static javax.ws.rs.core.Response.Status.BAD_REQUEST; import static javax.ws.rs.core.Response.Status.FORBIDDEN; @@ -39,9 +42,10 @@ public class Prov extends AbstractApiBean { /** Provenance JSON methods **/ @POST + @AuthRequired @Path("{id}/prov-json") @Consumes("application/json") - public Response addProvJson(String body, @PathParam("id") String idSupplied, @QueryParam("entityName") String entityName) { + public Response addProvJson(@Context ContainerRequestContext crc, String body, @PathParam("id") String idSupplied, @QueryParam("entityName") String entityName) { if(!systemConfig.isProvCollectionEnabled()) { return error(FORBIDDEN, BundleUtil.getStringFromBundle("api.prov.error.provDisabled")); } @@ -68,7 +72,7 @@ public Response addProvJson(String body, @PathParam("id") String idSupplied, @Qu return error(BAD_REQUEST, BundleUtil.getStringFromBundle("api.prov.error.entityMismatch")); } - execCommand(new PersistProvJsonCommand(createDataverseRequest(findUserOrDie()), dataFile , body, entityName, true)); + execCommand(new PersistProvJsonCommand(createDataverseRequest(getRequestUser(crc)), dataFile , body, entityName, true)); JsonObjectBuilder jsonResponse = Json.createObjectBuilder(); jsonResponse.add("message", BundleUtil.getStringFromBundle("api.prov.provJsonSaved") + " " + dataFile.getDisplayName()); return ok(jsonResponse); @@ -78,8 +82,9 @@ public Response addProvJson(String body, @PathParam("id") String idSupplied, @Qu } @DELETE + @AuthRequired @Path("{id}/prov-json") - public Response deleteProvJson(String body, @PathParam("id") String idSupplied) { + public Response deleteProvJson(@Context ContainerRequestContext crc, String body, @PathParam("id") String idSupplied) { if(!systemConfig.isProvCollectionEnabled()) { return error(FORBIDDEN, BundleUtil.getStringFromBundle("api.prov.error.provDisabled")); } @@ -88,7 +93,7 @@ public Response deleteProvJson(String body, @PathParam("id") String idSupplied) if(dataFile.isReleased()){ return error(FORBIDDEN, BundleUtil.getStringFromBundle("api.prov.error.jsonDeleteNotAllowed")); } - execCommand(new DeleteProvJsonCommand(createDataverseRequest(findUserOrDie()), dataFile, true)); + execCommand(new DeleteProvJsonCommand(createDataverseRequest(getRequestUser(crc)), dataFile, true)); return ok(BundleUtil.getStringFromBundle("api.prov.provJsonDeleted")); } catch (WrappedResponse ex) { return ex.getResponse(); @@ -97,9 +102,10 @@ public Response deleteProvJson(String body, @PathParam("id") String idSupplied) /** Provenance FreeForm methods **/ @POST + @AuthRequired @Path("{id}/prov-freeform") @Consumes("application/json") - public Response addProvFreeForm(String body, @PathParam("id") String idSupplied) { + public Response addProvFreeForm(@Context ContainerRequestContext crc, String body, @PathParam("id") String idSupplied) { if(!systemConfig.isProvCollectionEnabled()) { return error(FORBIDDEN, BundleUtil.getStringFromBundle("api.prov.error.provDisabled")); } @@ -118,7 +124,7 @@ public Response addProvFreeForm(String body, @PathParam("id") String idSupplied) return error(BAD_REQUEST, BundleUtil.getStringFromBundle("api.prov.error.freeformMissingJsonKey")); } try { - DataverseRequest dr= createDataverseRequest(findUserOrDie()); + DataverseRequest dr= createDataverseRequest(getRequestUser(crc)); DataFile dataFile = findDataFileOrDie(idSupplied); if (dataFile == null) { return error(BAD_REQUEST, BundleUtil.getStringFromBundle("api.prov.error.badDataFileId")); @@ -136,13 +142,14 @@ public Response addProvFreeForm(String body, @PathParam("id") String idSupplied) } @GET + @AuthRequired @Path("{id}/prov-freeform") - public Response getProvFreeForm(String body, @PathParam("id") String idSupplied) { + public Response getProvFreeForm(@Context ContainerRequestContext crc, String body, @PathParam("id") String idSupplied) { if(!systemConfig.isProvCollectionEnabled()) { return error(FORBIDDEN, BundleUtil.getStringFromBundle("api.prov.error.provDisabled")); } try { - String freeFormText = execCommand(new GetProvFreeFormCommand(createDataverseRequest(findUserOrDie()), findDataFileOrDie(idSupplied))); + String freeFormText = execCommand(new GetProvFreeFormCommand(createDataverseRequest(getRequestUser(crc)), findDataFileOrDie(idSupplied))); if(null == freeFormText) { return error(BAD_REQUEST, BundleUtil.getStringFromBundle("api.prov.error.freeformNoText")); } @@ -155,13 +162,14 @@ public Response getProvFreeForm(String body, @PathParam("id") String idSupplied) } @GET + @AuthRequired @Path("{id}/prov-json") - public Response getProvJson(String body, @PathParam("id") String idSupplied) { + public Response getProvJson(@Context ContainerRequestContext crc, String body, @PathParam("id") String idSupplied) { if(!systemConfig.isProvCollectionEnabled()) { return error(FORBIDDEN, BundleUtil.getStringFromBundle("api.prov.error.provDisabled")); } try { - JsonObject jsonText = execCommand(new GetProvJsonCommand(createDataverseRequest(findUserOrDie()), findDataFileOrDie(idSupplied))); + JsonObject jsonText = execCommand(new GetProvJsonCommand(createDataverseRequest(getRequestUser(crc)), findDataFileOrDie(idSupplied))); if(null == jsonText) { return error(BAD_REQUEST, BundleUtil.getStringFromBundle("api.prov.error.jsonNoContent")); } From d15ae3ba761df8c27f4ab998978230fea463b230 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 13:10:35 +0100 Subject: [PATCH 11/30] Refactor: using new filter-based auth on roles endpoints --- .../edu/harvard/iq/dataverse/api/Roles.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Roles.java b/src/main/java/edu/harvard/iq/dataverse/api/Roles.java index 72add184a24..b7f9e4821e5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Roles.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Roles.java @@ -1,6 +1,8 @@ package edu.harvard.iq.dataverse.api; import static edu.harvard.iq.dataverse.api.AbstractApiBean.error; + +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.api.dto.RoleDTO; import edu.harvard.iq.dataverse.authorization.DataverseRole; import edu.harvard.iq.dataverse.authorization.Permission; @@ -19,6 +21,8 @@ import javax.ejb.Stateless; import javax.ws.rs.DELETE; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; /** @@ -30,10 +34,11 @@ public class Roles extends AbstractApiBean { @GET + @AuthRequired @Path("{id}") - public Response viewRole( @PathParam("id") String id) { + public Response viewRole(@Context ContainerRequestContext crc, @PathParam("id") String id) { return response( ()-> { - final User user = findUserOrDie(); + final User user = getRequestUser(crc); final DataverseRole role = findRoleOrDie(id); return ( permissionSvc.userOn(user, role.getOwner()).has(Permission.ManageDataversePermissions) ) ? ok( json(role) ) : permissionError("Permission required to view roles."); @@ -41,8 +46,9 @@ public Response viewRole( @PathParam("id") String id) { } @DELETE + @AuthRequired @Path("{id}") - public Response deleteRole(@PathParam("id") String id) { + public Response deleteRole(@Context ContainerRequestContext crc, @PathParam("id") String id) { return response(req -> { DataverseRole role = findRoleOrDie(id); List args = Arrays.asList(role.getName()); @@ -51,15 +57,17 @@ public Response deleteRole(@PathParam("id") String id) { } execCommand(new DeleteRoleCommand(req, role)); return ok("role " + role.getName() + " deleted."); - }); + }, getRequestUser(crc)); } @POST - public Response createNewRole( RoleDTO roleDto, - @QueryParam("dvo") String dvoIdtf ) { + @AuthRequired + public Response createNewRole(@Context ContainerRequestContext crc, + RoleDTO roleDto, + @QueryParam("dvo") String dvoIdtf) { return response( req -> ok(json(execCommand( new CreateRoleCommand(roleDto.asRole(), - req,findDataverseOrDie(dvoIdtf)))))); + req,findDataverseOrDie(dvoIdtf))))), getRequestUser(crc)); } } From 5bf1a27c7933d71e2a002d6bc40659319a25aa38 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 13:18:16 +0100 Subject: [PATCH 12/30] Refactor: using new filter-based auth on users endpoints --- .../edu/harvard/iq/dataverse/api/Users.java | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 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 7568c7caff6..b68ed4e08b4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Users.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Users.java @@ -6,6 +6,8 @@ package edu.harvard.iq.dataverse.api; import static edu.harvard.iq.dataverse.api.AbstractApiBean.error; + +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.users.ApiToken; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.User; @@ -33,6 +35,7 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Request; @@ -50,11 +53,12 @@ public class Users extends AbstractApiBean { private static final Logger logger = Logger.getLogger(Users.class.getName()); @POST + @AuthRequired @Path("{consumedIdentifier}/mergeIntoUser/{baseIdentifier}") - public Response mergeInAuthenticatedUser(@PathParam("consumedIdentifier") String consumedIdentifier, @PathParam("baseIdentifier") String baseIdentifier) { + public Response mergeInAuthenticatedUser(@Context ContainerRequestContext crc, @PathParam("consumedIdentifier") String consumedIdentifier, @PathParam("baseIdentifier") String baseIdentifier) { User u; try { - u = findUserOrDie(); + u = getRequestUser(crc); if(!u.isSuperuser()) { throw new WrappedResponse(error(Response.Status.UNAUTHORIZED, "Only superusers can merge users")); } @@ -88,11 +92,12 @@ public Response mergeInAuthenticatedUser(@PathParam("consumedIdentifier") String } @POST + @AuthRequired @Path("{identifier}/changeIdentifier/{newIdentifier}") - public Response changeAuthenticatedUserIdentifier(@PathParam("identifier") String oldIdentifier, @PathParam("newIdentifier") String newIdentifier) { + public Response changeAuthenticatedUserIdentifier(@Context ContainerRequestContext crc, @PathParam("identifier") String oldIdentifier, @PathParam("newIdentifier") String newIdentifier) { User u; try { - u = findUserOrDie(); + u = getRequestUser(crc); if(!u.isSuperuser()) { throw new WrappedResponse(error(Response.Status.UNAUTHORIZED, "Only superusers can change userIdentifiers")); } @@ -121,16 +126,11 @@ public Response changeAuthenticatedUserIdentifier(@PathParam("identifier") Strin } @Path("token") + @AuthRequired @DELETE - public Response deleteToken() { - User u; - - try { - u = findUserOrDie(); - } catch (WrappedResponse ex) { - return ex.getResponse(); - } - AuthenticatedUser au; + public Response deleteToken(@Context ContainerRequestContext crc) { + User u = getRequestUser(crc); + AuthenticatedUser au; try{ au = (AuthenticatedUser) u; @@ -145,16 +145,9 @@ public Response deleteToken() { } @Path("token") + @AuthRequired @GET public Response getTokenExpirationDate() { - User u; - - try { - u = findUserOrDie(); - } catch (WrappedResponse ex) { - return ex.getResponse(); - } - ApiToken token = authSvc.findApiToken(getRequestApiKey()); if (token == null) { @@ -166,16 +159,11 @@ public Response getTokenExpirationDate() { } @Path("token/recreate") + @AuthRequired @POST - public Response recreateToken() { - User u; + public Response recreateToken(@Context ContainerRequestContext crc) { + User u = getRequestUser(crc); - try { - u = findUserOrDie(); - } catch (WrappedResponse ex) { - return ex.getResponse(); - } - AuthenticatedUser au; try{ au = (AuthenticatedUser) u; @@ -215,14 +203,15 @@ public Response getAuthenticatedUserByToken() { } @POST + @AuthRequired @Path("{identifier}/removeRoles") - public Response removeUserRoles(@PathParam("identifier") String identifier) { + public Response removeUserRoles(@Context ContainerRequestContext crc, @PathParam("identifier") String identifier) { try { AuthenticatedUser userToModify = authSvc.getAuthenticatedUser(identifier); if (userToModify == null) { return error(Response.Status.BAD_REQUEST, "Cannot find user based on " + identifier + "."); } - execCommand(new RevokeAllRolesCommand(userToModify, createDataverseRequest(findUserOrDie()))); + execCommand(new RevokeAllRolesCommand(userToModify, createDataverseRequest(getRequestUser(crc)))); return ok("Roles removed for user " + identifier + "."); } catch (WrappedResponse wr) { return wr.getResponse(); @@ -230,11 +219,12 @@ public Response removeUserRoles(@PathParam("identifier") String identifier) { } @GET + @AuthRequired @Path("{identifier}/traces") - public Response getTraces(@PathParam("identifier") String identifier) { + public Response getTraces(@Context ContainerRequestContext crc, @PathParam("identifier") String identifier) { try { AuthenticatedUser userToQuery = authSvc.getAuthenticatedUser(identifier); - JsonObjectBuilder jsonObj = execCommand(new GetUserTracesCommand(createDataverseRequest(findUserOrDie()), userToQuery, null)); + JsonObjectBuilder jsonObj = execCommand(new GetUserTracesCommand(createDataverseRequest(getRequestUser(crc)), userToQuery, null)); return ok(jsonObj); } catch (WrappedResponse ex) { return ex.getResponse(); @@ -244,15 +234,16 @@ public Response getTraces(@PathParam("identifier") String identifier) { private List elements = Arrays.asList("roleAssignments","dataverseCreator", "dataversePublisher","datasetCreator", "datasetPublisher","dataFileCreator","dataFilePublisher","datasetVersionUsers","explicitGroups","guestbookEntries", "savedSearches"); @GET + @AuthRequired @Path("{identifier}/traces/{element}") @Produces("text/csv, application/json") - public Response getTraces(@Context Request req, @PathParam("identifier") String identifier, @PathParam("element") String element) { + public Response getTraces(@Context ContainerRequestContext crc, @Context Request req, @PathParam("identifier") String identifier, @PathParam("element") String element) { try { AuthenticatedUser userToQuery = authSvc.getAuthenticatedUser(identifier); if(!elements.contains(element)) { throw new BadRequestException("Not a valid element"); } - JsonObjectBuilder jsonObj = execCommand(new GetUserTracesCommand(createDataverseRequest(findUserOrDie()), userToQuery, element)); + JsonObjectBuilder jsonObj = execCommand(new GetUserTracesCommand(createDataverseRequest(getRequestUser(crc)), userToQuery, element)); List vars = Variant .mediaTypes(MediaType.valueOf(FileUtil.MIME_TYPE_CSV), MediaType.APPLICATION_JSON_TYPE) From 7ccb78b2eb25c2b473fc685864ec89f4e56ff8e7 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 13:41:18 +0100 Subject: [PATCH 13/30] Refactor: using new filter-based auth on dataverses and admin endpoints --- .../edu/harvard/iq/dataverse/api/Admin.java | 118 ++++++++++------- .../harvard/iq/dataverse/api/Dataverses.java | 120 +++++++++++------- 2 files changed, 143 insertions(+), 95 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index e0f6fe84eb3..c8c1a18a3ff 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -519,10 +519,11 @@ public Response publishDataverseAsCreator(@PathParam("id") long id) { @Deprecated @GET + @AuthRequired @Path("authenticatedUsers") - public Response listAuthenticatedUsers() { + public Response listAuthenticatedUsers(@Context ContainerRequestContext crc) { try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -601,11 +602,12 @@ public Response createAuthenicatedUser(JsonObject jsonObject) { * Shib-specfic one. */ @PUT + @AuthRequired @Path("authenticatedUsers/id/{id}/convertShibToBuiltIn") @Deprecated - public Response convertShibUserToBuiltin(@PathParam("id") Long id, String newEmailAddress) { - try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + public Response convertShibUserToBuiltin(@Context ContainerRequestContext crc, @PathParam("id") Long id, String newEmailAddress) { + try { + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -638,10 +640,11 @@ public Response convertShibUserToBuiltin(@PathParam("id") Long id, String newEma } @PUT + @AuthRequired @Path("authenticatedUsers/id/{id}/convertRemoteToBuiltIn") - public Response convertOAuthUserToBuiltin(@PathParam("id") Long id, String newEmailAddress) { - try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + public Response convertOAuthUserToBuiltin(@Context ContainerRequestContext crc, @PathParam("id") Long id, String newEmailAddress) { + try { + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -678,12 +681,13 @@ public Response convertOAuthUserToBuiltin(@PathParam("id") Long id, String newEm * This is used in testing via AdminIT.java but we don't expect sysadmins to use * this. */ - @Path("authenticatedUsers/convert/builtin2shib") @PUT - public Response builtin2shib(String content) { + @AuthRequired + @Path("authenticatedUsers/convert/builtin2shib") + public Response builtin2shib(@Context ContainerRequestContext crc, String content) { logger.info("entering builtin2shib..."); try { - AuthenticatedUser userToRunThisMethod = findAuthenticatedUserOrDie(); + AuthenticatedUser userToRunThisMethod = getRequestAuthenticatedUserOrDie(crc); if (!userToRunThisMethod.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -828,12 +832,13 @@ public Response builtin2shib(String content) { * This is used in testing via AdminIT.java but we don't expect sysadmins to use * this. */ - @Path("authenticatedUsers/convert/builtin2oauth") @PUT - public Response builtin2oauth(String content) { + @AuthRequired + @Path("authenticatedUsers/convert/builtin2oauth") + public Response builtin2oauth(@Context ContainerRequestContext crc, String content) { logger.info("entering builtin2oauth..."); try { - AuthenticatedUser userToRunThisMethod = findAuthenticatedUserOrDie(); + AuthenticatedUser userToRunThisMethod = getRequestAuthenticatedUserOrDie(crc); if (!userToRunThisMethod.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -1008,14 +1013,15 @@ public Response listBuiltinRoles() { } @DELETE + @AuthRequired @Path("roles/{id}") - public Response deleteRole(@PathParam("id") String id) { + public Response deleteRole(@Context ContainerRequestContext crc, @PathParam("id") String id) { return response(req -> { DataverseRole doomed = findRoleOrDie(id); execCommand(new DeleteRoleCommand(req, doomed)); return ok("role " + doomed.getName() + " deleted."); - }); + }, getRequestUser(crc)); } @Path("superuser/{identifier}") @@ -1522,8 +1528,9 @@ public Response registerDataFile(@Context ContainerRequestContext crc, @PathPara } @GET + @AuthRequired @Path("/registerDataFileAll") - public Response registerDataFileAll() { + public Response registerDataFileAll(@Context ContainerRequestContext crc) { Integer count = fileService.findAll().size(); Integer successes = 0; Integer alreadyRegistered = 0; @@ -1536,7 +1543,7 @@ public Response registerDataFileAll() { if ((df.getIdentifier() == null || df.getIdentifier().isEmpty())) { if (df.isReleased()) { released++; - User u = findAuthenticatedUserOrDie(); + User u = getRequestAuthenticatedUserOrDie(crc); DataverseRequest r = createDataverseRequest(u); execCommand(new RegisterDvObjectCommand(r, df)); successes++; @@ -1571,8 +1578,9 @@ public Response registerDataFileAll() { } @GET + @AuthRequired @Path("/updateHashValues/{alg}") - public Response updateHashValues(@PathParam("alg") String alg, @QueryParam("num") int num) { + public Response updateHashValues(@Context ContainerRequestContext crc, @PathParam("alg") String alg, @QueryParam("num") int num) { Integer count = fileService.findAll().size(); Integer successes = 0; Integer alreadyUpdated = 0; @@ -1591,7 +1599,7 @@ public Response updateHashValues(@PathParam("alg") String alg, @QueryParam("num" logger.info("Hashes not created with " + alg + " will be verified, and, if valid, replaced with a hash using " + alg); try { - User u = findAuthenticatedUserOrDie(); + User u = getRequestAuthenticatedUserOrDie(crc); if (!u.isSuperuser()) return error(Status.UNAUTHORIZED, "must be superuser"); } catch (WrappedResponse e1) { @@ -1679,11 +1687,12 @@ public Response updateHashValues(@PathParam("alg") String alg, @QueryParam("num" } @POST + @AuthRequired @Path("/computeDataFileHashValue/{fileId}/algorithm/{alg}") - public Response computeDataFileHashValue(@PathParam("fileId") String fileId, @PathParam("alg") String alg) { + public Response computeDataFileHashValue(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId, @PathParam("alg") String alg) { try { - User u = findAuthenticatedUserOrDie(); + User u = getRequestAuthenticatedUserOrDie(crc); if (!u.isSuperuser()) { return error(Status.UNAUTHORIZED, "must be superuser"); } @@ -1740,11 +1749,12 @@ public Response computeDataFileHashValue(@PathParam("fileId") String fileId, @Pa } @POST + @AuthRequired @Path("/validateDataFileHashValue/{fileId}") - public Response validateDataFileHashValue(@PathParam("fileId") String fileId) { + public Response validateDataFileHashValue(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId) { try { - User u = findAuthenticatedUserOrDie(); + User u = getRequestAuthenticatedUserOrDie(crc); if (!u.isSuperuser()) { return error(Status.UNAUTHORIZED, "must be superuser"); } @@ -1806,12 +1816,13 @@ public Response validateDataFileHashValue(@PathParam("fileId") String fileId) { } @POST + @AuthRequired @Path("/submitDatasetVersionToArchive/{id}/{version}") - public Response submitDatasetVersionToArchive(@PathParam("id") String dsid, + public Response submitDatasetVersionToArchive(@Context ContainerRequestContext crc, @PathParam("id") String dsid, @PathParam("version") String versionNumber) { try { - AuthenticatedUser au = findAuthenticatedUserOrDie(); + AuthenticatedUser au = getRequestAuthenticatedUserOrDie(crc); Dataset ds = findDatasetOrDie(dsid); @@ -1878,11 +1889,12 @@ public void run() { * @return */ @POST + @AuthRequired @Path("/archiveAllUnarchivedDatasetVersions") - public Response archiveAllUnarchivedDatasetVersions(@QueryParam("listonly") boolean listonly, @QueryParam("limit") Integer limit, @QueryParam("latestonly") boolean latestonly) { + public Response archiveAllUnarchivedDatasetVersions(@Context ContainerRequestContext crc, @QueryParam("listonly") boolean listonly, @QueryParam("limit") Integer limit, @QueryParam("latestonly") boolean latestonly) { try { - AuthenticatedUser au = findAuthenticatedUserOrDie(); + AuthenticatedUser au = getRequestAuthenticatedUserOrDie(crc); List dsl = datasetversionService.getUnarchivedDatasetVersions(); if (dsl != null) { @@ -1976,14 +1988,15 @@ public Response clearMetricsCacheByName(@PathParam("name") String name) { } @GET + @AuthRequired @Path("/dataverse/{alias}/addRoleAssignmentsToChildren") - public Response addRoleAssignementsToChildren(@PathParam("alias") String alias) throws WrappedResponse { + public Response addRoleAssignementsToChildren(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse { Dataverse owner = dataverseSvc.findByAlias(alias); if (owner == null) { return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + "."); } try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -2006,14 +2019,15 @@ public Response addRoleAssignementsToChildren(@PathParam("alias") String alias) } @GET + @AuthRequired @Path("/dataverse/{alias}/storageDriver") - public Response getStorageDriver(@PathParam("alias") String alias) throws WrappedResponse { + public Response getStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse { Dataverse dataverse = dataverseSvc.findByAlias(alias); if (dataverse == null) { return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + "."); } try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -2025,14 +2039,15 @@ public Response getStorageDriver(@PathParam("alias") String alias) throws Wrappe } @PUT + @AuthRequired @Path("/dataverse/{alias}/storageDriver") - public Response setStorageDriver(@PathParam("alias") String alias, String label) throws WrappedResponse { + public Response setStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias, String label) throws WrappedResponse { Dataverse dataverse = dataverseSvc.findByAlias(alias); if (dataverse == null) { return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + "."); } try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -2050,14 +2065,15 @@ public Response setStorageDriver(@PathParam("alias") String alias, String label) } @DELETE + @AuthRequired @Path("/dataverse/{alias}/storageDriver") - public Response resetStorageDriver(@PathParam("alias") String alias) throws WrappedResponse { + public Response resetStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse { Dataverse dataverse = dataverseSvc.findByAlias(alias); if (dataverse == null) { return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + "."); } try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -2069,10 +2085,11 @@ public Response resetStorageDriver(@PathParam("alias") String alias) throws Wrap } @GET + @AuthRequired @Path("/dataverse/storageDrivers") - public Response listStorageDrivers() throws WrappedResponse { + public Response listStorageDrivers(@Context ContainerRequestContext crc) throws WrappedResponse { try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -2085,14 +2102,15 @@ public Response listStorageDrivers() throws WrappedResponse { } @GET + @AuthRequired @Path("/dataverse/{alias}/curationLabelSet") - public Response getCurationLabelSet(@PathParam("alias") String alias) throws WrappedResponse { + public Response getCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse { Dataverse dataverse = dataverseSvc.findByAlias(alias); if (dataverse == null) { return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + "."); } try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -2106,14 +2124,15 @@ public Response getCurationLabelSet(@PathParam("alias") String alias) throws Wra } @PUT + @AuthRequired @Path("/dataverse/{alias}/curationLabelSet") - public Response setCurationLabelSet(@PathParam("alias") String alias, @QueryParam("name") String name) throws WrappedResponse { + public Response setCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias, @QueryParam("name") String name) throws WrappedResponse { Dataverse dataverse = dataverseSvc.findByAlias(alias); if (dataverse == null) { return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + "."); } try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -2136,14 +2155,15 @@ public Response setCurationLabelSet(@PathParam("alias") String alias, @QueryPara } @DELETE + @AuthRequired @Path("/dataverse/{alias}/curationLabelSet") - public Response resetCurationLabelSet(@PathParam("alias") String alias) throws WrappedResponse { + public Response resetCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse { Dataverse dataverse = dataverseSvc.findByAlias(alias); if (dataverse == null) { return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + "."); } try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -2155,10 +2175,11 @@ public Response resetCurationLabelSet(@PathParam("alias") String alias) throws W } @GET + @AuthRequired @Path("/dataverse/curationLabelSets") - public Response listCurationLabelSets() throws WrappedResponse { + public Response listCurationLabelSets(@Context ContainerRequestContext crc) throws WrappedResponse { try { - AuthenticatedUser user = findAuthenticatedUserOrDie(); + AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc); if (!user.isSuperuser()) { return error(Response.Status.FORBIDDEN, "Superusers only."); } @@ -2249,12 +2270,13 @@ public Response getBannerMessages(@PathParam("id") Long id) throws WrappedRespon } @POST + @AuthRequired @Consumes("application/json") @Path("/requestSignedUrl") - public Response getSignedUrl(JsonObject urlInfo) { + public Response getSignedUrl(@Context ContainerRequestContext crc, JsonObject urlInfo) { AuthenticatedUser superuser = null; try { - superuser = findAuthenticatedUserOrDie(); + superuser = getRequestAuthenticatedUserOrDie(crc); } catch (WrappedResponse wr) { return wr.getResponse(); } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java index e0a642ef700..cf502250391 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -160,14 +160,16 @@ public class Dataverses extends AbstractApiBean { SwordServiceBean swordService; @POST - public Response addRoot(String body) { + @AuthRequired + public Response addRoot(@Context ContainerRequestContext crc, String body) { logger.info("Creating root dataverse"); - return addDataverse(body, ""); + return addDataverse(crc, body, ""); } @POST + @AuthRequired @Path("{identifier}") - public Response addDataverse(String body, @PathParam("identifier") String parentIdtf) { + public Response addDataverse(@Context ContainerRequestContext crc, String body, @PathParam("identifier") String parentIdtf) { Dataverse d; JsonObject dvJson; @@ -194,7 +196,7 @@ public Response addDataverse(String body, @PathParam("identifier") String parent dc.setDataverse(d); } - AuthenticatedUser u = findAuthenticatedUserOrDie(); + AuthenticatedUser u = getRequestAuthenticatedUserOrDie(crc); d = execCommand(new CreateDataverseCommand(d, createDataverseRequest(u), null, null)); return created("/dataverses/" + d.getAlias(), json(d)); } catch (WrappedResponse ww) { @@ -553,31 +555,34 @@ private Dataset parseDataset(String datasetJson) throws WrappedResponse { } @GET + @AuthRequired @Path("{identifier}") - public Response viewDataverse(@PathParam("identifier") String idtf) { + public Response viewDataverse(@Context ContainerRequestContext crc, @PathParam("identifier") String idtf) { return response(req -> ok( json(execCommand(new GetDataverseCommand(req, findDataverseOrDie(idtf))), settingsService.isTrueForKey(SettingsServiceBean.Key.ExcludeEmailFromExport, false) - ))); + )), getRequestUser(crc)); } @DELETE + @AuthRequired @Path("{identifier}") - public Response deleteDataverse(@PathParam("identifier") String idtf) { + public Response deleteDataverse(@Context ContainerRequestContext crc, @PathParam("identifier") String idtf) { return response(req -> { execCommand(new DeleteDataverseCommand(req, findDataverseOrDie(idtf))); return ok("Dataverse " + idtf + " deleted"); - }); + }, getRequestUser(crc)); } @DELETE + @AuthRequired @Path("{linkingDataverseId}/deleteLink/{linkedDataverseId}") - public Response deleteDataverseLinkingDataverse(@PathParam("linkingDataverseId") String linkingDataverseId, @PathParam("linkedDataverseId") String linkedDataverseId) { + public Response deleteDataverseLinkingDataverse(@Context ContainerRequestContext crc, @PathParam("linkingDataverseId") String linkingDataverseId, @PathParam("linkedDataverseId") String linkedDataverseId) { boolean index = true; return response(req -> { execCommand(new DeleteDataverseLinkingDataverseCommand(req, findDataverseOrDie(linkingDataverseId), findDataverseLinkingDataverseOrDie(linkingDataverseId, linkedDataverseId), index)); return ok("Link from Dataverse " + linkingDataverseId + " to linked Dataverse " + linkedDataverseId + " deleted"); - }); + }, getRequestUser(crc)); } @GET @@ -633,9 +638,10 @@ public Response getMetadataRoot_legacy(@PathParam("identifier") String dvIdtf) { } @GET + @AuthRequired @Path("{identifier}/metadatablocks/isRoot") @Produces(MediaType.APPLICATION_JSON) - public Response getMetadataRoot(@PathParam("identifier") String dvIdtf) { + public Response getMetadataRoot(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) { return response(req -> { final Dataverse dataverse = findDataverseOrDie(dvIdtf); if (permissionSvc.request(req) @@ -645,7 +651,7 @@ public Response getMetadataRoot(@PathParam("identifier") String dvIdtf) { } else { return error(Status.FORBIDDEN, "Not authorized"); } - }); + }, getRequestUser(crc)); } @POST @@ -657,16 +663,17 @@ public Response setMetadataRoot_legacy(@PathParam("identifier") String dvIdtf, S } @PUT + @AuthRequired @Path("{identifier}/metadatablocks/isRoot") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.WILDCARD) - public Response setMetadataRoot(@PathParam("identifier") String dvIdtf, String body) { + public Response setMetadataRoot(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String body) { return response(req -> { final boolean root = parseBooleanOrDie(body); final Dataverse dataverse = findDataverseOrDie(dvIdtf); execCommand(new UpdateDataverseMetadataBlocksCommand.SetRoot(req, dataverse, root)); return ok("Dataverse " + dataverse.getName() + " is now a metadata " + (root ? "" : "non-") + "root"); - }); + }, getRequestUser(crc)); } @GET @@ -805,8 +812,9 @@ public Response updateMetadataBlockFacetsRoot(@Context ContainerRequestContext c // (2438-4295-dois-for-files branch) such that a contributor API token no longer allows this method // to be called without a PermissionException being thrown. @GET + @AuthRequired @Path("{identifier}/contents") - public Response listContent(@PathParam("identifier") String dvIdtf) throws WrappedResponse { + public Response listContent(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) throws WrappedResponse { DvObject.Visitor ser = new DvObject.Visitor() { @Override @@ -832,43 +840,47 @@ public JsonObjectBuilder visit(DataFile df) { .stream() .map(dvo -> (JsonObjectBuilder) dvo.accept(ser)) .collect(toJsonArray()) - )); + ), getRequestUser(crc)); } @GET + @AuthRequired @Path("{identifier}/storagesize") - public Response getStorageSize(@PathParam("identifier") String dvIdtf, @QueryParam("includeCached") boolean includeCached) throws WrappedResponse { + public Response getStorageSize(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("includeCached") boolean includeCached) throws WrappedResponse { return response(req -> ok(MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.datasize"), - execCommand(new GetDataverseStorageSizeCommand(req, findDataverseOrDie(dvIdtf), includeCached))))); + execCommand(new GetDataverseStorageSizeCommand(req, findDataverseOrDie(dvIdtf), includeCached)))), getRequestUser(crc)); } @GET + @AuthRequired @Path("{identifier}/roles") - public Response listRoles(@PathParam("identifier") String dvIdtf) { + public Response listRoles(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) { return response(req -> ok( execCommand(new ListRolesCommand(req, findDataverseOrDie(dvIdtf))) .stream().map(r -> json(r)) .collect(toJsonArray()) - )); + ), getRequestUser(crc)); } @POST + @AuthRequired @Path("{identifier}/roles") - public Response createRole(RoleDTO roleDto, @PathParam("identifier") String dvIdtf) { - return response(req -> ok(json(execCommand(new CreateRoleCommand(roleDto.asRole(), req, findDataverseOrDie(dvIdtf)))))); + public Response createRole(@Context ContainerRequestContext crc, RoleDTO roleDto, @PathParam("identifier") String dvIdtf) { + return response(req -> ok(json(execCommand(new CreateRoleCommand(roleDto.asRole(), req, findDataverseOrDie(dvIdtf))))), getRequestUser(crc)); } @GET + @AuthRequired @Path("{identifier}/assignments") - public Response listAssignments(@PathParam("identifier") String dvIdtf) { + public Response listAssignments(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) { return response(req -> ok( execCommand(new ListRoleAssignments(req, findDataverseOrDie(dvIdtf))) .stream() .map(a -> json(a)) .collect(toJsonArray()) - )); + ), getRequestUser(crc)); } /** @@ -1020,11 +1032,12 @@ public Response deleteAssignment(@Context ContainerRequestContext crc, @PathPara } @POST + @AuthRequired @Path("{identifier}/actions/:publish") - public Response publishDataverse(@PathParam("identifier") String dvIdtf) { + public Response publishDataverse(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) { try { Dataverse dv = findDataverseOrDie(dvIdtf); - return ok(json(execCommand(new PublishDataverseCommand(createDataverseRequest(findAuthenticatedUserOrDie()), dv)))); + return ok(json(execCommand(new PublishDataverseCommand(createDataverseRequest(getRequestAuthenticatedUserOrDie(crc)), dv)))); } catch (WrappedResponse wr) { return wr.getResponse(); @@ -1032,8 +1045,9 @@ public Response publishDataverse(@PathParam("identifier") String dvIdtf) { } @POST + @AuthRequired @Path("{identifier}/groups/") - public Response createExplicitGroup(ExplicitGroupDTO dto, @PathParam("identifier") String dvIdtf) { + public Response createExplicitGroup(@Context ContainerRequestContext crc, ExplicitGroupDTO dto, @PathParam("identifier") String dvIdtf) { return response(req -> { ExplicitGroupProvider prv = explicitGroupSvc.getProvider(); ExplicitGroup newGroup = dto.apply(prv.makeGroup()); @@ -1042,26 +1056,29 @@ public Response createExplicitGroup(ExplicitGroupDTO dto, @PathParam("identifier String groupUri = String.format("%s/groups/%s", dvIdtf, newGroup.getGroupAliasInOwner()); return created(groupUri, json(newGroup)); - }); + }, getRequestUser(crc)); } @GET + @AuthRequired @Path("{identifier}/groups/") - public Response listGroups(@PathParam("identifier") String dvIdtf, @QueryParam("key") String apiKey) { + public Response listGroups(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("key") String apiKey) { return response(req -> ok( execCommand(new ListExplicitGroupsCommand(req, findDataverseOrDie(dvIdtf))) .stream().map(eg -> json(eg)) .collect(toJsonArray()) - )); + ), getRequestUser(crc)); } @GET + @AuthRequired @Path("{identifier}/groups/{aliasInOwner}") - public Response getGroupByOwnerAndAliasInOwner(@PathParam("identifier") String dvIdtf, - @PathParam("aliasInOwner") String grpAliasInOwner) { + public Response getGroupByOwnerAndAliasInOwner(@Context ContainerRequestContext crc, + @PathParam("identifier") String dvIdtf, + @PathParam("aliasInOwner") String grpAliasInOwner) { return response(req -> ok(json(findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, - grpAliasInOwner)))); + grpAliasInOwner))), getRequestUser(crc)); } @GET @@ -1108,18 +1125,21 @@ public void write(OutputStream os) throws IOException, } @PUT + @AuthRequired @Path("{identifier}/groups/{aliasInOwner}") - public Response updateGroup(ExplicitGroupDTO groupDto, + public Response updateGroup(@Context ContainerRequestContext crc, ExplicitGroupDTO groupDto, @PathParam("identifier") String dvIdtf, @PathParam("aliasInOwner") String grpAliasInOwner) { return response(req -> ok(json(execCommand( new UpdateExplicitGroupCommand(req, - groupDto.apply(findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, grpAliasInOwner))))))); + groupDto.apply(findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, grpAliasInOwner)))))), getRequestUser(crc)); } @PUT + @AuthRequired @Path("{identifier}/defaultContributorRole/{roleAlias}") public Response updateDefaultContributorRole( + @Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @PathParam("roleAlias") String roleAlias) { @@ -1155,7 +1175,7 @@ public Response updateDefaultContributorRole( List args = Arrays.asList(dv.getDisplayName(), defaultRoleName); String retString = BundleUtil.getStringFromBundle("dataverses.api.update.default.contributor.role.success", args); return ok(retString); - }); + }, getRequestUser(crc)); } catch (WrappedResponse wr) { return wr.getResponse(); @@ -1164,28 +1184,32 @@ public Response updateDefaultContributorRole( } @DELETE + @AuthRequired @Path("{identifier}/groups/{aliasInOwner}") - public Response deleteGroup(@PathParam("identifier") String dvIdtf, - @PathParam("aliasInOwner") String grpAliasInOwner) { + public Response deleteGroup(@Context ContainerRequestContext crc, + @PathParam("identifier") String dvIdtf, + @PathParam("aliasInOwner") String grpAliasInOwner) { return response(req -> { execCommand(new DeleteExplicitGroupCommand(req, findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, grpAliasInOwner))); return ok("Group " + dvIdtf + "/" + grpAliasInOwner + " deleted"); - }); + }, getRequestUser(crc)); } @POST + @AuthRequired @Path("{identifier}/groups/{aliasInOwner}/roleAssignees") @Consumes("application/json") - public Response addRoleAssingees(List roleAssingeeIdentifiers, - @PathParam("identifier") String dvIdtf, - @PathParam("aliasInOwner") String grpAliasInOwner) { + public Response addRoleAssingees(@Context ContainerRequestContext crc, + List roleAssingeeIdentifiers, + @PathParam("identifier") String dvIdtf, + @PathParam("aliasInOwner") String grpAliasInOwner) { return response(req -> ok( json( execCommand( new AddRoleAssigneesToExplicitGroupCommand(req, findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, grpAliasInOwner), - new TreeSet<>(roleAssingeeIdentifiers)))))); + new TreeSet<>(roleAssingeeIdentifiers))))), getRequestUser(crc)); } @PUT @@ -1197,14 +1221,16 @@ public Response addRoleAssingee(@PathParam("identifier") String dvIdtf, } @DELETE + @AuthRequired @Path("{identifier}/groups/{aliasInOwner}/roleAssignees/{roleAssigneeIdentifier: .*}") - public Response deleteRoleAssingee(@PathParam("identifier") String dvIdtf, - @PathParam("aliasInOwner") String grpAliasInOwner, - @PathParam("roleAssigneeIdentifier") String roleAssigneeIdentifier) { + public Response deleteRoleAssingee(@Context ContainerRequestContext crc, + @PathParam("identifier") String dvIdtf, + @PathParam("aliasInOwner") String grpAliasInOwner, + @PathParam("roleAssigneeIdentifier") String roleAssigneeIdentifier) { return response(req -> ok(json(execCommand( new RemoveRoleAssigneesFromExplicitGroupCommand(req, findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, grpAliasInOwner), - Collections.singleton(roleAssigneeIdentifier)))))); + Collections.singleton(roleAssigneeIdentifier))))), getRequestUser(crc)); } private ExplicitGroup findExplicitGroupOrDie(DvObject dv, DataverseRequest req, String groupIdtf) throws WrappedResponse { From e9b04b38e20ead49c00ad0a179a7e2cd9c8384f3 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 15:09:18 +0100 Subject: [PATCH 14/30] Refactor: using new filter-based auth on endpoints and unused deprecated AbstractApiBean method 'response' removed --- .../iq/dataverse/api/AbstractApiBean.java | 32 +------------------ .../harvard/iq/dataverse/api/Dataverses.java | 20 +++++++----- .../edu/harvard/iq/dataverse/api/Info.java | 18 +++++++---- .../api/batchjob/FileRecordJobResource.java | 17 ++++++---- 4 files changed, 36 insertions(+), 51 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index a434fd710e6..828b58ed078 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -278,7 +278,7 @@ public JsonParser call() throws Exception { /** * Functional interface for handling HTTP requests in the APIs. * - * @see #response(edu.harvard.iq.dataverse.api.AbstractApiBean.DataverseRequestHandler) + * @see #response(edu.harvard.iq.dataverse.api.AbstractApiBean.DataverseRequestHandler, edu.harvard.iq.dataverse.authorization.users.User) */ protected static interface DataverseRequestHandler { Response handle( DataverseRequest u ) throws WrappedResponse; @@ -745,36 +745,6 @@ protected Response response( Callable hdl ) { } } - /** - * The preferred way of handling a request that requires a user. The system - * looks for the user and, if found, handles it to the handler for doing the - * actual work. - * - * This is a relatively secure way to handle things, since if the user is not - * found, the response is about the bad API key, rather than something else - * (say, 404 NOT FOUND which leaks information about the existence of the - * sought object). - * - * @param hdl handling code block. - * @return HTTP Response appropriate for the way {@code hdl} executed. - * - * @deprecated Do not use this method. - * This method is expected to be removed once all API endpoints use the filter-based authentication. - * @see #9293 - * Replaced by: - * {@link #response(DataverseRequestHandler, User)} - */ - @Deprecated - protected Response response( DataverseRequestHandler hdl ) { - try { - return hdl.handle(createDataverseRequest(findUserOrDie())); - } catch ( WrappedResponse rr ) { - return rr.getResponse(); - } catch ( Exception ex ) { - return handleDataverseRequestHandlerException(ex); - } - } - /*** * The preferred way of handling a request that requires a user. The method * receives a user and handles it to the handler for doing the actual work. diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java index cf502250391..926a98487d1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -632,9 +632,10 @@ public Response setMetadataBlocks(@Context ContainerRequestContext crc, @PathPar } @GET + @AuthRequired @Path("{identifier}/metadatablocks/:isRoot") - public Response getMetadataRoot_legacy(@PathParam("identifier") String dvIdtf) { - return getMetadataRoot(dvIdtf); + public Response getMetadataRoot_legacy(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) { + return getMetadataRoot(crc, dvIdtf); } @GET @@ -655,11 +656,12 @@ public Response getMetadataRoot(@Context ContainerRequestContext crc, @PathParam } @POST + @AuthRequired @Path("{identifier}/metadatablocks/:isRoot") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.WILDCARD) - public Response setMetadataRoot_legacy(@PathParam("identifier") String dvIdtf, String body) { - return setMetadataRoot(dvIdtf, body); + public Response setMetadataRoot_legacy(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String body) { + return setMetadataRoot(crc, dvIdtf, body); } @PUT @@ -1213,11 +1215,13 @@ public Response addRoleAssingees(@Context ContainerRequestContext crc, } @PUT + @AuthRequired @Path("{identifier}/groups/{aliasInOwner}/roleAssignees/{roleAssigneeIdentifier: .*}") - public Response addRoleAssingee(@PathParam("identifier") String dvIdtf, - @PathParam("aliasInOwner") String grpAliasInOwner, - @PathParam("roleAssigneeIdentifier") String roleAssigneeIdentifier) { - return addRoleAssingees(Collections.singletonList(roleAssigneeIdentifier), dvIdtf, grpAliasInOwner); + public Response addRoleAssingee(@Context ContainerRequestContext crc, + @PathParam("identifier") String dvIdtf, + @PathParam("aliasInOwner") String grpAliasInOwner, + @PathParam("roleAssigneeIdentifier") String roleAssigneeIdentifier) { + return addRoleAssingees(crc, Collections.singletonList(roleAssigneeIdentifier), dvIdtf, grpAliasInOwner); } @DELETE diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Info.java b/src/main/java/edu/harvard/iq/dataverse/api/Info.java index fd7824c15cf..8bfddb95843 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Info.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Info.java @@ -1,5 +1,6 @@ package edu.harvard.iq.dataverse.api; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.SystemConfig; @@ -8,6 +9,8 @@ import javax.json.JsonValue; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; @Path("info") @@ -31,26 +34,29 @@ public Response getDatasetPublishPopupCustomText() { } @GET + @AuthRequired @Path("version") - public Response getInfo() { + public Response getInfo(@Context ContainerRequestContext crc) { String versionStr = systemConfig.getVersion(true); String[] comps = versionStr.split("build",2); String version = comps[0].trim(); JsonValue build = comps.length > 1 ? Json.createArrayBuilder().add(comps[1].trim()).build().get(0) : JsonValue.NULL; return response( req -> ok( Json.createObjectBuilder().add("version", version) - .add("build", build))); + .add("build", build)), getRequestUser(crc)); } @GET + @AuthRequired @Path("server") - public Response getServer() { - return response( req -> ok(JvmSettings.FQDN.lookup())); + public Response getServer(@Context ContainerRequestContext crc) { + return response( req -> ok(JvmSettings.FQDN.lookup()), getRequestUser(crc)); } @GET + @AuthRequired @Path("apiTermsOfUse") - public Response getTermsOfUse() { - return response( req -> ok(systemConfig.getApiTermsOfUse())); + public Response getTermsOfUse(@Context ContainerRequestContext crc) { + return response( req -> ok(systemConfig.getApiTermsOfUse()), getRequestUser(crc)); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/batchjob/FileRecordJobResource.java b/src/main/java/edu/harvard/iq/dataverse/api/batchjob/FileRecordJobResource.java index 688a0267085..8695c7dcab7 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/batchjob/FileRecordJobResource.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/batchjob/FileRecordJobResource.java @@ -3,6 +3,7 @@ import edu.harvard.iq.dataverse.DatasetServiceBean; import edu.harvard.iq.dataverse.PermissionServiceBean; import edu.harvard.iq.dataverse.api.AbstractApiBean; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.batch.jobs.importer.ImportMode; import edu.harvard.iq.dataverse.engine.command.impl.ImportFromFileSystemCommand; import javax.ejb.EJB; @@ -13,6 +14,8 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.logging.Logger; @@ -33,13 +36,15 @@ public class FileRecordJobResource extends AbstractApiBean { DatasetServiceBean datasetService; @POST + @AuthRequired @Path("import/datasets/files/{identifier}") @Produces(MediaType.APPLICATION_JSON) - public Response getFilesystemImport(@PathParam("identifier") String identifier, - @QueryParam("mode") @DefaultValue("MERGE") String mode, - /*@QueryParam("fileMode") @DefaultValue("package_file") String fileMode*/ - @QueryParam("uploadFolder") String uploadFolder, - @QueryParam("totalSize") Long totalSize) { + public Response getFilesystemImport(@Context ContainerRequestContext crc, + @PathParam("identifier") String identifier, + @QueryParam("mode") @DefaultValue("MERGE") String mode, + /*@QueryParam("fileMode") @DefaultValue("package_file") String fileMode*/ + @QueryParam("uploadFolder") String uploadFolder, + @QueryParam("totalSize") Long totalSize) { return response(req -> { ImportMode importMode = ImportMode.MERGE; // Switch to this if you ever need to use something other than MERGE. @@ -53,7 +58,7 @@ public Response getFilesystemImport(@PathParam("identifier") String identifier, .add("message", returnString) .add("executionId", jsonObject.getInt("executionId")) ); - }); + }, getRequestUser(crc)); } } From 36dbd9b666022d647cac9aa2f92d99def0d7a0fa Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 31 Jan 2023 15:25:26 +0100 Subject: [PATCH 15/30] Refactor: changed to filter-based auth from direct findAuthenticatedUserOrDie calls in API endpoint methods --- .../edu/harvard/iq/dataverse/api/Access.java | 47 ++++++++++--------- .../harvard/iq/dataverse/api/BatchImport.java | 8 +++- .../edu/harvard/iq/dataverse/api/Files.java | 17 +++---- .../iq/dataverse/api/HarvestingClients.java | 5 +- .../iq/dataverse/api/HarvestingServer.java | 18 ++++--- .../edu/harvard/iq/dataverse/api/Index.java | 8 +++- .../harvard/iq/dataverse/api/Licenses.java | 28 +++++++---- .../edu/harvard/iq/dataverse/api/Users.java | 5 +- 8 files changed, 82 insertions(+), 54 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Access.java b/src/main/java/edu/harvard/iq/dataverse/api/Access.java index 87dd427fcf0..e7b6e3a9dad 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -1238,23 +1238,22 @@ private String getWebappImageResource(String imageName) { * @return * */ - @Path("datafile/{fileId}/auxiliary/{formatTag}/{formatVersion}") @POST + @AuthRequired + @Path("datafile/{fileId}/auxiliary/{formatTag}/{formatVersion}") @Consumes(MediaType.MULTIPART_FORM_DATA) - - public Response saveAuxiliaryFileWithVersion(@PathParam("fileId") Long fileId, - @PathParam("formatTag") String formatTag, - @PathParam("formatVersion") String formatVersion, - @FormDataParam("origin") String origin, - @FormDataParam("isPublic") boolean isPublic, - @FormDataParam("type") String type, - @FormDataParam("file") final FormDataBodyPart formDataBodyPart, - @FormDataParam("file") InputStream fileInputStream - - ) { + public Response saveAuxiliaryFileWithVersion(@Context ContainerRequestContext crc, + @PathParam("fileId") Long fileId, + @PathParam("formatTag") String formatTag, + @PathParam("formatVersion") String formatVersion, + @FormDataParam("origin") String origin, + @FormDataParam("isPublic") boolean isPublic, + @FormDataParam("type") String type, + @FormDataParam("file") final FormDataBodyPart formDataBodyPart, + @FormDataParam("file") InputStream fileInputStream) { AuthenticatedUser authenticatedUser; try { - authenticatedUser = findAuthenticatedUserOrDie(); + authenticatedUser = getRequestAuthenticatedUserOrDie(crc); } catch (WrappedResponse ex) { return error(FORBIDDEN, "Authorized users only."); } @@ -1296,14 +1295,16 @@ public Response saveAuxiliaryFileWithVersion(@PathParam("fileId") Long fileId, * @param formDataBodyPart * @return */ - @Path("datafile/{fileId}/auxiliary/{formatTag}/{formatVersion}") @DELETE - public Response deleteAuxiliaryFileWithVersion(@PathParam("fileId") Long fileId, - @PathParam("formatTag") String formatTag, - @PathParam("formatVersion") String formatVersion) { + @AuthRequired + @Path("datafile/{fileId}/auxiliary/{formatTag}/{formatVersion}") + public Response deleteAuxiliaryFileWithVersion(@Context ContainerRequestContext crc, + @PathParam("fileId") Long fileId, + @PathParam("formatTag") String formatTag, + @PathParam("formatVersion") String formatVersion) { AuthenticatedUser authenticatedUser; try { - authenticatedUser = findAuthenticatedUserOrDie(); + authenticatedUser = getRequestAuthenticatedUserOrDie(crc); } catch (WrappedResponse ex) { return error(FORBIDDEN, "Authorized users only."); } @@ -1384,8 +1385,9 @@ public Response allowAccessRequest(@Context ContainerRequestContext crc, @PathPa * @return */ @PUT + @AuthRequired @Path("/datafile/{id}/requestAccess") - public Response requestFileAccess(@PathParam("id") String fileToRequestAccessId, @Context HttpHeaders headers) { + public Response requestFileAccess(@Context ContainerRequestContext crc, @PathParam("id") String fileToRequestAccessId, @Context HttpHeaders headers) { DataverseRequest dataverseRequest; DataFile dataFile; @@ -1404,7 +1406,7 @@ public Response requestFileAccess(@PathParam("id") String fileToRequestAccessId, AuthenticatedUser requestor; try { - requestor = findAuthenticatedUserOrDie(); + requestor = getRequestAuthenticatedUserOrDie(crc); dataverseRequest = createDataverseRequest(requestor); } catch (WrappedResponse wr) { List args = Arrays.asList(wr.getLocalizedMessage()); @@ -1442,8 +1444,9 @@ public Response requestFileAccess(@PathParam("id") String fileToRequestAccessId, * @return */ @GET + @AuthRequired @Path("/datafile/{id}/listRequests") - public Response listFileAccessRequests(@PathParam("id") String fileToRequestAccessId, @Context HttpHeaders headers) { + public Response listFileAccessRequests(@Context ContainerRequestContext crc, @PathParam("id") String fileToRequestAccessId, @Context HttpHeaders headers) { DataverseRequest dataverseRequest; @@ -1456,7 +1459,7 @@ public Response listFileAccessRequests(@PathParam("id") String fileToRequestAcce } try { - dataverseRequest = createDataverseRequest(findAuthenticatedUserOrDie()); + dataverseRequest = createDataverseRequest(getRequestAuthenticatedUserOrDie(crc)); } catch (WrappedResponse wr) { List args = Arrays.asList(wr.getLocalizedMessage()); return error(UNAUTHORIZED, BundleUtil.getStringFromBundle("access.api.fileAccess.failure.noUser", args)); diff --git a/src/main/java/edu/harvard/iq/dataverse/api/BatchImport.java b/src/main/java/edu/harvard/iq/dataverse/api/BatchImport.java index 7b44d920fbe..9e83199f0e7 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/BatchImport.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/BatchImport.java @@ -1,5 +1,6 @@ package edu.harvard.iq.dataverse.api; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.api.imports.ImportServiceBean; import edu.harvard.iq.dataverse.DatasetFieldServiceBean; import edu.harvard.iq.dataverse.DatasetServiceBean; @@ -20,6 +21,8 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; @Stateless @@ -57,12 +60,13 @@ public Response harvest(@QueryParam("path") String fileDir, @QueryParam("dv") St * @return import status (including id of the dataset created) */ @POST + @AuthRequired @Path("import") - public Response postImport(String body, @QueryParam("dv") String parentIdtf, @QueryParam("key") String apiKey) { + public Response postImport(@Context ContainerRequestContext crc, String body, @QueryParam("dv") String parentIdtf, @QueryParam("key") String apiKey) { DataverseRequest dataverseRequest; try { - dataverseRequest = createDataverseRequest(findAuthenticatedUserOrDie()); + dataverseRequest = createDataverseRequest(getRequestAuthenticatedUserOrDie(crc)); } catch (WrappedResponse wr) { return wr.getResponse(); } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index d224ab5ab5e..047fa509853 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -595,15 +595,15 @@ public Response uningestDatafile(@Context ContainerRequestContext crc, @PathPara // type for which ingest was not previously supported. // We are considering making it possible, in the future, to reingest // a datafile that's already ingested as Tabular; for example, to address a - // bug that has been found in an ingest plugin. - - @Path("{id}/reingest") + // bug that has been found in an ingest plugin. @POST - public Response reingest(@PathParam("id") String id) { + @AuthRequired + @Path("{id}/reingest") + public Response reingest(@Context ContainerRequestContext crc, @PathParam("id") String id) { AuthenticatedUser u; try { - u = findAuthenticatedUserOrDie(); + u = getRequestAuthenticatedUserOrDie(crc); if (!u.isSuperuser()) { return error(Response.Status.FORBIDDEN, "This API call can be used by superusers only"); } @@ -684,11 +684,12 @@ public Response redetectDatafile(@Context ContainerRequestContext crc, @PathPara } } - @Path("{id}/extractNcml") @POST - public Response extractNcml(@PathParam("id") String id) { + @AuthRequired + @Path("{id}/extractNcml") + public Response extractNcml(@Context ContainerRequestContext crc, @PathParam("id") String id) { try { - AuthenticatedUser au = findAuthenticatedUserOrDie(); + AuthenticatedUser au = getRequestAuthenticatedUserOrDie(crc); if (!au.isSuperuser()) { // We can always make a command in the future if there's a need // for non-superusers to call this API. diff --git a/src/main/java/edu/harvard/iq/dataverse/api/HarvestingClients.java b/src/main/java/edu/harvard/iq/dataverse/api/HarvestingClients.java index d6dbb5370ad..e739b1520a0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/HarvestingClients.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/HarvestingClients.java @@ -374,14 +374,15 @@ public Response deleteHarvestingClient(@Context ContainerRequestContext crc, @Pa // This POST starts a new harvesting run: @POST + @AuthRequired @Path("{nickName}/run") - public Response startHarvestingJob(@PathParam("nickName") String clientNickname, @QueryParam("key") String apiKey) throws IOException { + public Response startHarvestingJob(@Context ContainerRequestContext crc, @PathParam("nickName") String clientNickname, @QueryParam("key") String apiKey) throws IOException { try { AuthenticatedUser authenticatedUser = null; try { - authenticatedUser = findAuthenticatedUserOrDie(); + authenticatedUser = getRequestAuthenticatedUserOrDie(crc); } catch (WrappedResponse wr) { return error(Response.Status.UNAUTHORIZED, "Authentication required to use this API method"); } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/HarvestingServer.java b/src/main/java/edu/harvard/iq/dataverse/api/HarvestingServer.java index c69eab0307e..f5e3e669083 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/HarvestingServer.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/HarvestingServer.java @@ -5,6 +5,7 @@ */ package edu.harvard.iq.dataverse.api; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.harvest.server.OAISet; import edu.harvard.iq.dataverse.harvest.server.OAISetServiceBean; @@ -30,6 +31,8 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; @@ -104,14 +107,15 @@ public Response oaiSet(@PathParam("specname") String spec, @QueryParam("key") St * "description":$optional_set_description,"definition":$set_search_query_string}. */ @POST + @AuthRequired @Path("/add") - public Response createOaiSet(String jsonBody, @QueryParam("key") String apiKey) throws IOException, JsonParseException { + public Response createOaiSet(@Context ContainerRequestContext crc, String jsonBody, @QueryParam("key") String apiKey) throws IOException, JsonParseException { /* * authorization modeled after the UI (aka HarvestingSetsPage) */ AuthenticatedUser dvUser; try { - dvUser = findAuthenticatedUserOrDie(); + dvUser = getRequestAuthenticatedUserOrDie(crc); } catch (WrappedResponse wr) { return wr.getResponse(); } @@ -173,12 +177,13 @@ public Response createOaiSet(String jsonBody, @QueryParam("key") String apiKey) } @PUT + @AuthRequired @Path("{specname}") - public Response modifyOaiSet(String jsonBody, @PathParam("specname") String spec, @QueryParam("key") String apiKey) throws IOException, JsonParseException { + public Response modifyOaiSet(@Context ContainerRequestContext crc, String jsonBody, @PathParam("specname") String spec, @QueryParam("key") String apiKey) throws IOException, JsonParseException { AuthenticatedUser dvUser; try { - dvUser = findAuthenticatedUserOrDie(); + dvUser = getRequestAuthenticatedUserOrDie(crc); } catch (WrappedResponse wr) { return wr.getResponse(); } @@ -225,12 +230,13 @@ public Response modifyOaiSet(String jsonBody, @PathParam("specname") String spec } @DELETE + @AuthRequired @Path("{specname}") - public Response deleteOaiSet(@PathParam("specname") String spec, @QueryParam("key") String apiKey) { + public Response deleteOaiSet(@Context ContainerRequestContext crc, @PathParam("specname") String spec, @QueryParam("key") String apiKey) { AuthenticatedUser dvUser; try { - dvUser = findAuthenticatedUserOrDie(); + dvUser = getRequestAuthenticatedUserOrDie(crc); } catch (WrappedResponse wr) { return wr.getResponse(); } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Index.java b/src/main/java/edu/harvard/iq/dataverse/api/Index.java index a7f4ee769b7..c5102b7b71a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Index.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Index.java @@ -15,6 +15,7 @@ import edu.harvard.iq.dataverse.DvObjectServiceBean; import edu.harvard.iq.dataverse.FileMetadata; import edu.harvard.iq.dataverse.RoleAssignment; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.GuestUser; import edu.harvard.iq.dataverse.search.SearchServiceBean; @@ -62,6 +63,8 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.apache.solr.client.solrj.SolrServerException; @@ -636,15 +639,16 @@ public Response deleteTimestamp(@PathParam("dvObjectId") long dvObjectId) { } @GET + @AuthRequired @Path("filesearch") - public Response filesearch(@QueryParam("persistentId") String persistentId, @QueryParam("semanticVersion") String semanticVersion, @QueryParam("q") String userSuppliedQuery) { + public Response filesearch(@Context ContainerRequestContext crc, @QueryParam("persistentId") String persistentId, @QueryParam("semanticVersion") String semanticVersion, @QueryParam("q") String userSuppliedQuery) { Dataset dataset = datasetService.findByGlobalId(persistentId); if (dataset == null) { return error(Status.BAD_REQUEST, "Could not find dataset with persistent id " + persistentId); } User user = GuestUser.get(); try { - AuthenticatedUser authenticatedUser = findAuthenticatedUserOrDie(); + AuthenticatedUser authenticatedUser = getRequestAuthenticatedUserOrDie(crc); if (authenticatedUser != null) { user = authenticatedUser; } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Licenses.java b/src/main/java/edu/harvard/iq/dataverse/api/Licenses.java index 1fdf7818cfb..a9d7eb8024c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Licenses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Licenses.java @@ -10,11 +10,14 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import java.util.logging.Logger; import javax.ejb.Stateless; import javax.ws.rs.core.Response.Status; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.license.License; import edu.harvard.iq.dataverse.util.json.JsonPrinter; @@ -51,11 +54,12 @@ public Response getLicenseById(@PathParam("id") long id) { } @POST + @AuthRequired @Path("/") - public Response addLicense(License license) { + public Response addLicense(@Context ContainerRequestContext crc, License license) { User authenticatedUser; try { - authenticatedUser = findAuthenticatedUserOrDie(); + authenticatedUser = getRequestAuthenticatedUserOrDie(crc); if (!authenticatedUser.isSuperuser()) { return error(Status.FORBIDDEN, "must be superuser"); } @@ -86,11 +90,12 @@ public Response getDefault() { } @PUT + @AuthRequired @Path("/default/{id}") - public Response setDefault(@PathParam("id") long id) { + public Response setDefault(@Context ContainerRequestContext crc, @PathParam("id") long id) { User authenticatedUser; try { - authenticatedUser = findAuthenticatedUserOrDie(); + authenticatedUser = getRequestAuthenticatedUserOrDie(crc); if (!authenticatedUser.isSuperuser()) { return error(Status.FORBIDDEN, "must be superuser"); } @@ -117,11 +122,12 @@ public Response setDefault(@PathParam("id") long id) { } @PUT + @AuthRequired @Path("/{id}/:active/{activeState}") - public Response setActiveState(@PathParam("id") long id, @PathParam("activeState") boolean active) { + public Response setActiveState(@Context ContainerRequestContext crc, @PathParam("id") long id, @PathParam("activeState") boolean active) { User authenticatedUser; try { - authenticatedUser = findAuthenticatedUserOrDie(); + authenticatedUser = getRequestAuthenticatedUserOrDie(crc); if (!authenticatedUser.isSuperuser()) { return error(Status.FORBIDDEN, "must be superuser"); } @@ -147,11 +153,12 @@ public Response setActiveState(@PathParam("id") long id, @PathParam("activeState } @PUT + @AuthRequired @Path("/{id}/:sortOrder/{sortOrder}") - public Response setSortOrder(@PathParam("id") long id, @PathParam("sortOrder") long sortOrder) { + public Response setSortOrder(@Context ContainerRequestContext crc, @PathParam("id") long id, @PathParam("sortOrder") long sortOrder) { User authenticatedUser; try { - authenticatedUser = findAuthenticatedUserOrDie(); + authenticatedUser = getRequestAuthenticatedUserOrDie(crc); if (!authenticatedUser.isSuperuser()) { return error(Status.FORBIDDEN, "must be superuser"); } @@ -178,11 +185,12 @@ public Response setSortOrder(@PathParam("id") long id, @PathParam("sortOrder") l } @DELETE + @AuthRequired @Path("/{id}") - public Response deleteLicenseById(@PathParam("id") long id) { + public Response deleteLicenseById(@Context ContainerRequestContext crc, @PathParam("id") long id) { User authenticatedUser; try { - authenticatedUser = findAuthenticatedUserOrDie(); + authenticatedUser = getRequestAuthenticatedUserOrDie(crc); if (!authenticatedUser.isSuperuser()) { return error(Status.FORBIDDEN, "must be superuser"); } 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 b68ed4e08b4..14bf25c91b2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Users.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Users.java @@ -183,8 +183,9 @@ public Response recreateToken(@Context ContainerRequestContext crc) { } @GET + @AuthRequired @Path(":me") - public Response getAuthenticatedUserByToken() { + public Response getAuthenticatedUserByToken(@Context ContainerRequestContext crc) { String tokenFromRequestAPI = getRequestApiKey(); @@ -193,7 +194,7 @@ public Response getAuthenticatedUserByToken() { // this is a good idea if (authenticatedUser == null) { try { - authenticatedUser = findAuthenticatedUserOrDie(); + authenticatedUser = getRequestAuthenticatedUserOrDie(crc); } catch (WrappedResponse ex) { Logger.getLogger(Users.class.getName()).log(Level.SEVERE, null, ex); return error(Response.Status.BAD_REQUEST, "User with token " + tokenFromRequestAPI + " not found."); From b13e0548a8d4c5cf4330cae3f204676f4eec1c6b Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 1 Feb 2023 11:59:56 +0000 Subject: [PATCH 16/30] Refactor: removed findAuthenticatedUserOrDie() from AbstractApiBean and replaced its calls to filter-based logic --- .../iq/dataverse/api/AbstractApiBean.java | 24 --------------- .../harvard/iq/dataverse/api/BatchImport.java | 30 +++++++++++-------- .../edu/harvard/iq/dataverse/api/Search.java | 10 +++++-- 3 files changed, 24 insertions(+), 40 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index 828b58ed078..52fbd444dc1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -415,30 +415,6 @@ protected User findUserOrDie() throws WrappedResponse { return findAuthenticatedUserOrDie(requestApiKey, requestWFKey); } - /** - * Finds the authenticated user, based on (in order): - *
    - *
  1. The key in the HTTP header {@link #DATAVERSE_KEY_HEADER_NAME}
  2. - *
  3. The key in the query parameter {@code key} - *
- * - * If no user is found, throws a wrapped bad api key (HTTP UNAUTHORIZED) response. - * - * @return The authenticated user which owns the passed api key. - * @throws edu.harvard.iq.dataverse.api.AbstractApiBean.WrappedResponse in case said user is not found. - * - * @deprecated Do not use this method. - * This method is expected to be removed once all API endpoints use the filter-based authentication. - * @see #9293 - * Replaced by: - * {@link #getRequestAuthenticatedUserOrDie(ContainerRequestContext)} - */ - @Deprecated - protected AuthenticatedUser findAuthenticatedUserOrDie() throws WrappedResponse { - return findAuthenticatedUserOrDie(getRequestApiKey(), getRequestWorkflowInvocationID()); - } - - private AuthenticatedUser findAuthenticatedUserOrDie( String key, String wfid ) throws WrappedResponse { if (key != null) { // No check for deactivated user because it's done in authSvc.lookupUser. diff --git a/src/main/java/edu/harvard/iq/dataverse/api/BatchImport.java b/src/main/java/edu/harvard/iq/dataverse/api/BatchImport.java index 9e83199f0e7..bf9ce2adc5a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/BatchImport.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/BatchImport.java @@ -10,6 +10,7 @@ import edu.harvard.iq.dataverse.api.imports.ImportException; import edu.harvard.iq.dataverse.api.imports.ImportUtil.ImportType; +import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import java.io.IOException; @@ -45,10 +46,14 @@ public class BatchImport extends AbstractApiBean { BatchServiceBean batchService; @GET + @AuthRequired @Path("harvest") - public Response harvest(@QueryParam("path") String fileDir, @QueryParam("dv") String parentIdtf, @QueryParam("createDV") Boolean createDV, @QueryParam("key") String apiKey) throws IOException { - return startBatchJob(fileDir, parentIdtf, apiKey, ImportType.HARVEST, createDV); - + public Response harvest(@Context ContainerRequestContext crc, @QueryParam("path") String fileDir, @QueryParam("dv") String parentIdtf, @QueryParam("createDV") Boolean createDV, @QueryParam("key") String apiKey) throws IOException { + try { + return startBatchJob(getRequestAuthenticatedUserOrDie(crc), fileDir, parentIdtf, apiKey, ImportType.HARVEST, createDV); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } } /** @@ -98,24 +103,23 @@ public Response postImport(@Context ContainerRequestContext crc, String body, @Q * @return import status (including id's of the datasets created) */ @GET + @AuthRequired @Path("import") - public Response getImport(@QueryParam("path") String fileDir, @QueryParam("dv") String parentIdtf, @QueryParam("createDV") Boolean createDV, @QueryParam("key") String apiKey) { - - return startBatchJob(fileDir, parentIdtf, apiKey, ImportType.NEW, createDV); - + public Response getImport(@Context ContainerRequestContext crc, @QueryParam("path") String fileDir, @QueryParam("dv") String parentIdtf, @QueryParam("createDV") Boolean createDV, @QueryParam("key") String apiKey) { + try { + return startBatchJob(getRequestAuthenticatedUserOrDie(crc), fileDir, parentIdtf, apiKey, ImportType.NEW, createDV); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } } - private Response startBatchJob(String fileDir, String parentIdtf, String apiKey, ImportType importType, Boolean createDV) { + private Response startBatchJob(User user, String fileDir, String parentIdtf, String apiKey, ImportType importType, Boolean createDV) { if (createDV == null) { createDV = Boolean.FALSE; } try { DataverseRequest dataverseRequest; - try { - dataverseRequest = createDataverseRequest(findAuthenticatedUserOrDie()); - } catch (WrappedResponse wr) { - return wr.getResponse(); - } + dataverseRequest = createDataverseRequest(user); if (parentIdtf == null) { parentIdtf = "root"; } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Search.java b/src/main/java/edu/harvard/iq/dataverse/api/Search.java index cef509b1ec5..d3c9d0a4cc6 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Search.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Search.java @@ -1,6 +1,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.search.SearchFields; import edu.harvard.iq.dataverse.DataverseServiceBean; import edu.harvard.iq.dataverse.DvObjectServiceBean; @@ -33,6 +34,7 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; @@ -56,7 +58,9 @@ public class Search extends AbstractApiBean { SolrIndexServiceBean SolrIndexService; @GET + @AuthRequired public Response search( + @Context ContainerRequestContext crc, @QueryParam("q") String query, @QueryParam("type") final List types, @QueryParam("subtree") final List subtrees, @@ -79,7 +83,7 @@ public Response search( User user; try { - user = getUser(); + user = getUser(crc); } catch (WrappedResponse ex) { return ex.getResponse(); } @@ -227,10 +231,10 @@ public Response search( } } - private User getUser() throws WrappedResponse { + private User getUser(ContainerRequestContext crc) throws WrappedResponse { User userToExecuteSearchAs = GuestUser.get(); try { - AuthenticatedUser authenticatedUser = findAuthenticatedUserOrDie(); + AuthenticatedUser authenticatedUser = getRequestAuthenticatedUserOrDie(crc); if (authenticatedUser != null) { userToExecuteSearchAs = authenticatedUser; } From e8163bd29e40dc45c13eae799600697dbbd5eded Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 2 Feb 2023 10:45:30 +0000 Subject: [PATCH 17/30] Refactor: Access API endpoint class to avoid any endpoint or related private method to use findUserOrDie method --- .../edu/harvard/iq/dataverse/api/Access.java | 199 +++++++++--------- 1 file changed, 97 insertions(+), 102 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Access.java b/src/main/java/edu/harvard/iq/dataverse/api/Access.java index e7b6e3a9dad..1d7241de181 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -194,10 +194,11 @@ public class Access extends AbstractApiBean { // TODO: // versions? -- L.A. 4.0 beta 10 - @Path("datafile/bundle/{fileId}") @GET + @AuthRequired + @Path("datafile/bundle/{fileId}") @Produces({"application/zip"}) - public BundleDownloadInstance datafileBundle(@PathParam("fileId") String fileId, @QueryParam("fileMetadataId") Long fileMetadataId,@QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) /*throws NotFoundException, ServiceUnavailableException, PermissionDeniedException, AuthorizationRequiredException*/ { + public BundleDownloadInstance datafileBundle(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId, @QueryParam("fileMetadataId") Long fileMetadataId,@QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) /*throws NotFoundException, ServiceUnavailableException, PermissionDeniedException, AuthorizationRequiredException*/ { GuestbookResponse gbr = null; @@ -205,12 +206,12 @@ public BundleDownloadInstance datafileBundle(@PathParam("fileId") String fileId, DataFile df = findDataFileOrDieWrapper(fileId); // This will throw a ForbiddenException if access isn't authorized: - checkAuthorization(df); + checkAuthorization(getRequestUser(crc), df); if (gbrecs != true && df.isReleased()){ // Write Guestbook record if not done previously and file is released //This calls findUserOrDie which will retrieve the key param or api token header, or the workflow token header. - User apiTokenUser = findAPITokenUser(); + User apiTokenUser = findAPITokenUser(getRequestUser(crc)); gbr = guestbookResponseService.initAPIGuestbookResponse(df.getOwner(), df, session, apiTokenUser); guestbookResponseService.save(gbr); MakeDataCountEntry entry = new MakeDataCountEntry(uriInfo, headers, dvRequestService, df); @@ -272,10 +273,11 @@ private DataFile findDataFileOrDieWrapper(String fileId){ } - @Path("datafile/{fileId:.+}") @GET + @AuthRequired + @Path("datafile/{fileId:.+}") @Produces({"application/xml"}) - public Response datafile(@PathParam("fileId") String fileId, @QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) /*throws NotFoundException, ServiceUnavailableException, PermissionDeniedException, AuthorizationRequiredException*/ { + public Response datafile(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId, @QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) /*throws NotFoundException, ServiceUnavailableException, PermissionDeniedException, AuthorizationRequiredException*/ { // check first if there's a trailing slash, and chop it: while (fileId.lastIndexOf('/') == fileId.length() - 1) { @@ -302,11 +304,11 @@ public Response datafile(@PathParam("fileId") String fileId, @QueryParam("gbrecs } // This will throw a ForbiddenException if access isn't authorized: - checkAuthorization(df); + checkAuthorization(getRequestUser(crc), df); if (gbrecs != true && df.isReleased()){ // Write Guestbook record if not done previously and file is released - User apiTokenUser = findAPITokenUser(); + User apiTokenUser = findAPITokenUser(getRequestUser(crc)); gbr = guestbookResponseService.initAPIGuestbookResponse(df.getOwner(), df, session, apiTokenUser); } @@ -520,30 +522,34 @@ public String tabularDatafileMetadataDDI(@Context ContainerRequestContext crc, @ * a tabular datafile. */ - @Path("datafile/{fileId}/auxiliary") @GET - public Response listDatafileMetadataAux(@PathParam("fileId") String fileId, - @Context UriInfo uriInfo, - @Context HttpHeaders headers, - @Context HttpServletResponse response) throws ServiceUnavailableException { - return listAuxiliaryFiles(fileId, null, uriInfo, headers, response); + @AuthRequired + @Path("datafile/{fileId}/auxiliary") + public Response listDatafileMetadataAux(@Context ContainerRequestContext crc, + @PathParam("fileId") String fileId, + @Context UriInfo uriInfo, + @Context HttpHeaders headers, + @Context HttpServletResponse response) throws ServiceUnavailableException { + return listAuxiliaryFiles(getRequestUser(crc), fileId, null, uriInfo, headers, response); } /* * GET method for retrieving a list auxiliary files associated with * a tabular datafile and having the specified origin. */ - - @Path("datafile/{fileId}/auxiliary/{origin}") + @GET - public Response listDatafileMetadataAuxByOrigin(@PathParam("fileId") String fileId, - @PathParam("origin") String origin, - @Context UriInfo uriInfo, - @Context HttpHeaders headers, - @Context HttpServletResponse response) throws ServiceUnavailableException { - return listAuxiliaryFiles(fileId, origin, uriInfo, headers, response); + @AuthRequired + @Path("datafile/{fileId}/auxiliary/{origin}") + public Response listDatafileMetadataAuxByOrigin(@Context ContainerRequestContext crc, + @PathParam("fileId") String fileId, + @PathParam("origin") String origin, + @Context UriInfo uriInfo, + @Context HttpHeaders headers, + @Context HttpServletResponse response) throws ServiceUnavailableException { + return listAuxiliaryFiles(getRequestUser(crc), fileId, origin, uriInfo, headers, response); } - private Response listAuxiliaryFiles(String fileId, String origin, UriInfo uriInfo, HttpHeaders headers, HttpServletResponse response) { + private Response listAuxiliaryFiles(User user, String fileId, String origin, UriInfo uriInfo, HttpHeaders headers, HttpServletResponse response) { DataFile df = findDataFileOrDieWrapper(fileId); List auxFileList = auxiliaryFileService.findAuxiliaryFiles(df, origin); @@ -551,7 +557,7 @@ private Response listAuxiliaryFiles(String fileId, String origin, UriInfo uriInf if (auxFileList == null || auxFileList.isEmpty()) { throw new NotFoundException("No Auxiliary files exist for datafile " + fileId + (origin==null ? "": " and the specified origin")); } - boolean isAccessAllowed = isAccessAuthorized(df); + boolean isAccessAllowed = isAccessAuthorized(user, df); JsonArrayBuilder jab = Json.createArrayBuilder(); auxFileList.forEach(auxFile -> { if (isAccessAllowed || auxFile.getIsPublic()) { @@ -574,14 +580,16 @@ private Response listAuxiliaryFiles(String fileId, String origin, UriInfo uriInf * */ + @GET + @AuthRequired @Path("datafile/{fileId}/auxiliary/{formatTag}/{formatVersion}") - @GET - public DownloadInstance downloadAuxiliaryFile(@PathParam("fileId") String fileId, - @PathParam("formatTag") String formatTag, - @PathParam("formatVersion") String formatVersion, - @Context UriInfo uriInfo, - @Context HttpHeaders headers, - @Context HttpServletResponse response) throws ServiceUnavailableException { + public DownloadInstance downloadAuxiliaryFile(@Context ContainerRequestContext crc, + @PathParam("fileId") String fileId, + @PathParam("formatTag") String formatTag, + @PathParam("formatVersion") String formatVersion, + @Context UriInfo uriInfo, + @Context HttpHeaders headers, + @Context HttpServletResponse response) throws ServiceUnavailableException { DataFile df = findDataFileOrDieWrapper(fileId); @@ -634,7 +642,7 @@ public DownloadInstance downloadAuxiliaryFile(@PathParam("fileId") String fileId // as defined for the DataFile itself), and will throw a ForbiddenException // if access is denied: if (!publiclyAvailable) { - checkAuthorization(df); + checkAuthorization(getRequestUser(crc), df); } return downloadInstance; @@ -646,19 +654,20 @@ public DownloadInstance downloadAuxiliaryFile(@PathParam("fileId") String fileId // TODO: Rather than only supporting looking up files by their database IDs, // consider supporting persistent identifiers. - @Path("datafiles") @POST + @AuthRequired + @Path("datafiles") @Consumes("text/plain") @Produces({ "application/zip" }) - public Response postDownloadDatafiles(String fileIds, @QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WebApplicationException { + public Response postDownloadDatafiles(@Context ContainerRequestContext crc, String fileIds, @QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WebApplicationException { - return downloadDatafiles(fileIds, gbrecs, uriInfo, headers, response); + return downloadDatafiles(getRequestUser(crc), fileIds, gbrecs, uriInfo, headers, response); } - @Path("dataset/{id}") - @AuthRequired @GET + @AuthRequired + @Path("dataset/{id}") @Produces({"application/zip"}) public Response downloadAllFromLatest(@Context ContainerRequestContext crc, @PathParam("id") String datasetIdOrPersistentId, @QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WebApplicationException { try { @@ -674,7 +683,7 @@ public Response downloadAllFromLatest(@Context ContainerRequestContext crc, @Pat // We don't want downloads from Draft versions to be counted, // so we are setting the gbrecs (aka "do not write guestbook response") // variable accordingly: - return downloadDatafiles(fileIds, true, uriInfo, headers, response); + return downloadDatafiles(getRequestUser(crc), fileIds, true, uriInfo, headers, response); } } @@ -695,15 +704,15 @@ public Response downloadAllFromLatest(@Context ContainerRequestContext crc, @Pat } String fileIds = getFileIdsAsCommaSeparated(latest.getFileMetadatas()); - return downloadDatafiles(fileIds, gbrecs, uriInfo, headers, response); + return downloadDatafiles(getRequestUser(crc), fileIds, gbrecs, uriInfo, headers, response); } catch (WrappedResponse wr) { return wr.getResponse(); } } - @Path("dataset/{id}/versions/{versionId}") - @AuthRequired @GET + @AuthRequired + @Path("dataset/{id}/versions/{versionId}") @Produces({"application/zip"}) public Response downloadAllFromVersion(@Context ContainerRequestContext crc, @PathParam("id") String datasetIdOrPersistentId, @PathParam("versionId") String versionId, @QueryParam("gbrecs") boolean gbrecs, @QueryParam("key") String apiTokenParam, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WebApplicationException { try { @@ -745,7 +754,7 @@ public Command handleLatestPublished() { if (dsv.isDraft()) { gbrecs = true; } - return downloadDatafiles(fileIds, gbrecs, uriInfo, headers, response); + return downloadDatafiles(getRequestUser(crc), fileIds, gbrecs, uriInfo, headers, response); } catch (WrappedResponse wr) { return wr.getResponse(); } @@ -763,14 +772,15 @@ private static String getFileIdsAsCommaSeparated(List fileMetadata /* * API method for downloading zipped bundles of multiple files: */ - @Path("datafiles/{fileIds}") @GET + @AuthRequired + @Path("datafiles/{fileIds}") @Produces({"application/zip"}) - public Response datafiles(@PathParam("fileIds") String fileIds, @QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WebApplicationException { - return downloadDatafiles(fileIds, gbrecs, uriInfo, headers, response); + public Response datafiles(@Context ContainerRequestContext crc, @PathParam("fileIds") String fileIds, @QueryParam("gbrecs") boolean gbrecs, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WebApplicationException { + return downloadDatafiles(getRequestUser(crc), fileIds, gbrecs, uriInfo, headers, response); } - private Response downloadDatafiles(String rawFileIds, boolean donotwriteGBResponse, UriInfo uriInfo, HttpHeaders headers, HttpServletResponse response) throws WebApplicationException /* throws NotFoundException, ServiceUnavailableException, PermissionDeniedException, AuthorizationRequiredException*/ { + private Response downloadDatafiles(User user, String rawFileIds, boolean donotwriteGBResponse, UriInfo uriInfo, HttpHeaders headers, HttpServletResponse response) throws WebApplicationException /* throws NotFoundException, ServiceUnavailableException, PermissionDeniedException, AuthorizationRequiredException*/ { final long zipDownloadSizeLimit = systemConfig.getZipDownloadLimit(); logger.fine("setting zip download size limit to " + zipDownloadSizeLimit + " bytes."); @@ -792,7 +802,7 @@ private Response downloadDatafiles(String rawFileIds, boolean donotwriteGBRespon String customZipServiceUrl = settingsService.getValueForKey(SettingsServiceBean.Key.CustomZipDownloadServiceUrl); boolean useCustomZipService = customZipServiceUrl != null; - User apiTokenUser = findAPITokenUser(); //for use in adding gb records if necessary + User apiTokenUser = findAPITokenUser(user); //for use in adding gb records if necessary Boolean getOrig = false; for (String key : uriInfo.getQueryParameters().keySet()) { @@ -805,7 +815,7 @@ private Response downloadDatafiles(String rawFileIds, boolean donotwriteGBRespon if (useCustomZipService) { URI redirect_uri = null; try { - redirect_uri = handleCustomZipDownload(customZipServiceUrl, fileIds, apiTokenUser, uriInfo, headers, donotwriteGBResponse, true); + redirect_uri = handleCustomZipDownload(user, customZipServiceUrl, fileIds, apiTokenUser, uriInfo, headers, donotwriteGBResponse, true); } catch (WebApplicationException wae) { throw wae; } @@ -845,8 +855,8 @@ public void write(OutputStream os) throws IOException, logger.fine("attempting to look up file id " + fileId); DataFile file = dataFileService.find(fileId); if (file != null) { - if (isAccessAuthorized(file)) { - + if (isAccessAuthorized(user, file)) { + logger.fine("adding datafile (id=" + file.getId() + ") to the download list of the ZippedDownloadInstance."); //downloadInstance.addDataFile(file); if (donotwriteGBResponse != true && file.isReleased()){ @@ -1379,8 +1389,8 @@ public Response allowAccessRequest(@Context ContainerRequestContext crc, @PathPa * * @author sekmiller * + * @param crc * @param fileToRequestAccessId - * @param apiToken * @param headers * @return */ @@ -1413,7 +1423,7 @@ public Response requestFileAccess(@Context ContainerRequestContext crc, @PathPar return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.fileAccess.failure.noUser", args)); } //Already have access - if (isAccessAuthorized(dataFile)) { + if (isAccessAuthorized(getRequestUser(crc), dataFile)) { return error(BAD_REQUEST, BundleUtil.getStringFromBundle("access.api.requestAccess.failure.invalidRequest")); } @@ -1490,9 +1500,9 @@ public Response listFileAccessRequests(@Context ContainerRequestContext crc, @Pa * * @author sekmiller * + * @param crc * @param fileToRequestAccessId * @param identifier - * @param apiToken * @param headers * @return */ @@ -1550,9 +1560,9 @@ public Response grantFileAccess(@Context ContainerRequestContext crc, @PathParam * * @author sekmiller * + * @param crc * @param fileToRequestAccessId * @param identifier - * @param apiToken * @param headers * @return */ @@ -1616,9 +1626,9 @@ public Response revokeFileAccess(@Context ContainerRequestContext crc, @PathPara * * @author sekmiller * + * @param crc * @param fileToRequestAccessId * @param identifier - * @param apiToken * @param headers * @return */ @@ -1673,15 +1683,15 @@ public Response rejectFileAccess(@Context ContainerRequestContext crc, @PathPara // checkAuthorization is a convenience method; it calls the boolean method // isAccessAuthorized(), the actual workhorse, tand throws a 403 exception if not. - private void checkAuthorization(DataFile df) throws WebApplicationException { + private void checkAuthorization(User user, DataFile df) throws WebApplicationException { - if (!isAccessAuthorized(df)) { + if (!isAccessAuthorized(user, df)) { throw new ForbiddenException(); } } - private boolean isAccessAuthorized(DataFile df) { + private boolean isAccessAuthorized(User requestUser, DataFile df) { // First, check if the file belongs to a released Dataset version: boolean published = false; @@ -1763,34 +1773,26 @@ private boolean isAccessAuthorized(DataFile df) { } //For permissions check decide if we have a session user, or an API user - User user = null; + User sessionUser = null; /** * Authentication/authorization: */ - - User apiTokenUser = null; + + User apiUser = requestUser; /* - * The logic looks for an apitoken authenticated user and uses it if it exists. - * If not, and a session user exists, we use that. If the apitoken method - * indicates a GuestUser, we will use that if there's no session. + * If API user is not authenticated, and a session user exists, we use that. + * If the API user indicates a GuestUser, we will use that if there's no session. * * This is currently the only API call that supports sessions. If the rest of * the API is opened up, the custom logic here wouldn't be needed. */ - try { - logger.fine("calling apiTokenUser = findUserOrDie()..."); - apiTokenUser = findUserOrDie(); - } catch (WrappedResponse wr) { - logger.log(Level.FINE, "Message from findUserOrDie(): {0}", wr.getMessage()); - } - - if ((apiTokenUser instanceof GuestUser) && session != null) { + if ((apiUser instanceof GuestUser) && session != null) { if (session.getUser() != null) { - user = session.getUser(); - apiTokenUser=null; + sessionUser = session.getUser(); + apiUser = null; //Fine logging if (!session.getUser().isAuthenticated()) { logger.fine("User associated with the session is not an authenticated user."); @@ -1808,7 +1810,7 @@ private boolean isAccessAuthorized(DataFile df) { logger.fine("Session is null."); } //If we don't have a user, nothing more to do. (Note session could have returned GuestUser) - if (user == null && apiTokenUser == null) { + if (sessionUser == null && apiUser == null) { logger.warning("Unable to find a user via session or with a token."); return false; } @@ -1821,8 +1823,8 @@ private boolean isAccessAuthorized(DataFile df) { */ DataverseRequest dvr = null; - if (apiTokenUser != null) { - dvr = createDataverseRequest(apiTokenUser); + if (apiUser != null) { + dvr = createDataverseRequest(apiUser); } else { // used in JSF context, user may be Guest dvr = dvRequestService.getDataverseRequest(); @@ -1846,42 +1848,35 @@ private boolean isAccessAuthorized(DataFile df) { return true; } } - if (user != null) { - logger.log(Level.FINE, "Session-based auth: user {0} has NO access rights on the requested datafile.", user.getIdentifier()); + if (sessionUser != null) { + logger.log(Level.FINE, "Session-based auth: user {0} has NO access rights on the requested datafile.", sessionUser.getIdentifier()); } - if (apiTokenUser != null) { - logger.log(Level.FINE, "Token-based auth: user {0} has NO access rights on the requested datafile.", apiTokenUser.getIdentifier()); + if (apiUser != null) { + logger.log(Level.FINE, "Token-based auth: user {0} has NO access rights on the requested datafile.", apiUser.getIdentifier()); } return false; } - private User findAPITokenUser() { - User apiTokenUser = null; - try { - logger.fine("calling apiTokenUser = findUserOrDie()..."); - apiTokenUser = findUserOrDie(); - /* - * The idea here is to not let a guest user returned from findUserOrDie (which - * happens when there is no key/token, and which we want if there's no session) - * from overriding an authenticated session user. - */ - if(apiTokenUser instanceof GuestUser) { - if(session!=null && session.getUser()!=null) { - //The apiTokenUser, if set, will override the sessionUser in permissions calcs, so set it to null if we have a session user - apiTokenUser=null; - } + private User findAPITokenUser(User requestUser) { + User apiTokenUser = requestUser; + /* + * The idea here is to not let a guest user coming from the request (which + * happens when there is no key/token, and which we want if there's no session) + * from overriding an authenticated session user. + */ + if(apiTokenUser instanceof GuestUser) { + if(session!=null && session.getUser()!=null) { + //The apiTokenUser, if set, will override the sessionUser in permissions calcs, so set it to null if we have a session user + apiTokenUser=null; } - return apiTokenUser; - } catch (WrappedResponse wr) { - logger.log(Level.FINE, "Message from findUserOrDie(): {0}", wr.getMessage()); - return null; } + return apiTokenUser; } - private URI handleCustomZipDownload(String customZipServiceUrl, String fileIds, User apiTokenUser, UriInfo uriInfo, HttpHeaders headers, boolean donotwriteGBResponse, boolean orig) throws WebApplicationException { + private URI handleCustomZipDownload(User user, String customZipServiceUrl, String fileIds, User apiTokenUser, UriInfo uriInfo, HttpHeaders headers, boolean donotwriteGBResponse, boolean orig) throws WebApplicationException { String zipServiceKey = null; Timestamp timestamp = null; @@ -1907,7 +1902,7 @@ private URI handleCustomZipDownload(String customZipServiceUrl, String fileIds, DataFile file = dataFileService.find(fileId); if (file != null) { validFileCount++; - if (isAccessAuthorized(file)) { + if (isAccessAuthorized(user, file)) { logger.fine("adding datafile (id=" + file.getId() + ") to the download list of the ZippedDownloadInstance."); if (donotwriteGBResponse != true && file.isReleased()) { GuestbookResponse gbr = guestbookResponseService.initAPIGuestbookResponse(file.getOwner(), file, session, apiTokenUser); From 1304ed6ed78478a13e526f2f72ba3fd8e5cca2f8 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 2 Feb 2023 10:52:51 +0000 Subject: [PATCH 18/30] Refactor: replaced rest of findUserOrDie calls and removed the method from AbstractApiBean in addition to its associated methods --- .../iq/dataverse/api/AbstractApiBean.java | 88 ------------------- .../edu/harvard/iq/dataverse/api/EditDDI.java | 34 +++---- .../edu/harvard/iq/dataverse/api/Files.java | 18 ++-- 3 files changed, 20 insertions(+), 120 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index 52fbd444dc1..2becdf98bf3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -371,101 +371,13 @@ protected RoleAssignee findAssignee(String identifier) { } /** - * * @param apiKey the key to find the user with * @return the user, or null - * @see #findUserOrDie(java.lang.String) */ protected AuthenticatedUser findUserByApiToken( String apiKey ) { return authSvc.lookupUser(apiKey); } - /** - * Returns the user of pointed by the API key, or the guest user - * @return a user, may be a guest user. - * @throws edu.harvard.iq.dataverse.api.AbstractApiBean.WrappedResponse iff there is an api key present, but it is invalid. - * - * @deprecated Do not use this method. - * This method is expected to be removed once all API endpoints use the filter-based authentication. - * @see #9293 - * In case you are implementing a new endpoint that requires user authentication, here is an example of how to apply the new filter-based authentication: - * {@link edu.harvard.iq.dataverse.api.Datasets#getDataset(ContainerRequestContext, String, UriInfo, HttpHeaders, HttpServletResponse)} - */ - @Deprecated - protected User findUserOrDie() throws WrappedResponse { - final String requestApiKey = getRequestApiKey(); - final String requestWFKey = getRequestWorkflowInvocationID(); - if (requestApiKey == null && requestWFKey == null && getRequestParameter(UrlSignerUtil.SIGNED_URL_TOKEN)==null) { - return GuestUser.get(); - } - PrivateUrlUser privateUrlUser = privateUrlSvc.getPrivateUrlUserFromToken(requestApiKey); - // For privateUrlUsers restricted to anonymized access, all api calls are off-limits except for those used in the UI - // to download the file or image thumbs - if (privateUrlUser != null) { - if (privateUrlUser.hasAnonymizedAccess()) { - String pathInfo = httpRequest.getPathInfo(); - String prefix= "/access/datafile/"; - if (!(pathInfo.startsWith(prefix) && !pathInfo.substring(prefix.length()).contains("/"))) { - logger.info("Anonymized access request for " + pathInfo); - throw new WrappedResponse(error(Status.UNAUTHORIZED, "API Access not allowed with this Key")); - } - } - return privateUrlUser; - } - return findAuthenticatedUserOrDie(requestApiKey, requestWFKey); - } - - private AuthenticatedUser findAuthenticatedUserOrDie( String key, String wfid ) throws WrappedResponse { - if (key != null) { - // No check for deactivated user because it's done in authSvc.lookupUser. - AuthenticatedUser authUser = authSvc.lookupUser(key); - - if (authUser != null) { - authUser = userSvc.updateLastApiUseTime(authUser); - - return authUser; - } - else { - throw new WrappedResponse(badApiKey(key)); - } - } else if (wfid != null) { - AuthenticatedUser authUser = authSvc.lookupUserForWorkflowInvocationID(wfid); - if (authUser != null) { - return authUser; - } else { - throw new WrappedResponse(badWFKey(wfid)); - } - } else if (getRequestParameter(UrlSignerUtil.SIGNED_URL_TOKEN) != null) { - AuthenticatedUser authUser = getAuthenticatedUserFromSignedUrl(); - if (authUser != null) { - return authUser; - } - } - //Just send info about the apiKey - workflow users will learn about invocationId elsewhere - throw new WrappedResponse(badApiKey(null)); - } - - private AuthenticatedUser getAuthenticatedUserFromSignedUrl() { - AuthenticatedUser authUser = null; - // The signedUrl contains a param telling which user this is supposed to be for. - // We don't trust this. So we lookup that user, and get their API key, and use - // that as a secret in validating the signedURL. If the signature can't be - // validated with their key, the user (or their API key) has been changed and - // we reject the request. - // ToDo - add null checks/ verify that calling methods catch things. - String user = httpRequest.getParameter("user"); - AuthenticatedUser targetUser = authSvc.getAuthenticatedUser(user); - String key = JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("") - + authSvc.findApiTokenByUser(targetUser).getTokenString(); - String signedUrl = httpRequest.getRequestURL().toString() + "?" + httpRequest.getQueryString(); - String method = httpRequest.getMethod(); - boolean validated = UrlSignerUtil.isValidUrl(signedUrl, user, method, key); - if (validated) { - authUser = targetUser; - } - return authUser; - } - protected Dataverse findDataverseOrDie( String dvIdtf ) throws WrappedResponse { Dataverse dv = findDataverse(dvIdtf); if ( dv == null ) { diff --git a/src/main/java/edu/harvard/iq/dataverse/api/EditDDI.java b/src/main/java/edu/harvard/iq/dataverse/api/EditDDI.java index 82938fd3687..513f27c9e3d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/EditDDI.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/EditDDI.java @@ -1,5 +1,6 @@ package edu.harvard.iq.dataverse.api; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.authorization.Permission; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.User; @@ -35,6 +36,7 @@ import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.Response; import javax.ws.rs.core.Context; import javax.ws.rs.Path; @@ -95,9 +97,10 @@ public class EditDDI extends AbstractApiBean { @PUT - @Consumes("application/xml") + @AuthRequired @Path("{fileId}") - public Response edit (InputStream body, @PathParam("fileId") String fileId) { + @Consumes("application/xml") + public Response edit(@Context ContainerRequestContext crc, InputStream body, @PathParam("fileId") String fileId) { DataFile dataFile = null; try { dataFile = findDataFileOrDie(fileId); @@ -105,7 +108,7 @@ public Response edit (InputStream body, @PathParam("fileId") String fileId) { } catch (WrappedResponse ex) { return ex.getResponse(); } - User apiTokenUser = checkAuth(dataFile); + User apiTokenUser = checkAuth(getRequestUser(crc), dataFile); if (apiTokenUser == null) { return unauthorized("Cannot edit metadata, access denied" ); @@ -426,27 +429,10 @@ private boolean AreDefaultValues(VariableMetadata varMet) { } - private User checkAuth(DataFile dataFile) { - - User apiTokenUser = null; - - try { - apiTokenUser = findUserOrDie(); - } catch (WrappedResponse wr) { - apiTokenUser = null; - logger.log(Level.FINE, "Message from findUserOrDie(): {0}", wr.getMessage()); - } - - if (apiTokenUser != null) { - // used in an API context - if (!permissionService.requestOn(createDataverseRequest(apiTokenUser), dataFile.getOwner()).has(Permission.EditDataset)) { - apiTokenUser = null; - } + private User checkAuth(User requestUser, DataFile dataFile) { + if (!permissionService.requestOn(createDataverseRequest(requestUser), dataFile.getOwner()).has(Permission.EditDataset)) { + return null; } - - return apiTokenUser; - + return requestUser; } } - - diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index 047fa509853..44c7e944556 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -436,23 +436,25 @@ public Response updateFileMetadata(@Context ContainerRequestContext crc, @FormDa .build(); } - @GET + @GET + @AuthRequired @Path("{id}/draft") - public Response getFileDataDraft(@PathParam("id") String fileIdOrPersistentId, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WrappedResponse, Exception { - return getFileDataResponse(fileIdOrPersistentId, uriInfo, headers, response, true); + public Response getFileDataDraft(@Context ContainerRequestContext crc, @PathParam("id") String fileIdOrPersistentId, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WrappedResponse, Exception { + return getFileDataResponse(getRequestUser(crc), fileIdOrPersistentId, uriInfo, headers, response, true); } - @GET + @GET + @AuthRequired @Path("{id}") - public Response getFileData(@PathParam("id") String fileIdOrPersistentId, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WrappedResponse, Exception { - return getFileDataResponse(fileIdOrPersistentId, uriInfo, headers, response, false); + public Response getFileData(@Context ContainerRequestContext crc, @PathParam("id") String fileIdOrPersistentId, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws WrappedResponse, Exception { + return getFileDataResponse(getRequestUser(crc), fileIdOrPersistentId, uriInfo, headers, response, false); } - private Response getFileDataResponse(String fileIdOrPersistentId, UriInfo uriInfo, HttpHeaders headers, HttpServletResponse response, boolean draft ){ + private Response getFileDataResponse(User user, String fileIdOrPersistentId, UriInfo uriInfo, HttpHeaders headers, HttpServletResponse response, boolean draft ){ DataverseRequest req; try { - req = createDataverseRequest(findUserOrDie()); + req = createDataverseRequest(user); } catch (Exception e) { return error(BAD_REQUEST, "Error attempting to request information. Maybe a bad API token?"); } From 6a64c0a6fffd7f6ce5686dc0fff3d551d73a380d Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 2 Feb 2023 15:13:17 +0000 Subject: [PATCH 19/30] Refactor: unused method, imports and declared EJBs removed --- .../iq/dataverse/api/AbstractApiBean.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index 2becdf98bf3..022055e1a59 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -30,8 +30,6 @@ import edu.harvard.iq.dataverse.authorization.RoleAssignee; import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; -import edu.harvard.iq.dataverse.authorization.users.GuestUser; -import edu.harvard.iq.dataverse.authorization.users.PrivateUrlUser; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailServiceBean; import edu.harvard.iq.dataverse.datacapturemodule.DataCaptureModuleServiceBean; @@ -43,14 +41,11 @@ import edu.harvard.iq.dataverse.externaltools.ExternalToolServiceBean; import edu.harvard.iq.dataverse.license.LicenseServiceBean; import edu.harvard.iq.dataverse.metrics.MetricsServiceBean; -import edu.harvard.iq.dataverse.privateurl.PrivateUrlServiceBean; import edu.harvard.iq.dataverse.locality.StorageSiteServiceBean; import edu.harvard.iq.dataverse.search.savedsearch.SavedSearchServiceBean; -import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.SystemConfig; -import edu.harvard.iq.dataverse.util.UrlSignerUtil; import edu.harvard.iq.dataverse.util.json.JsonParser; import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; import edu.harvard.iq.dataverse.validation.PasswordValidatorServiceBean; @@ -58,7 +53,6 @@ import java.net.URI; import java.util.Arrays; import java.util.Collections; -import java.util.Enumeration; import java.util.UUID; import java.util.concurrent.Callable; import java.util.logging.Level; @@ -77,7 +71,6 @@ import javax.persistence.NoResultException; import javax.persistence.PersistenceContext; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.*; import javax.ws.rs.core.Response.ResponseBuilder; @@ -209,9 +202,6 @@ String getWrappedMessageWhenJson() { @EJB protected SavedSearchServiceBean savedSearchSvc; - @EJB - protected PrivateUrlServiceBean privateUrlSvc; - @EJB protected ConfirmEmailServiceBean confirmEmailSvc; @@ -320,13 +310,6 @@ protected String getRequestApiKey() { return headerParamApiKey!=null ? headerParamApiKey : queryParamApiKey; } - - protected String getRequestWorkflowInvocationID() { - String headerParamWFKey = httpRequest.getHeader(DATAVERSE_WORKFLOW_INVOCATION_HEADER_NAME); - String queryParamWFKey = httpRequest.getParameter("invocationID"); - - return headerParamWFKey!=null ? headerParamWFKey : queryParamWFKey; - } protected User getRequestUser(ContainerRequestContext crc) { return (User) crc.getProperty(ApiConstants.CONTAINER_REQUEST_CONTEXT_USER); From 590995067246b1492f42835c9195f17e2756e80a Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 2 Feb 2023 15:18:38 +0000 Subject: [PATCH 20/30] Fixed: DataversesTest --- .../iq/dataverse/api/DataversesTest.java | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataversesTest.java b/src/test/java/edu/harvard/iq/dataverse/api/DataversesTest.java index 10113110b66..1ebba65c9c4 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesTest.java @@ -12,6 +12,7 @@ import edu.harvard.iq.dataverse.api.dto.DataverseMetadataBlockFacetDTO; import edu.harvard.iq.dataverse.api.imports.ImportServiceBean; import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroupServiceBean; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.PrivateUrlUser; import edu.harvard.iq.dataverse.engine.command.impl.ListMetadataBlockFacetsCommand; import edu.harvard.iq.dataverse.engine.command.impl.UpdateMetadataBlockFacetRootCommand; @@ -31,6 +32,7 @@ import org.mockito.junit.MockitoJUnitRunner; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.Response; import java.util.Arrays; import java.util.Collections; @@ -68,6 +70,8 @@ public class DataversesTest { private DataverseServiceBean dataverseService; @Mock private SwordServiceBean swordService; + @Mock + private ContainerRequestContext containerRequestContext; @InjectMocks private Dataverses target; @@ -84,13 +88,14 @@ public void beforeEachTest() { Mockito.lenient().when(dataverseService.findByAlias(VALID_DATAVERSE.getAlias())).thenReturn(VALID_DATAVERSE); Mockito.lenient().when(httpRequest.getHeader("X-Dataverse-key")).thenReturn(UUID.randomUUID().toString()); Mockito.lenient().when(privateUrlSvc.getPrivateUrlUserFromToken(Mockito.anyString())).thenReturn(new PrivateUrlUser(0)); + Mockito.lenient().when(containerRequestContext.getProperty(ApiConstants.CONTAINER_REQUEST_CONTEXT_USER)).thenReturn(new AuthenticatedUser()); } @Test public void listMetadataBlockFacets_should_return_404_when_dataverse_is_not_found() { String dataverseAlias = UUID.randomUUID().toString(); Mockito.when(dataverseService.findByAlias(dataverseAlias)).thenReturn(null); - Response result = target.listMetadataBlockFacets(dataverseAlias); + Response result = target.listMetadataBlockFacets(containerRequestContext, dataverseAlias); MatcherAssert.assertThat(result.getStatus(), Matchers.is(404)); Mockito.verifyNoMoreInteractions(engineSvc); @@ -106,7 +111,7 @@ public void listMetadataBlockFacets_should_return_the_list_of_metadataBlockFacet dataverseMetadataBlockFacet.setMetadataBlock(metadataBlock); Mockito.when(engineSvc.submit(Mockito.any(ListMetadataBlockFacetsCommand.class))).thenReturn(Arrays.asList(dataverseMetadataBlockFacet)); - Response response = target.listMetadataBlockFacets(VALID_DATAVERSE.getAlias()); + Response response = target.listMetadataBlockFacets(containerRequestContext, VALID_DATAVERSE.getAlias()); MatcherAssert.assertThat(response.getStatus(), Matchers.is(200)); MatcherAssert.assertThat(response.getEntity(), Matchers.notNullValue()); @@ -125,7 +130,7 @@ public void listMetadataBlockFacets_should_return_the_list_of_metadataBlockFacet public void listMetadataBlockFacets_should_return_empty_list_when_metadata_block_facet_is_null() throws Exception{ Mockito.when(engineSvc.submit(Mockito.any(ListMetadataBlockFacetsCommand.class))).thenReturn(null); - Response response = target.listMetadataBlockFacets(VALID_DATAVERSE.getAlias()); + Response response = target.listMetadataBlockFacets(containerRequestContext, VALID_DATAVERSE.getAlias()); MatcherAssert.assertThat(response.getStatus(), Matchers.is(200)); DataverseMetadataBlockFacetDTO result = (DataverseMetadataBlockFacetDTO)response.getEntity(); @@ -141,7 +146,7 @@ public void listMetadataBlockFacets_should_return_empty_list_when_metadata_block public void setMetadataBlockFacets_should_return_404_when_dataverse_is_not_found() { String dataverseAlias = UUID.randomUUID().toString(); Mockito.when(dataverseService.findByAlias(dataverseAlias)).thenReturn(null); - Response result = target.setMetadataBlockFacets(dataverseAlias, Collections.emptyList()); + Response result = target.setMetadataBlockFacets(containerRequestContext, dataverseAlias, Collections.emptyList()); MatcherAssert.assertThat(result.getStatus(), Matchers.is(404)); Mockito.verifyNoMoreInteractions(engineSvc); @@ -154,7 +159,7 @@ public void setMetadataBlockFacets_should_return_400_when_dataverse_has_metadata Mockito.when(dataverse.isMetadataBlockFacetRoot()).thenReturn(false); Mockito.when(dataverseService.findByAlias(dataverseAlias)).thenReturn(dataverse); - Response result = target.setMetadataBlockFacets(dataverseAlias, Collections.emptyList()); + Response result = target.setMetadataBlockFacets(containerRequestContext, dataverseAlias, Collections.emptyList()); MatcherAssert.assertThat(result.getStatus(), Matchers.is(400)); Mockito.verifyNoMoreInteractions(engineSvc); @@ -165,7 +170,7 @@ public void setMetadataBlockFacets_should_return_400_when_invalid_metadata_block Mockito.when(metadataBlockSvc.findByName("valid_block")).thenReturn(new MetadataBlock()); Mockito.when(metadataBlockSvc.findByName("invalid_block")).thenReturn(null); List metadataBlocks = Arrays.asList("valid_block", "invalid_block"); - Response result = target.setMetadataBlockFacets(VALID_DATAVERSE.getAlias(), metadataBlocks); + Response result = target.setMetadataBlockFacets(containerRequestContext, VALID_DATAVERSE.getAlias(), metadataBlocks); MatcherAssert.assertThat(result.getStatus(), Matchers.is(400)); Mockito.verifyNoMoreInteractions(engineSvc); @@ -176,7 +181,7 @@ public void setMetadataBlockFacets_should_return_200_when_update_is_successful() MetadataBlock validBlock = new MetadataBlock(); Mockito.when(metadataBlockSvc.findByName("valid_block")).thenReturn(validBlock); List metadataBlocks = Arrays.asList("valid_block"); - Response result = target.setMetadataBlockFacets(VALID_DATAVERSE.getAlias(), metadataBlocks); + Response result = target.setMetadataBlockFacets(containerRequestContext, VALID_DATAVERSE.getAlias(), metadataBlocks); MatcherAssert.assertThat(result.getStatus(), Matchers.is(200)); ArgumentCaptor updateCommand = ArgumentCaptor.forClass(UpdateMetadataBlockFacetsCommand.class); @@ -190,7 +195,7 @@ public void setMetadataBlockFacets_should_return_200_when_update_is_successful() @Test public void setMetadataBlockFacets_should_support_empty_metadatablock_list() throws Exception{ - Response result = target.setMetadataBlockFacets(VALID_DATAVERSE.getAlias(), Collections.emptyList()); + Response result = target.setMetadataBlockFacets(containerRequestContext, VALID_DATAVERSE.getAlias(), Collections.emptyList()); MatcherAssert.assertThat(result.getStatus(), Matchers.is(200)); Mockito.verify(engineSvc).submit(Mockito.any(UpdateMetadataBlockFacetsCommand.class)); @@ -200,7 +205,7 @@ public void setMetadataBlockFacets_should_support_empty_metadatablock_list() thr public void updateMetadataBlockFacetsRoot_should_return_404_when_dataverse_is_not_found() { String dataverseAlias = UUID.randomUUID().toString(); Mockito.when(dataverseService.findByAlias(dataverseAlias)).thenReturn(null); - Response result = target.updateMetadataBlockFacetsRoot(dataverseAlias, "true"); + Response result = target.updateMetadataBlockFacetsRoot(containerRequestContext, dataverseAlias, "true"); MatcherAssert.assertThat(result.getStatus(), Matchers.is(404)); Mockito.verifyNoMoreInteractions(engineSvc); @@ -208,7 +213,7 @@ public void updateMetadataBlockFacetsRoot_should_return_404_when_dataverse_is_no @Test public void updateMetadataBlockFacetsRoot_should_return_400_when_invalid_boolean() throws Exception{ - Response result = target.updateMetadataBlockFacetsRoot(VALID_DATAVERSE.getAlias(), "invalid"); + Response result = target.updateMetadataBlockFacetsRoot(containerRequestContext, VALID_DATAVERSE.getAlias(), "invalid"); MatcherAssert.assertThat(result.getStatus(), Matchers.is(400)); Mockito.verifyNoMoreInteractions(engineSvc); @@ -217,7 +222,7 @@ public void updateMetadataBlockFacetsRoot_should_return_400_when_invalid_boolean @Test public void updateMetadataBlockFacetsRoot_should_return_200_and_make_no_update_when_dataverse_is_found_and_facet_root_has_not_changed() { // VALID_DATAVERSE.metadataBlockFacetRoot is true - Response result = target.updateMetadataBlockFacetsRoot(VALID_DATAVERSE.getAlias(), "true"); + Response result = target.updateMetadataBlockFacetsRoot(containerRequestContext, VALID_DATAVERSE.getAlias(), "true"); MatcherAssert.assertThat(result.getStatus(), Matchers.is(200)); Mockito.verifyZeroInteractions(engineSvc); @@ -226,7 +231,7 @@ public void updateMetadataBlockFacetsRoot_should_return_200_and_make_no_update_w @Test public void updateMetadataBlockFacetsRoot_should_return_200_and_execute_command_when_dataverse_is_found_and_facet_root_has_changed() throws Exception { // VALID_DATAVERSE.metadataBlockFacetRoot is true - Response result = target.updateMetadataBlockFacetsRoot(VALID_DATAVERSE.getAlias(), "false"); + Response result = target.updateMetadataBlockFacetsRoot(containerRequestContext, VALID_DATAVERSE.getAlias(), "false"); MatcherAssert.assertThat(result.getStatus(), Matchers.is(200)); ArgumentCaptor updateRootCommand = ArgumentCaptor.forClass(UpdateMetadataBlockFacetRootCommand.class); From 94082ebbe33c718c9a045d624bc5bd39f7a61f2b Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 3 Feb 2023 11:42:33 +0000 Subject: [PATCH 21/30] Added: minor refactoring and IT test fixes --- .../iq/dataverse/api/AbstractApiBean.java | 13 ++-------- .../iq/dataverse/api/BuiltinUsers.java | 24 +++++++++---------- .../edu/harvard/iq/dataverse/api/AdminIT.java | 2 +- .../harvard/iq/dataverse/api/DatasetsIT.java | 2 +- .../harvard/iq/dataverse/api/SearchIT.java | 6 ++--- 5 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index 022055e1a59..8590c45f91c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -90,6 +90,7 @@ public abstract class AbstractApiBean { private static final String ALIAS_KEY=":alias"; public static final String STATUS_WF_IN_PROGRESS = "WORKFLOW_IN_PROGRESS"; public static final String DATAVERSE_WORKFLOW_INVOCATION_HEADER_NAME = "X-Dataverse-invocationID"; + public static final String RESPONSE_MESSAGE_AUTHENTICATED_USER_REQUIRED = "Only authenticated users can perform the requested operation"; /** * Utility class to convey a proper error response using Java's exceptions. @@ -757,19 +758,9 @@ protected Response forbidden( String msg ) { protected Response conflict( String msg ) { return error( Status.CONFLICT, msg ); } - - protected Response badApiKey( String apiKey ) { - return error(Status.UNAUTHORIZED, (apiKey != null ) ? "Bad api key " : "Please provide a key query parameter (?key=XXX) or via the HTTP header " + DATAVERSE_KEY_HEADER_NAME); - } - - protected Response badWFKey( String wfId ) { - String message = (wfId != null ) ? "Bad workflow invocationId " : "Please provide an invocationId query parameter (?invocationId=XXX) or via the HTTP header " + DATAVERSE_WORKFLOW_INVOCATION_HEADER_NAME; - return error(Status.UNAUTHORIZED, message ); - } protected Response authenticatedUserRequired() { - String message = "Only authenticated users can perform the requested operation"; - return error(Status.UNAUTHORIZED, message ); + return error(Status.UNAUTHORIZED, RESPONSE_MESSAGE_AUTHENTICATED_USER_REQUIRED); } protected Response permissionError( PermissionException pe ) { diff --git a/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java b/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java index 0d0176eb636..9262cc6ef46 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java @@ -3,6 +3,7 @@ import edu.harvard.iq.dataverse.Dataverse; import edu.harvard.iq.dataverse.UserNotification; import edu.harvard.iq.dataverse.actionlogging.ActionLogRecord; +import edu.harvard.iq.dataverse.api.auth.ApiKeyAuthMechanism; import edu.harvard.iq.dataverse.authorization.UserRecordIdentifier; import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinAuthenticationProvider; import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUser; @@ -212,17 +213,14 @@ private Response internalSave(BuiltinUser user, String password, String key, Boo } } + /*** + * This method was moved here from AbstractApiBean during the filter-based auth + * refactoring, in order to preserve the existing BuiltinUsers endpoints behavior. + * + * @param apiKey from request + * @return error Response + */ + private Response badApiKey(String apiKey) { + return error(Status.UNAUTHORIZED, (apiKey != null) ? "Bad api key " : "Please provide a key query parameter (?key=XXX) or via the HTTP header " + ApiKeyAuthMechanism.DATAVERSE_API_KEY_REQUEST_HEADER_NAME); + } } - - - - - - - - - - - - - diff --git a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java index bcee8d18e17..99e7cb6d15c 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java @@ -96,7 +96,7 @@ public void testFilterAuthenticatedUsersForbidden() throws Exception { String nonSuperUsername = UtilIT.getUsernameFromResponse(createUserResponse); Response filterResponseBadToken = UtilIT.filterAuthenticatedUsers(nonSuperuserApiToken, null, null, null, null); - filterResponseBadToken.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); + filterResponseBadToken.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); // delete user Response deleteNonSuperuser = UtilIT.deleteUser(nonSuperUsername); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java index 3c4706f10c2..5e8c603784a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java @@ -1475,7 +1475,7 @@ public void testCreateDatasetWithDcmDependency() { getRsyncScriptPermErrorGuest.then().assertThat() .statusCode(UNAUTHORIZED.getStatusCode()) .contentType(ContentType.JSON) - .body("message", equalTo("Please provide a key query parameter (?key=XXX) or via the HTTP header X-Dataverse-key")); + .body("message", equalTo(AbstractApiBean.RESPONSE_MESSAGE_AUTHENTICATED_USER_REQUIRED)); Response createNoPermsUser = UtilIT.createRandomUser(); String noPermsUsername = UtilIT.getUsernameFromResponse(createNoPermsUser); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java b/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java index 61a55a88a3b..7a16b78ba98 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java @@ -155,7 +155,7 @@ public void testSearchPermisions() throws InterruptedException { Response dataverse47behaviorOfTokensBeingRequired = UtilIT.search("id:dataset_" + datasetId1, nullToken); dataverse47behaviorOfTokensBeingRequired.prettyPrint(); dataverse47behaviorOfTokensBeingRequired.then().assertThat() - .body("message", CoreMatchers.equalTo("Please provide a key query parameter (?key=XXX) or via the HTTP header X-Dataverse-key")) + .body("message", CoreMatchers.equalTo(AbstractApiBean.RESPONSE_MESSAGE_AUTHENTICATED_USER_REQUIRED)) .statusCode(UNAUTHORIZED.getStatusCode()); Response reEnableTokenlessSearch = UtilIT.deleteSetting(SettingsServiceBean.Key.SearchApiRequiresToken); @@ -973,12 +973,12 @@ public void testSubtreePermissions() { Response searchFakeSubtree = UtilIT.search(searchPart, apiToken, "&subtree=fake"); searchFakeSubtree.prettyPrint(); searchFakeSubtree.then().assertThat() - .statusCode(400); + .statusCode(BAD_REQUEST.getStatusCode()); Response searchFakeSubtreeNoAPI = UtilIT.search(searchPart, null, "&subtree=fake"); searchFakeSubtreeNoAPI.prettyPrint(); searchFakeSubtreeNoAPI.then().assertThat() - .statusCode(400); + .statusCode(FORBIDDEN.getStatusCode()); Response searchUnpublishedSubtree = UtilIT.search(searchPart, apiToken, "&subtree="+dataverseAlias); searchUnpublishedSubtree.prettyPrint(); From 56fb7984ceb36d9a8c72434cb578027017165ec9 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 3 Feb 2023 13:55:38 +0000 Subject: [PATCH 22/30] Fixed: SearchIT.testSubtreePermissions wrong response code when no API key provided --- src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java b/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java index 7a16b78ba98..ae108f5e127 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java @@ -978,7 +978,7 @@ public void testSubtreePermissions() { Response searchFakeSubtreeNoAPI = UtilIT.search(searchPart, null, "&subtree=fake"); searchFakeSubtreeNoAPI.prettyPrint(); searchFakeSubtreeNoAPI.then().assertThat() - .statusCode(FORBIDDEN.getStatusCode()); + .statusCode(UNAUTHORIZED.getStatusCode()); Response searchUnpublishedSubtree = UtilIT.search(searchPart, apiToken, "&subtree="+dataverseAlias); searchUnpublishedSubtree.prettyPrint(); From 5d7b0ec2d9e9247cf35ddf22fcad9f4c6dc78b0c Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 3 Feb 2023 14:10:39 +0000 Subject: [PATCH 23/30] Fixed: DeactivateUsersIT.testDeactivateUser --- .../java/edu/harvard/iq/dataverse/api/DeactivateUsersIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DeactivateUsersIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DeactivateUsersIT.java index de2a1d422c0..801c7cbf7e7 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DeactivateUsersIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DeactivateUsersIT.java @@ -104,7 +104,7 @@ public void testDeactivateUser() { Response getUserDeactivated = UtilIT.getAuthenticatedUserByToken(apiToken); getUserDeactivated.prettyPrint(); - getUserDeactivated.then().assertThat().statusCode(BAD_REQUEST.getStatusCode()); + getUserDeactivated.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); Response userTracesAfterDeactivate = UtilIT.getUserTraces(username, superuserApiToken); userTracesAfterDeactivate.prettyPrint(); From b4459fdbf87cab31dc3b69e39350af54b1bb42ae Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 3 Feb 2023 14:14:20 +0000 Subject: [PATCH 24/30] Fixed: BuiltinUsersIT.testFindByToken for bad API key --- .../java/edu/harvard/iq/dataverse/api/BuiltinUsersIT.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/BuiltinUsersIT.java b/src/test/java/edu/harvard/iq/dataverse/api/BuiltinUsersIT.java index 0f8385409a3..301cd24b35b 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/BuiltinUsersIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/BuiltinUsersIT.java @@ -5,6 +5,7 @@ import com.jayway.restassured.http.ContentType; import com.jayway.restassured.path.json.JsonPath; import com.jayway.restassured.response.Response; +import edu.harvard.iq.dataverse.api.auth.ApiKeyAuthMechanism; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import java.util.AbstractMap; import java.util.Arrays; @@ -16,9 +17,9 @@ import java.util.stream.Stream; import javax.json.Json; import javax.json.JsonObjectBuilder; -import static javax.ws.rs.core.Response.Status.BAD_REQUEST; import static javax.ws.rs.core.Response.Status.OK; import static javax.ws.rs.core.Response.Status.FORBIDDEN; +import static javax.ws.rs.core.Response.Status.UNAUTHORIZED; import static junit.framework.Assert.assertEquals; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.startsWith; @@ -93,8 +94,8 @@ public void testFindByToken() { getUserAsJsonByToken = UtilIT.getAuthenticatedUserByToken("badcode"); getUserAsJsonByToken.then().assertThat() .body("status", equalTo("ERROR")) - .body("message", equalTo("User with token badcode not found.")) - .statusCode(BAD_REQUEST.getStatusCode()); + .body("message", equalTo(ApiKeyAuthMechanism.RESPONSE_MESSAGE_BAD_API_KEY)) + .statusCode(UNAUTHORIZED.getStatusCode()); } From 2f64428f0c9dd34b7b8da97d7ab44b9bdde4c1cf Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 3 Feb 2023 14:17:50 +0000 Subject: [PATCH 25/30] Fixed: DatasetsIT.testPrivateUrl wrong status code when bad API token provided --- src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java index 5e8c603784a..6f3ac70f412 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java @@ -1052,7 +1052,7 @@ public void testPrivateUrl() { Response downloadFile = UtilIT.downloadFile(fileId, tokenForPrivateUrlUser); assertEquals(OK.getStatusCode(), downloadFile.getStatusCode()); Response downloadFileBadToken = UtilIT.downloadFile(fileId, "junk"); - assertEquals(FORBIDDEN.getStatusCode(), downloadFileBadToken.getStatusCode()); + assertEquals(UNAUTHORIZED.getStatusCode(), downloadFileBadToken.getStatusCode()); Response notPermittedToListRoleAssignment = UtilIT.getRoleAssignmentsOnDataset(datasetId.toString(), null, userWithNoRolesApiToken); assertEquals(UNAUTHORIZED.getStatusCode(), notPermittedToListRoleAssignment.getStatusCode()); Response roleAssignments = UtilIT.getRoleAssignmentsOnDataset(datasetId.toString(), null, apiToken); From b428cb68a69a212025f58a481a58ade0f925cc21 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 3 Feb 2023 14:21:25 +0000 Subject: [PATCH 26/30] Fixed: AdminIT tests anon calls status codes --- src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java index 99e7cb6d15c..82d68897b87 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java @@ -47,7 +47,7 @@ public static void setUp() { public void testListAuthenticatedUsers() throws Exception { Response anon = UtilIT.listAuthenticatedUsers(""); anon.prettyPrint(); - anon.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); + anon.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); Response createNonSuperuser = UtilIT.createRandomUser(); @@ -84,7 +84,7 @@ public void testFilterAuthenticatedUsersForbidden() throws Exception { // -------------------------------------------- Response anon = UtilIT.filterAuthenticatedUsers("", null, null, null, null); anon.prettyPrint(); - anon.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); + anon.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); // -------------------------------------------- // Forbidden: Try with a regular user--*not a superuser* @@ -287,7 +287,7 @@ public void testConvertShibUserToBuiltin() throws Exception { Response builtinToShibAnon = UtilIT.migrateBuiltinToShib(data, ""); builtinToShibAnon.prettyPrint(); - builtinToShibAnon.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); + builtinToShibAnon.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); Response createSuperuser = UtilIT.createRandomUser(); String superuserUsername = UtilIT.getUsernameFromResponse(createSuperuser); @@ -381,7 +381,7 @@ public void testConvertDeactivateUserToShib() { Response builtinToShibAnon = UtilIT.migrateBuiltinToShib(data, ""); builtinToShibAnon.prettyPrint(); - builtinToShibAnon.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); + builtinToShibAnon.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); Response createSuperuser = UtilIT.createRandomUser(); String superuserUsername = UtilIT.getUsernameFromResponse(createSuperuser); From 735712a7f86602963ae648438e4379a140c399c9 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 3 Feb 2023 15:26:17 +0000 Subject: [PATCH 27/30] Fixed: AdminIT tests --- .../edu/harvard/iq/dataverse/api/AdminIT.java | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java index 82d68897b87..043b2641d86 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java @@ -38,6 +38,8 @@ public class AdminIT { private static final Logger logger = Logger.getLogger(AdminIT.class.getCanonicalName()); + private final String testNonSuperuserApiToken = createTestNonSuperuserApiToken(); + @BeforeClass public static void setUp() { RestAssured.baseURI = UtilIT.getRestAssuredBaseUri(); @@ -45,16 +47,11 @@ public static void setUp() { @Test public void testListAuthenticatedUsers() throws Exception { - Response anon = UtilIT.listAuthenticatedUsers(""); + Response anon = UtilIT.listAuthenticatedUsers(testNonSuperuserApiToken); anon.prettyPrint(); - anon.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); + anon.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); - Response createNonSuperuser = UtilIT.createRandomUser(); - - String nonSuperuserUsername = UtilIT.getUsernameFromResponse(createNonSuperuser); - String nonSuperuserApiToken = UtilIT.getApiTokenFromResponse(createNonSuperuser); - - Response nonSuperuser = UtilIT.listAuthenticatedUsers(nonSuperuserApiToken); + Response nonSuperuser = UtilIT.listAuthenticatedUsers(testNonSuperuserApiToken); nonSuperuser.prettyPrint(); nonSuperuser.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); @@ -69,6 +66,9 @@ public void testListAuthenticatedUsers() throws Exception { superuser.prettyPrint(); superuser.then().assertThat().statusCode(OK.getStatusCode()); + Response createNonSuperuser = UtilIT.createRandomUser(); + String nonSuperuserUsername = UtilIT.getUsernameFromResponse(createNonSuperuser); + Response deleteNonSuperuser = UtilIT.deleteUser(nonSuperuserUsername); assertEquals(200, deleteNonSuperuser.getStatusCode()); @@ -82,7 +82,7 @@ public void testFilterAuthenticatedUsersForbidden() throws Exception { // -------------------------------------------- // Forbidden: Try *without* an API token // -------------------------------------------- - Response anon = UtilIT.filterAuthenticatedUsers("", null, null, null, null); + Response anon = UtilIT.filterAuthenticatedUsers(null, null, null, null, null); anon.prettyPrint(); anon.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); @@ -96,7 +96,7 @@ public void testFilterAuthenticatedUsersForbidden() throws Exception { String nonSuperUsername = UtilIT.getUsernameFromResponse(createUserResponse); Response filterResponseBadToken = UtilIT.filterAuthenticatedUsers(nonSuperuserApiToken, null, null, null, null); - filterResponseBadToken.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); + filterResponseBadToken.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); // delete user Response deleteNonSuperuser = UtilIT.deleteUser(nonSuperUsername); @@ -285,9 +285,9 @@ public void testConvertShibUserToBuiltin() throws Exception { String newEmailAddressToUse = "builtin2shib." + UUID.randomUUID().toString().substring(0, 8) + "@mailinator.com"; String data = emailOfUserToConvert + ":" + password + ":" + newEmailAddressToUse; - Response builtinToShibAnon = UtilIT.migrateBuiltinToShib(data, ""); + Response builtinToShibAnon = UtilIT.migrateBuiltinToShib(data, testNonSuperuserApiToken); builtinToShibAnon.prettyPrint(); - builtinToShibAnon.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); + builtinToShibAnon.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); Response createSuperuser = UtilIT.createRandomUser(); String superuserUsername = UtilIT.getUsernameFromResponse(createSuperuser); @@ -315,11 +315,11 @@ public void testConvertShibUserToBuiltin() throws Exception { * the Shib user has an invalid email address: * https://github.com/IQSS/dataverse/issues/2998 */ - Response shibToBuiltinAnon = UtilIT.migrateShibToBuiltin(Long.MAX_VALUE, "", ""); + Response shibToBuiltinAnon = UtilIT.migrateShibToBuiltin(Long.MAX_VALUE, "", testNonSuperuserApiToken); shibToBuiltinAnon.prettyPrint(); shibToBuiltinAnon.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); - Response nonSuperuser = UtilIT.migrateShibToBuiltin(Long.MAX_VALUE, "", ""); + Response nonSuperuser = UtilIT.migrateShibToBuiltin(Long.MAX_VALUE, "", testNonSuperuserApiToken); nonSuperuser.prettyPrint(); nonSuperuser.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); @@ -379,9 +379,9 @@ public void testConvertDeactivateUserToShib() { String newEmailAddressToUse = "builtin2shib." + UUID.randomUUID().toString().substring(0, 8) + "@mailinator.com"; String data = emailOfUserToConvert + ":" + password + ":" + newEmailAddressToUse; - Response builtinToShibAnon = UtilIT.migrateBuiltinToShib(data, ""); + Response builtinToShibAnon = UtilIT.migrateBuiltinToShib(data, testNonSuperuserApiToken); builtinToShibAnon.prettyPrint(); - builtinToShibAnon.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); + builtinToShibAnon.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); Response createSuperuser = UtilIT.createRandomUser(); String superuserUsername = UtilIT.getUsernameFromResponse(createSuperuser); @@ -434,7 +434,7 @@ public void testConvertOAuthUserToBuiltin() throws Exception { String data = emailOfUserToConvert + ":" + password + ":" + newEmailAddressToUse + ":" + providerIdToConvertTo + ":" + newPersistentUserIdInLookupTable; System.out.println("data: " + data); - Response builtinToOAuthAnon = UtilIT.migrateBuiltinToOAuth(data, ""); + Response builtinToOAuthAnon = UtilIT.migrateBuiltinToOAuth(data, testNonSuperuserApiToken); builtinToOAuthAnon.prettyPrint(); builtinToOAuthAnon.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); @@ -465,11 +465,7 @@ public void testConvertOAuthUserToBuiltin() throws Exception { * the OAuth user has an invalid email address: * https://github.com/IQSS/dataverse/issues/2998 */ - Response oauthToBuiltinAnon = UtilIT.migrateOAuthToBuiltin(Long.MAX_VALUE, "", ""); - oauthToBuiltinAnon.prettyPrint(); - oauthToBuiltinAnon.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); - - Response nonSuperuser = UtilIT.migrateOAuthToBuiltin(Long.MAX_VALUE, "", ""); + Response nonSuperuser = UtilIT.migrateOAuthToBuiltin(Long.MAX_VALUE, "", testNonSuperuserApiToken); nonSuperuser.prettyPrint(); nonSuperuser.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); @@ -842,4 +838,10 @@ public void testBannerMessages(){ assertEquals("OK", status); } + + private String createTestNonSuperuserApiToken() { + Response createUserResponse = UtilIT.createRandomUser(); + createUserResponse.then().assertThat().statusCode(OK.getStatusCode()); + return UtilIT.getApiTokenFromResponse(createUserResponse); + } } From 38ba4b0fbf45657b035fc7546be74e9ffefc7bf0 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 3 Feb 2023 17:35:19 +0000 Subject: [PATCH 28/30] Fixed: reverted wrong IT test changes --- src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java | 2 +- src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java index 043b2641d86..2ba06314ddb 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java @@ -82,7 +82,7 @@ public void testFilterAuthenticatedUsersForbidden() throws Exception { // -------------------------------------------- // Forbidden: Try *without* an API token // -------------------------------------------- - Response anon = UtilIT.filterAuthenticatedUsers(null, null, null, null, null); + Response anon = UtilIT.filterAuthenticatedUsers("", null, null, null, null); anon.prettyPrint(); anon.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode()); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java b/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java index ae108f5e127..bc3f9471107 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java @@ -978,7 +978,7 @@ public void testSubtreePermissions() { Response searchFakeSubtreeNoAPI = UtilIT.search(searchPart, null, "&subtree=fake"); searchFakeSubtreeNoAPI.prettyPrint(); searchFakeSubtreeNoAPI.then().assertThat() - .statusCode(UNAUTHORIZED.getStatusCode()); + .statusCode(BAD_REQUEST.getStatusCode()); Response searchUnpublishedSubtree = UtilIT.search(searchPart, apiToken, "&subtree="+dataverseAlias); searchUnpublishedSubtree.prettyPrint(); From 9ce4795e3324022a02f61009439fbd9794bbf7aa Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 9 Feb 2023 17:54:11 +0000 Subject: [PATCH 29/30] Changed: DataRetrieverAPI.retrieveMyDataAsJsonString now uses auth-filter to allow more authentication mechanisms --- .../iq/dataverse/mydata/DataRetrieverAPI.java | 94 +++++++------------ .../iq/dataverse/api/DataRetrieverApiIT.java | 48 ++++++++++ .../edu/harvard/iq/dataverse/api/UtilIT.java | 12 +++ 3 files changed, 94 insertions(+), 60 deletions(-) create mode 100644 src/test/java/edu/harvard/iq/dataverse/api/DataRetrieverApiIT.java 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 6b31cfbecf8..15415667f87 100644 --- a/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java +++ b/src/main/java/edu/harvard/iq/dataverse/mydata/DataRetrieverAPI.java @@ -8,6 +8,7 @@ import edu.harvard.iq.dataverse.DataverseSession; import edu.harvard.iq.dataverse.DvObjectServiceBean; import edu.harvard.iq.dataverse.RoleAssigneeServiceBean; +import edu.harvard.iq.dataverse.api.auth.AuthRequired; import edu.harvard.iq.dataverse.search.SearchServiceBean; import edu.harvard.iq.dataverse.search.SolrQueryResponse; import edu.harvard.iq.dataverse.search.SolrSearchResult; @@ -38,6 +39,8 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; import edu.harvard.iq.dataverse.util.BundleUtil; import org.apache.commons.lang3.StringUtils; @@ -254,78 +257,49 @@ private String getJSONErrorString(String jsonMsg, String optionalLoggerMsg){ return jsonData.build().toString(); } - - /** - * @todo This should support the "X-Dataverse-key" header like the other - * APIs. - */ - @Path(retrieveDataPartialAPIPath) + @GET + @AuthRequired + @Path(retrieveDataPartialAPIPath) @Produces({"application/json"}) - public String retrieveMyDataAsJsonString(@QueryParam("dvobject_types") List dvobject_types, - @QueryParam("published_states") List published_states, - @QueryParam("selected_page") Integer selectedPage, - @QueryParam("mydata_search_term") String searchTerm, - @QueryParam("role_ids") List roleIds, - @QueryParam("userIdentifier") String userIdentifier, - @QueryParam("key") String apiToken) { //String myDataParams) { - //System.out.println("_YE_OLDE_QUERY_COUNTER_"); - //msgt("_YE_OLDE_QUERY_COUNTER_"); // for debug purposes - boolean DEBUG_MODE = false; + public String retrieveMyDataAsJsonString( + @Context ContainerRequestContext crc, + @QueryParam("dvobject_types") List dvobject_types, + @QueryParam("published_states") List published_states, + @QueryParam("selected_page") Integer selectedPage, + @QueryParam("mydata_search_term") String searchTerm, + @QueryParam("role_ids") List roleIds, + @QueryParam("userIdentifier") String userIdentifier) { boolean OTHER_USER = false; String localeCode = session.getLocaleCode(); String noMsgResultsFound = BundleUtil.getStringFromPropertyFile("dataretrieverAPI.noMsgResultsFound", "Bundle", new Locale(localeCode)); - - // For, superusers, the searchUser may differ from the authUser - // - AuthenticatedUser searchUser = null; - if (DEBUG_MODE==true){ // DEBUG: use userIdentifier - authUser = getUserFromIdentifier(userIdentifier); - if (authUser == null){ - return this.getJSONErrorString("Requires authentication", "retrieveMyDataAsJsonString. User not found! Shouldn't be using this anyway"); + if ((session.getUser() != null) && (session.getUser().isAuthenticated())) { + authUser = (AuthenticatedUser) session.getUser(); + } else { + try { + authUser = getRequestAuthenticatedUserOrDie(crc); + } catch (WrappedResponse e) { + return this.getJSONErrorString("Requires authentication. Please login.", "retrieveMyDataAsJsonString. User not found! Shouldn't be using this anyway"); } - } else if ((session.getUser() != null)&&(session.getUser().isAuthenticated())){ - authUser = (AuthenticatedUser)session.getUser(); - - // If person 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("No user found for: \"" + userIdentifier + "\"", null); - } - } - } else if (apiToken != null) { // Is this being accessed by an API Token? - - authUser = findUserByApiToken(apiToken); - if (authUser == null){ - return this.getJSONErrorString("Requires authentication. Please login.", "retrieveMyDataAsJsonString. User not found! Shouldn't be using this anyway"); - }else{ - // If person 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("No user found for: \"" + userIdentifier + "\"", null); - } - } + } + // 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("No user found for: \"" + userIdentifier + "\"", null); } - - } else{ - return this.getJSONErrorString("Requires authentication. Please login.", "retrieveMyDataAsJsonString. User not found! Shouldn't be using this anyway"); } - + roleList = dataverseRoleService.findAll(); rolePermissionHelper = new DataverseRolePermissionHelper(roleList); @@ -462,7 +436,7 @@ public String retrieveMyDataAsJsonString(@QueryParam("dvobject_types") List emptyRoleIdsList = new ArrayList<>(); + Response badApiTokenResponse = UtilIT.retrieveMyDataAsJsonString("bad-token", "dummy-user-identifier", emptyRoleIdsList); + badApiTokenResponse.then().assertThat().body("status", equalTo(ApiConstants.STATUS_ERROR)).body("message", equalTo(ApiKeyAuthMechanism.RESPONSE_MESSAGE_BAD_API_KEY)).statusCode(UNAUTHORIZED.getStatusCode()); + + // Call as superuser with invalid user identifier + Response createUserResponse = UtilIT.createRandomUser(); + Response makeSuperUserResponse = UtilIT.makeSuperUser(UtilIT.getUsernameFromResponse(createUserResponse)); + assertEquals(OK.getStatusCode(), makeSuperUserResponse.getStatusCode()); + String superUserApiToken = UtilIT.getApiTokenFromResponse(createUserResponse); + + String badUserIdentifier = "bad-identifier"; + Response invalidUserIdentifierResponse = UtilIT.retrieveMyDataAsJsonString(superUserApiToken, badUserIdentifier, emptyRoleIdsList); + assertEquals("{\"success\":false,\"error_message\":\"No user found for: \\\"" + badUserIdentifier + "\\\"\"}", invalidUserIdentifierResponse.prettyPrint()); + assertEquals(OK.getStatusCode(), invalidUserIdentifierResponse.getStatusCode()); + + // Call as superuser with valid user identifier + Response createSecondUserResponse = UtilIT.createRandomUser(); + String userIdentifier = UtilIT.getUsernameFromResponse(createSecondUserResponse); + Response validUserIdentifierResponse = UtilIT.retrieveMyDataAsJsonString(superUserApiToken, userIdentifier, emptyRoleIdsList); + assertEquals("{\"success\":false,\"error_message\":\"Sorry, you have no assigned roles.\"}", validUserIdentifierResponse.prettyPrint()); + assertEquals(OK.getStatusCode(), validUserIdentifierResponse.getStatusCode()); + } +} 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 dc9152859ee..25dc6ee60f0 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -21,6 +21,7 @@ import java.util.logging.Level; import edu.harvard.iq.dataverse.api.datadeposit.SwordConfigurationImpl; import com.jayway.restassured.path.xml.XmlPath; +import edu.harvard.iq.dataverse.mydata.MyDataFilterParams; import org.apache.commons.lang3.StringUtils; import org.junit.Test; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; @@ -3137,4 +3138,15 @@ static Response importDatasetDDIViaNativeApi(String apiToken, String dataverseAl return importDDI.post(postString); } + + static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifier, ArrayList roleIds) { + Response response = given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .contentType("application/json; charset=utf-8") + .queryParam("role_ids", roleIds) + .queryParam("dvobject_types", MyDataFilterParams.defaultDvObjectTypes) + .queryParam("published_states", MyDataFilterParams.defaultPublishedStates) + .get("/api/mydata/retrieve?userIdentifier=" + userIdentifier); + return response; + } } From 5f52321bcb76f4c5b113c5e47d639bda8cf9df0a Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 22 Feb 2023 13:38:32 +0000 Subject: [PATCH 30/30] Added: SignedUrlAuthMechanism IT --- .../api/SignedUrlAuthMechanismIT.java | 46 +++++++++++++++++++ .../edu/harvard/iq/dataverse/api/UtilIT.java | 15 ++++++ 2 files changed, 61 insertions(+) create mode 100644 src/test/java/edu/harvard/iq/dataverse/api/SignedUrlAuthMechanismIT.java diff --git a/src/test/java/edu/harvard/iq/dataverse/api/SignedUrlAuthMechanismIT.java b/src/test/java/edu/harvard/iq/dataverse/api/SignedUrlAuthMechanismIT.java new file mode 100644 index 00000000000..e30bf40082b --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/api/SignedUrlAuthMechanismIT.java @@ -0,0 +1,46 @@ +package edu.harvard.iq.dataverse.api; + +import com.jayway.restassured.RestAssured; +import com.jayway.restassured.response.Response; +import org.junit.BeforeClass; +import org.junit.jupiter.api.Test; + +import static com.jayway.restassured.RestAssured.get; +import static javax.ws.rs.core.Response.Status.OK; +import static javax.ws.rs.core.Response.Status.UNAUTHORIZED; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SignedUrlAuthMechanismIT { + + @BeforeClass + public static void setUp() { + RestAssured.baseURI = UtilIT.getRestAssuredBaseUri(); + } + + @Test + public void testSignedUrlAuthMechanism() { + // Test user setup + Response createUserResponse = UtilIT.createRandomUser(); + String username = UtilIT.getUsernameFromResponse(createUserResponse); + String apiToken = UtilIT.getApiTokenFromResponse(createUserResponse); + UtilIT.makeSuperUser(username); + + // Test dataset setup + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + String datasetPersistentId = UtilIT.getDatasetPersistentIdFromResponse(createDatasetResponse); + + // Valid Signed URL behavior + String apiPath = String.format("/api/v1/datasets/:persistentId/?persistentId=%s", datasetPersistentId); + Response createSignedUrlResponse = UtilIT.createSignedUrl(apiToken, apiPath, username); + String signedUrl = UtilIT.getSignedUrlFromResponse(createSignedUrlResponse); + Response signedUrlResponse = get(signedUrl); + assertEquals(OK.getStatusCode(), signedUrlResponse.getStatusCode()); + + // Invalid Signed URL behavior + String invalidSignedUrlPath = String.format("/api/v1/datasets/:persistentId/?persistentId=%s&until=2999-01-01T23:59:29.855&user=dataverseAdmin&method=GET&token=invalidToken", datasetPersistentId); + Response invalidSignedUrlResponse = get(invalidSignedUrlPath); + assertEquals(UNAUTHORIZED.getStatusCode(), invalidSignedUrlResponse.getStatusCode()); + } +} 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 25dc6ee60f0..4262b709f58 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3149,4 +3149,19 @@ static Response retrieveMyDataAsJsonString(String apiToken, String userIdentifie .get("/api/mydata/retrieve?userIdentifier=" + userIdentifier); return response; } + + static Response createSignedUrl(String apiToken, String apiPath, String username) { + Response response = given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .body(String.format("{\"url\":\"%s\",\"timeOut\":35,\"user\":\"%s\"}", getRestAssuredBaseUri() + apiPath, username)) + .contentType("application/json") + .post("/api/admin/requestSignedUrl"); + return response; + } + + static String getSignedUrlFromResponse(Response createSignedUrlResponse) { + JsonPath jsonPath = JsonPath.from(createSignedUrlResponse.body().asString()); + String signedUrl = jsonPath.getString("data.signedUrl"); + return signedUrl; + } }