diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index fa71a163608..d62e3976d85 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -481,6 +481,8 @@ The fully expanded example above (without the environment variables) looks like You should expect an HTTP 200 ("OK") response and JSON indicating the database ID and Persistent ID (PID such as DOI or Handle) that has been assigned to your newly created dataset. +.. note:: Only a Dataverse account with superuser permissions is allowed to include files when creating a dataset via this API. Adding files this way only adds their file metadata to the database, you will need to manually add the physical files to the file system. + Import a Dataset into a Dataverse ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 0b2e25a7f02..f93f5c7e171 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -572,6 +572,11 @@ public Response updateDraftVersion( String jsonBody, @PathParam("id") String id, incomingVersion.setDataset(ds); incomingVersion.setCreateTime(null); incomingVersion.setLastUpdateTime(null); + + if (!incomingVersion.getFileMetadatas().isEmpty()){ + return error( Response.Status.BAD_REQUEST, "You may not add files via this api."); + } + boolean updateDraft = ds.getLatestVersion().isDraft(); DatasetVersion managedVersion; 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 bd5f17dcd75..d060ca3105e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -224,6 +224,10 @@ public Response createDataset(String jsonBody, @PathParam("identifier") String p if (ds.getVersions().isEmpty()) { return badRequest("Please provide initial version in the dataset json"); } + + if (!ds.getFiles().isEmpty() && !u.isSuperuser()){ + return badRequest("Only a superuser may add files via this api"); + } // clean possible version metadata DatasetVersion version = ds.getVersions().get(0); @@ -253,6 +257,9 @@ public Response createDataset(String jsonBody, @PathParam("identifier") String p public Response importDataset(String jsonBody, @PathParam("identifier") String parentIdtf, @QueryParam("pid") String pidParam, @QueryParam("release") String releaseParam) { try { User u = findUserOrDie(); + if (!u.isSuperuser()) { + return error(Status.FORBIDDEN, "Not a superuser"); + } Dataverse owner = findDataverseOrDie(parentIdtf); Dataset ds = parseDataset(jsonBody); ds.setOwner(owner); @@ -382,7 +389,7 @@ public Response importDatasetDdi(String xml, @PathParam("identifier") String par return ex.getResponse(); } } - + private Dataset parseDataset(String datasetJson) throws WrappedResponse { try (StringReader rdr = new StringReader(datasetJson)) { return jsonParser().parseDataset(Json.createReader(rdr).readObject()); diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java index 40a1a31f12a..d3a2017e33e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java @@ -395,7 +395,6 @@ private List parseFieldsFromArray(JsonArray fieldsArray, Boolean t public List parseFiles(JsonArray metadatasJson, DatasetVersion dsv) throws JsonParseException { List fileMetadatas = new LinkedList<>(); - if (metadatasJson != null) { for (JsonObject filemetadataJson : metadatasJson.getValuesAs(JsonObject.class)) { String label = filemetadataJson.getString("label"); @@ -413,13 +412,14 @@ public List parseFiles(JsonArray metadatasJson, DatasetVersion dsv dataFile.getFileMetadatas().add(fileMetadata); dataFile.setOwner(dsv.getDataset()); fileMetadata.setDataFile(dataFile); - if (dsv.getDataset().getFiles() == null) { - dsv.getDataset().setFiles(new ArrayList<>()); + if (dsv.getDataset() != null) { + if (dsv.getDataset().getFiles() == null) { + dsv.getDataset().setFiles(new ArrayList<>()); + } + dsv.getDataset().getFiles().add(dataFile); } - dsv.getDataset().getFiles().add(dataFile); } - fileMetadatas.add(fileMetadata); fileMetadata.setCategories(getCategories(filemetadataJson, dsv.getDataset())); } 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 713fb770cb0..65b6d9c5ba9 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java @@ -41,10 +41,17 @@ import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonObjectBuilder; +import static javax.ws.rs.core.Response.Status.BAD_REQUEST; +import static javax.ws.rs.core.Response.Status.CREATED; +import static javax.ws.rs.core.Response.Status.FORBIDDEN; +import static javax.ws.rs.core.Response.Status.NO_CONTENT; +import static javax.ws.rs.core.Response.Status.OK; +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.CoreMatchers.nullValue; import org.junit.AfterClass; +import org.junit.Assert; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.matchers.JUnitMatchers.containsString; @@ -1869,4 +1876,66 @@ public void testUpdatePIDMetadataAPI() { } + @Test + public void testUpdateDatasetVersionWithFiles() throws InterruptedException { + Response createCurator = UtilIT.createRandomUser(); + createCurator.prettyPrint(); + createCurator.then().assertThat() + .statusCode(OK.getStatusCode()); + String curatorUsername = UtilIT.getUsernameFromResponse(createCurator); + String curatorApiToken = UtilIT.getApiTokenFromResponse(createCurator); + + Response createDataverseResponse = UtilIT.createRandomDataverse(curatorApiToken); + createDataverseResponse.prettyPrint(); + createDataverseResponse.then().assertThat() + .statusCode(CREATED.getStatusCode()); + + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + Response createAuthor = UtilIT.createRandomUser(); + createAuthor.prettyPrint(); + createAuthor.then().assertThat() + .statusCode(OK.getStatusCode()); + String authorUsername = UtilIT.getUsernameFromResponse(createAuthor); + String authorApiToken = UtilIT.getApiTokenFromResponse(createAuthor); + + + Response grantAuthorAddDataset = UtilIT.grantRoleOnDataverse(dataverseAlias, DataverseRole.DS_CONTRIBUTOR.toString(), "@" + authorUsername, curatorApiToken); + grantAuthorAddDataset.prettyPrint(); + grantAuthorAddDataset.then().assertThat() + .body("data.assignee", equalTo("@" + authorUsername)) + .body("data._roleAlias", equalTo("dsContributor")) + .statusCode(OK.getStatusCode()); + + Response createDataset = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, authorApiToken); + createDataset.prettyPrint(); + createDataset.then().assertThat() + .statusCode(CREATED.getStatusCode()); + + Integer datasetId = UtilIT.getDatasetIdFromResponse(createDataset); + + // FIXME: have the initial create return the DOI or Handle to obviate the need for this call. + Response getDatasetJsonBeforePublishing = UtilIT.nativeGet(datasetId, authorApiToken); + getDatasetJsonBeforePublishing.prettyPrint(); + String protocol = JsonPath.from(getDatasetJsonBeforePublishing.getBody().asString()).getString("data.protocol"); + String authority = JsonPath.from(getDatasetJsonBeforePublishing.getBody().asString()).getString("data.authority"); + String identifier = JsonPath.from(getDatasetJsonBeforePublishing.getBody().asString()).getString("data.identifier"); + + String datasetPersistentId = protocol + ":" + authority + "/" + identifier; + System.out.println("datasetPersistentId: " + datasetPersistentId); + + String pathToJsonFile = "src/test/resources/json/update-dataset-version-with-files.json"; + + Response updateMetadataAddFilesViaNative = UtilIT.updateDatasetMetadataViaNative(datasetPersistentId, pathToJsonFile, authorApiToken); + updateMetadataAddFilesViaNative.prettyPrint(); + updateMetadataAddFilesViaNative.then().assertThat() + .statusCode(BAD_REQUEST.getStatusCode()); + + // These println's are here in case you want to log into the GUI to see what notifications look like. + System.out.println("Curator username/password: " + curatorUsername); + System.out.println("Author username/password: " + authorUsername); + + } + + } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java index 89b4eae4592..c069217f4df 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -2,6 +2,7 @@ import com.jayway.restassured.RestAssured; import static com.jayway.restassured.RestAssured.given; +import com.jayway.restassured.path.json.JsonPath; import static com.jayway.restassured.path.json.JsonPath.with; import com.jayway.restassured.response.Response; import edu.harvard.iq.dataverse.Dataverse; @@ -18,8 +19,11 @@ import static javax.ws.rs.core.Response.Status.CREATED; import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; import javax.ws.rs.core.Response.Status; +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.UNAUTHORIZED; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; import org.junit.BeforeClass; import org.junit.Test; import static org.hamcrest.CoreMatchers.equalTo; @@ -453,4 +457,44 @@ public void testUpdateDefaultContributorRole() { } + @Test + public void testDataFileAPIPermissions() { + + Response createUser = UtilIT.createRandomUser(); + createUser.prettyPrint(); + String username = UtilIT.getUsernameFromResponse(createUser); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + createDataverseResponse.prettyPrint(); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + String pathToJsonFile = "src/test/resources/json/complete-dataset-with-files.json"; + Response createDatasetResponse = UtilIT.createDatasetViaNativeApi(dataverseAlias, pathToJsonFile, apiToken); + + //should fail if non-super user and attempting to + //create a dataset with files + createDatasetResponse.prettyPrint(); + createDatasetResponse.then().assertThat() + .statusCode(BAD_REQUEST.getStatusCode()); + + //should be ok to create a dataset without files... + pathToJsonFile = "scripts/api/data/dataset-create-new.json"; + createDatasetResponse = UtilIT.createDatasetViaNativeApi(dataverseAlias, pathToJsonFile, apiToken); + + createDatasetResponse.prettyPrint(); + createDatasetResponse.then().assertThat() + .statusCode(CREATED.getStatusCode()); + Integer datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse); + + //As non-super user should be able to add a real file + String pathToFile1 = "src/main/webapp/resources/images/cc0.png"; + Response authorAttemptsToAddFileViaNative = UtilIT.uploadFileViaNative(datasetId.toString(), pathToFile1, apiToken); + + authorAttemptsToAddFileViaNative.prettyPrint(); + authorAttemptsToAddFileViaNative.then().assertThat() + .statusCode(OK.getStatusCode()); + + } + } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FileMetadataIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FileMetadataIT.java index 321c6c45676..46905daa887 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/FileMetadataIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/FileMetadataIT.java @@ -134,6 +134,8 @@ public void testJsonParserWithDirectoryLabels() { // create dataset and set id System.out.println("Creating dataset...."); + //SEK 4/14/2020 need to be super user to add a dataset with files + UtilIT.makeSuperUser(testName); dsId = given() .header(keyString, token) .body(IOUtils.toString(classLoader.getResourceAsStream("json/complete-dataset-with-files.json"))) 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 4507c18eb89..bd3914a92ba 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -356,7 +356,8 @@ private static String getDatasetJson() { static Response createDatasetViaNativeApi(String dataverseAlias, String pathToJsonFile, String apiToken) { String jsonIn = getDatasetJson(pathToJsonFile); - Response createDatasetResponse = given() + + Response createDatasetResponse = given() .header(API_TOKEN_HTTP_HEADER, apiToken) .body(jsonIn) .contentType("application/json") @@ -372,6 +373,9 @@ private static String getDatasetJson(String pathToJsonFile) { } catch (IOException ex) { Logger.getLogger(UtilIT.class.getName()).log(Level.SEVERE, null, ex); return null; + } catch (Exception e){ + Logger.getLogger(UtilIT.class.getName()).log(Level.SEVERE, null, e); + return null; } } diff --git a/src/test/resources/json/update-dataset-version-with-files.json b/src/test/resources/json/update-dataset-version-with-files.json new file mode 100644 index 00000000000..5701008a14d --- /dev/null +++ b/src/test/resources/json/update-dataset-version-with-files.json @@ -0,0 +1,129 @@ +{ + "metadataBlocks": { + "citation": { + "displayName": "Citation Metadata", + "fields": [ + { + "typeName": "title", + "multiple": false, + "typeClass": "primitive", + "value": "Update Version with Files" + }, + { + "typeName": "author", + "multiple": true, + "typeClass": "compound", + "value": [ + { + "authorName": { + "typeName": "authorName", + "multiple": false, + "typeClass": "primitive", + "value": "Spruce, Sabrina" + } + } + ] + }, + { + "typeName": "datasetContact", + "multiple": true, + "typeClass": "compound", + "value": [ + { + "datasetContactName": { + "typeName": "datasetContactName", + "multiple": false, + "typeClass": "primitive", + "value": "Spruce, Sabrina" + }, + "datasetContactEmail": { + "typeName": "datasetContactEmail", + "multiple": false, + "typeClass": "primitive", + "value": "spruce@mailinator.com" + } + } + ] + }, + { + "typeName": "dsDescription", + "multiple": true, + "typeClass": "compound", + "value": [ + { + "dsDescriptionValue": { + "typeName": "dsDescriptionValue", + "multiple": false, + "typeClass": "primitive", + "value": "test" + } + } + ] + }, + { + "typeName": "subject", + "multiple": true, + "typeClass": "controlledVocabulary", + "value": [ + "Other" + ] + }, + { + "typeName": "depositor", + "multiple": false, + "typeClass": "primitive", + "value": "Spruce, Sabrina" + }, + { + "typeName": "dateOfDeposit", + "multiple": false, + "typeClass": "primitive", + "value": "2017-04-19" + } + ] + } + }, + "files": [ + { + "description": "", + "label": "yn152_4_001.img", + "directoryLabel": "data/subdir1", + "version": 1, + "datasetVersionId": 2, + "dataFile": { + "id": 424, + "filename": "yn152_4_001.img", + "contentType": "application/octet-stream", + "storageIdentifier": "data/subdir/yn152_4_001.img", + "originalFormatLabel": "UNKNOWN", + "checksum": { + "type": "MD5", + "value": "fc4ac06da6fd84785ed1b79cab9b714a73621ac7" + }, + "description": "" + } + }, + { + "description": "", + "label": "yn152_4_002.img", + "directoryLabel": "data/subdir2", + "version": 1, + "datasetVersionId": 2, + "categories": ["Data"], + "dataFile": { + "id": 425, + "filename": "yn152_4_002.img", + "contentType": "application/octet-stream", + "storageIdentifier": "data/subdir/yn152_4_002.img", + "originalFormatLabel": "UNKNOWN", + "checksum": { + "type": "MD5", + "value": "756c1963e34eb6dd8bb5b48333c4f20ce64a0307" + }, + "description": "" + } + } + ] +} + +