Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 92 additions & 11 deletions src/main/java/edu/harvard/iq/dataverse/api/Access.java

Large diffs are not rendered by default.

444 changes: 380 additions & 64 deletions src/main/java/edu/harvard/iq/dataverse/api/Admin.java

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/ApiConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@

import jakarta.ws.rs.ApplicationPath;

import org.eclipse.microprofile.openapi.annotations.enums.SecuritySchemeIn;
import org.eclipse.microprofile.openapi.annotations.enums.SecuritySchemeType;
import org.eclipse.microprofile.openapi.annotations.security.SecurityScheme;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("api/v1")
@SecurityScheme(
securitySchemeName = "DataverseApiKey",
type = SecuritySchemeType.APIKEY,
in = SecuritySchemeIn.HEADER,
apiKeyName = "X-Dataverse-key",
description = "Dataverse API token."
)
public class ApiConfiguration extends ResourceConfig {

public ApiConfiguration() {
Expand Down
41 changes: 38 additions & 3 deletions src/main/java/edu/harvard/iq/dataverse/api/BatchImport.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,16 @@
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

@Stateless
@Path("batch")
@SecurityRequirement(name = "DataverseApiKey")
@Tag(name = "Admin", description = "Administrative Dataverse operations.")
public class BatchImport extends AbstractApiBean {

@EJB
Expand All @@ -48,7 +55,17 @@ public class BatchImport extends AbstractApiBean {
@GET
@AuthRequired
@Path("harvest")
public Response harvest(@Context ContainerRequestContext crc, @QueryParam("path") String fileDir, @QueryParam("dv") String parentIdtf, @QueryParam("createDV") Boolean createDV, @QueryParam("key") String apiKey) throws IOException {
@Operation(summary = "Starts a harvest batch import",
description = "Starts a background harvest import from a server-side file or directory into the specified dataverse.")
public Response harvest(@Context ContainerRequestContext crc,
@Parameter(description = "Server-side file or directory path to import.", required = true)
@QueryParam("path") String fileDir,
@Parameter(description = "Target dataverse id or alias; defaults to root when omitted.")
@QueryParam("dv") String parentIdtf,
@Parameter(description = "Create the target dataverse when it does not exist.")
@QueryParam("createDV") Boolean createDV,
@Parameter(hidden = true)
@QueryParam("key") String apiKey) throws IOException {
try {
return startBatchJob(getRequestAuthenticatedUserOrDie(crc), fileDir, parentIdtf, apiKey, ImportType.HARVEST, createDV);
} catch (WrappedResponse wr) {
Expand All @@ -67,7 +84,15 @@ public Response harvest(@Context ContainerRequestContext crc, @QueryParam("path"
@POST
@AuthRequired
@Path("import")
public Response postImport(@Context ContainerRequestContext crc, String body, @QueryParam("dv") String parentIdtf, @QueryParam("key") String apiKey) {
@Operation(summary = "Imports a dataset from DDI XML",
description = "Imports a new dataset into the specified dataverse from DDI XML supplied in the request body.")
public Response postImport(@Context ContainerRequestContext crc,
@RequestBody(description = "DDI XML dataset metadata to import as a new dataset.")
String body,
@Parameter(description = "Target dataverse id or alias; defaults to root when omitted.")
@QueryParam("dv") String parentIdtf,
@Parameter(hidden = true)
@QueryParam("key") String apiKey) {

DataverseRequest dataverseRequest;
try {
Expand Down Expand Up @@ -105,7 +130,17 @@ public Response postImport(@Context ContainerRequestContext crc, String body, @Q
@GET
@AuthRequired
@Path("import")
public Response getImport(@Context ContainerRequestContext crc, @QueryParam("path") String fileDir, @QueryParam("dv") String parentIdtf, @QueryParam("createDV") Boolean createDV, @QueryParam("key") String apiKey) {
@Operation(summary = "Starts a dataset batch import",
description = "Starts a background import of one or more datasets from a server-side file or directory into the specified dataverse.")
public Response getImport(@Context ContainerRequestContext crc,
@Parameter(description = "Server-side file or directory path to import.", required = true)
@QueryParam("path") String fileDir,
@Parameter(description = "Target dataverse id or alias; defaults to root when omitted.")
@QueryParam("dv") String parentIdtf,
@Parameter(description = "Create the target dataverse when it does not exist.")
@QueryParam("createDV") Boolean createDV,
@Parameter(hidden = true)
@QueryParam("key") String apiKey) {
try {
return startBatchJob(getRequestAuthenticatedUserOrDie(crc), fileDir, parentIdtf, apiKey, ImportType.NEW, createDV);
} catch (WrappedResponse wr) {
Expand Down
47 changes: 43 additions & 4 deletions src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@
import jakarta.ws.rs.core.Response.Status;
import java.util.Date;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

/**
* REST API bean for managing {@link BuiltinUser}s.
*
* @author michael
*/
@Path("builtin-users")
@Tag(name = "Users", description = "User account and authenticated user operations.")
public class BuiltinUsers extends AbstractApiBean {

private static final Logger logger = Logger.getLogger(BuiltinUsers.class.getName());
Expand All @@ -45,7 +50,13 @@ public class BuiltinUsers extends AbstractApiBean {

@GET
@Path("{username}/api-token")
public Response getApiToken( @PathParam("username") String username, @QueryParam("password") String password ) {
@Operation(summary = "Returns a builtin user's API token",
description = "Returns the API token for a builtin user when token lookup is enabled and the supplied password is valid.")
public Response getApiToken(
@Parameter(description = "Builtin username whose API token is requested.", required = true)
@PathParam("username") String username,
@Parameter(description = "Builtin account password used to verify token lookup.")
@QueryParam("password") String password) {
boolean disabled = true;
boolean lookupAllowed = settingsSvc.isTrueForKey(SettingsServiceBean.Key.AllowApiTokenLookupViaApi, false);
if (lookupAllowed) {
Expand Down Expand Up @@ -80,7 +91,17 @@ public Response getApiToken( @PathParam("username") String username, @QueryParam
//and use the values to create BuiltinUser/AuthenticatedUser.
//--MAD 4.9.3
@POST
public Response save(BuiltinUser user, @QueryParam("password") String password, @QueryParam("key") String key, @QueryParam("sendEmailNotification") Boolean sendEmailNotification) {
@Operation(summary = "Creates a builtin user",
description = "Creates a builtin user, matching authenticated user, and API token when the builtin-users management key is valid.")
public Response save(
@RequestBody(description = "Builtin user account data used to create the builtin and authenticated user records.")
BuiltinUser user,
@Parameter(description = "Password assigned to the builtin user.")
@QueryParam("password") String password,
@Parameter(description = "Builtin-users management key required to create the account.")
@QueryParam("key") String key,
@Parameter(description = "Send a create-account email notification to the new user.")
@QueryParam("sendEmailNotification") Boolean sendEmailNotification) {
if( sendEmailNotification == null )
sendEmailNotification = true;

Expand All @@ -100,7 +121,15 @@ public Response save(BuiltinUser user, @QueryParam("password") String password,
*/
@POST
@Path("{password}/{key}")
public Response create(BuiltinUser user, @PathParam("password") String password, @PathParam("key") String key) {
@Operation(summary = "Creates a builtin user with path credentials",
description = "Creates a builtin user, matching authenticated user, and API token using password and management key path values.")
public Response create(
@RequestBody(description = "Builtin user account data used to create the builtin and authenticated user records.")
BuiltinUser user,
@Parameter(description = "Password assigned to the builtin user.", required = true)
@PathParam("password") String password,
@Parameter(description = "Builtin-users management key required to create the account.", required = true)
@PathParam("key") String key) {
return internalSave(user, password, key);
}

Expand All @@ -117,7 +146,17 @@ public Response create(BuiltinUser user, @PathParam("password") String password,
*/
@POST
@Path("{password}/{key}/{sendEmailNotification}")
public Response createWithNotification(BuiltinUser user, @PathParam("password") String password, @PathParam("key") String key, @PathParam("sendEmailNotification") Boolean sendEmailNotification) {
@Operation(summary = "Creates a builtin user with notification control",
description = "Creates a builtin user, matching authenticated user, and API token while controlling whether a create-account notification is sent.")
public Response createWithNotification(
@RequestBody(description = "Builtin user account data used to create the builtin and authenticated user records.")
BuiltinUser user,
@Parameter(description = "Password assigned to the builtin user.", required = true)
@PathParam("password") String password,
@Parameter(description = "Builtin-users management key required to create the account.", required = true)
@PathParam("key") String key,
@Parameter(description = "Send a create-account email notification to the new user.", required = true)
@PathParam("sendEmailNotification") Boolean sendEmailNotification) {
return internalSave(user, password, key, sendEmailNotification);
}

Expand Down
15 changes: 13 additions & 2 deletions src/main/java/edu/harvard/iq/dataverse/api/DataTagsAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

/**
*
* @author Naomi
*/
@Stateless
@Path("datatags")
@Tag(name = "Datasets", description = "Dataset metadata, versions, files, and publishing operations.")
public class DataTagsAPI extends AbstractApiBean {

private static final String TAGGING_SERVER_ENDPOINT = "http://datatags.org/api/1/interviewLink";
Expand Down Expand Up @@ -76,7 +81,13 @@ public String getCallbackURL() {

@POST
@Path("receiveTags/{uniqueCacheId}")
public Response receiveTags(JsonObject tags, @PathParam("uniqueCacheId") String uniqueCacheId) {
@Operation(summary = "Receives DataTags results",
description = "Stores returned DataTags JSON in the callback cache and returns the Dataverse redirect URL.")
public Response receiveTags(
@RequestBody(description = "DataTags result JSON returned by the external tagging interview.")
JsonObject tags,
@Parameter(description = "Callback cache identifier associated with the DataTags interview.", required = true)
@PathParam("uniqueCacheId") String uniqueCacheId) {

// store json tags in the DataTagsContainer holding the dataset name
container.setTag(tags);
Expand All @@ -86,4 +97,4 @@ public Response receiveTags(JsonObject tags, @PathParam("uniqueCacheId") String
return ok( USER_REDIRECT_URL );
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

@Path("admin/datasetfield")
@Tag(name = "Dataset Fields", description = "Dataset field type, controlled vocabulary, and metadata block loading operations.")
public class DatasetFieldServiceApi extends AbstractApiBean {

@EJB
Expand All @@ -72,6 +77,8 @@
private static final Logger logger = Logger.getLogger(DatasetFieldServiceApi.class.getName());

@GET
@Operation(summary = "Lists dataset field groupings",
description = "Returns dataset field type names grouped by parent relationship, multiplicity, and required status.")
public Response getAll() {
try {
List<String> listOfIsHasParentsTrue = new ArrayList<>();
Expand Down Expand Up @@ -118,7 +125,11 @@

@GET
@Path("{name}")
public Response getByName(@PathParam("name") String name) {
@Operation(summary = "Returns a dataset field type",
description = "Returns a dataset field type with metadata block, Solr field names, parent relationship, multiplicity, required state, URI, and controlled vocabulary values.")
public Response getByName(
@Parameter(description = "Dataset field type name to return.", required = true)
@PathParam("name") String name) {
try {
DatasetFieldType dsf = datasetFieldService.findByName(name);
Long id = dsf.getId();
Expand Down Expand Up @@ -190,6 +201,8 @@
*/
@GET
@Path("controlledVocabulary/subject")
@Operation(summary = "Lists subject vocabulary values",
description = "Returns the configured controlled vocabulary display values for the subject dataset field.")
public Response showControlledVocabularyForSubject() {
DatasetFieldType subjectDatasetField = datasetFieldService.findByName(DatasetFieldConstant.subject);
JsonArrayBuilder possibleSubjects = Json.createArrayBuilder();
Expand All @@ -206,6 +219,8 @@
// TODO consider replacing with a @Startup method on the datasetFieldServiceBean
@GET
@Path("loadNAControlledVocabularyValue")
@Operation(summary = "Creates the N/A controlled vocabulary value",
description = "Creates the global N/A controlled vocabulary value when it is missing and reports whether it was created or already existed.")
public Response loadNAControlledVocabularyValue() {
// the find will throw a NoResultException if no values are in db
// datasetFieldService.findNAControlledVocabularyValue();
Expand All @@ -226,11 +241,15 @@
public enum HeaderType {
METADATABLOCK, DATASETFIELD, CONTROLLEDVOCABULARY
}

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
@POST
@Consumes("text/tab-separated-values")
@Path("load")
public Response loadDatasetFields(File file) {
@Operation(summary = "Loads dataset field definitions",
description = "Reads tab-separated metadata block, dataset field, and controlled vocabulary definitions from the uploaded file path and creates or updates those definitions.")
public Response loadDatasetFields(
@RequestBody(description = "Tab-separated definitions for metadata blocks, dataset fields, and controlled vocabulary values.")
File file) {
ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.Admin, "loadDatasetFields");
alr.setInfo( file.getName() );
BufferedReader br = null;
Expand Down Expand Up @@ -489,7 +508,11 @@
@POST
@Consumes("application/zip")
@Path("loadpropertyfiles")
public Response loadLanguagePropertyFile(File inputFile) {
@Operation(summary = "Loads language property files",
description = "Extracts uploaded language property files into the configured Dataverse language directory while rejecting ZIP entries outside that directory.")
public Response loadLanguagePropertyFile(
@RequestBody(description = "ZIP archive containing language property files to extract.")
File inputFile) {
try (ZipFile file = new ZipFile(inputFile)) {
//Get file entries
Enumeration<? extends ZipEntry> entries = file.entries();
Expand Down Expand Up @@ -551,7 +574,13 @@
*/
@POST
@Path("/setDisplayOnCreate")
public Response setDisplayOnCreate(@QueryParam("datasetFieldType") String datasetFieldTypeIn, @QueryParam("setDisplayOnCreate") Boolean setDisplayOnCreateIn) {
@Operation(summary = "Sets dataset field create-page display",
description = "Updates whether the specified dataset field type is displayed on the dataset creation page.")
public Response setDisplayOnCreate(
@Parameter(description = "Dataset field type name to update.", required = true)
@QueryParam("datasetFieldType") String datasetFieldTypeIn,
@Parameter(description = "New display-on-create setting for the dataset field type.", required = true)
@QueryParam("setDisplayOnCreate") Boolean setDisplayOnCreateIn) {
DatasetFieldType dft = datasetFieldService.findByName(datasetFieldTypeIn);
if (dft == null) {
return error(Status.NOT_FOUND, "Cound not find a DatasetFieldType by looking up " + datasetFieldTypeIn);
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/DatasetFields.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import jakarta.ws.rs.core.Response;

import java.util.List;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

import static edu.harvard.iq.dataverse.util.json.JsonPrinter.jsonDatasetFieldTypes;

Expand All @@ -15,13 +17,16 @@
*/
@Path("datasetfields")
@Produces("application/json")
@Tag(name = "Dataset Fields", description = "Dataset field type, controlled vocabulary, and metadata block loading operations.")
public class DatasetFields extends AbstractApiBean {

@EJB
DatasetFieldServiceBean datasetFieldService;

@GET
@Path("facetables")
@Operation(summary = "Lists facetable dataset fields",
description = "Lists all facetable dataset fields defined in the installation.")
public Response listAllFacetableDatasetFields() {
List<DatasetFieldType> datasetFieldTypes = datasetFieldService.findAllFacetableFieldTypes();
return ok(jsonDatasetFieldTypes(datasetFieldTypes));
Expand Down
Loading
Loading