DURACOM-136 allow script execution by user other than admins

This commit is contained in:
Andrea Bollini
2023-04-21 06:54:13 +02:00
committed by Andrea Bollini
parent 74e3d10326
commit 7971887b9a
37 changed files with 659 additions and 319 deletions

View File

@@ -7,33 +7,16 @@
*/
package org.dspace.administer;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link ProcessCleaner} script.
*/
public class ProcessCleanerConfiguration<T extends ProcessCleaner> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (options == null) {

View File

@@ -7,33 +7,16 @@
*/
package org.dspace.app.bulkedit;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link MetadataDeletion} script.
*/
public class MetadataDeletionScriptConfiguration<T extends MetadataDeletion> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (options == null) {

View File

@@ -7,22 +7,14 @@
*/
package org.dspace.app.bulkedit;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link MetadataExport} script
*/
public class MetadataExportScriptConfiguration<T extends MetadataExport> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
@@ -39,15 +31,6 @@ public class MetadataExportScriptConfiguration<T extends MetadataExport> extends
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (options == null) {

View File

@@ -9,7 +9,6 @@
package org.dspace.app.bulkedit;
import org.apache.commons.cli.Options;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
/**
@@ -29,11 +28,6 @@ public class MetadataExportSearchScriptConfiguration<T extends MetadataExportSea
this.dspaceRunnableclass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(Context context) {
return true;
}
@Override
public Options getOptions() {
if (options == null) {

View File

@@ -8,22 +8,15 @@
package org.dspace.app.bulkedit;
import java.io.InputStream;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link MetadataImport} script
*/
public class MetadataImportScriptConfiguration<T extends MetadataImport> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
@@ -40,15 +33,6 @@ public class MetadataImportScriptConfiguration<T extends MetadataImport> extends
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (options == null) {

View File

@@ -7,18 +7,11 @@
*/
package org.dspace.app.harvest;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
public class HarvestScriptConfiguration<T extends Harvest> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@@ -32,13 +25,6 @@ public class HarvestScriptConfiguration<T extends Harvest> extends ScriptConfigu
this.dspaceRunnableClass = dspaceRunnableClass;
}
public boolean isAllowedToExecute(final Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
public Options getOptions() {
Options options = new Options();

View File

@@ -7,14 +7,9 @@
*/
package org.dspace.app.itemexport;
import java.sql.SQLException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link ItemExport} script
@@ -23,9 +18,6 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public class ItemExportScriptConfiguration<T extends ItemExport> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
@@ -38,15 +30,6 @@ public class ItemExportScriptConfiguration<T extends ItemExport> extends ScriptC
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(final Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
Options options = new Options();

View File

@@ -8,14 +8,10 @@
package org.dspace.app.itemimport;
import java.io.InputStream;
import java.sql.SQLException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link ItemImport} script
@@ -24,9 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public class ItemImportScriptConfiguration<T extends ItemImport> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
@@ -39,15 +32,6 @@ public class ItemImportScriptConfiguration<T extends ItemImport> extends ScriptC
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(final Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
Options options = new Options();

View File

@@ -7,25 +7,16 @@
*/
package org.dspace.app.mediafilter;
import java.sql.SQLException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
public class MediaFilterScriptConfiguration<T extends MediaFilterScript> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
private static final String MEDIA_FILTER_PLUGINS_KEY = "filter.plugins";
@Override
public Class<T> getDspaceRunnableClass() {
return dspaceRunnableClass;
@@ -36,16 +27,6 @@ public class MediaFilterScriptConfiguration<T extends MediaFilterScript> extends
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(final Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
Options options = new Options();

View File

@@ -8,7 +8,6 @@
package org.dspace.app.solrdatabaseresync;
import org.apache.commons.cli.Options;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
/**
@@ -27,11 +26,6 @@ public class SolrDatabaseResyncCliScriptConfiguration extends ScriptConfiguratio
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(Context context) {
return true;
}
@Override
public Options getOptions() {
if (options == null) {

View File

@@ -43,6 +43,7 @@ import org.dspace.discovery.SearchService;
import org.dspace.discovery.SearchServiceException;
import org.dspace.discovery.indexobject.IndexableCollection;
import org.dspace.discovery.indexobject.IndexableCommunity;
import org.dspace.discovery.indexobject.IndexableItem;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
@@ -809,6 +810,19 @@ public class AuthorizeServiceImpl implements AuthorizeService {
return performCheck(context, "search.resourcetype:" + IndexableCollection.TYPE);
}
/**
* Checks that the context's current user is an item admin in the site by querying the solr database.
*
* @param context context with the current user
* @return true if the current user is an item admin in the site
* false when this is not the case, or an exception occurred
* @throws java.sql.SQLException passed through.
*/
@Override
public boolean isItemAdmin(Context context) throws SQLException {
return performCheck(context, "search.resourcetype:" + IndexableItem.TYPE);
}
/**
* Checks that the context's current user is a community or collection admin in the site.
*

View File

@@ -532,6 +532,15 @@ public interface AuthorizeService {
*/
boolean isCollectionAdmin(Context context) throws SQLException;
/**
* Checks that the context's current user is an item admin in the site by querying the solr database.
*
* @param context context with the current user
* @return true if the current user is an item admin in the site
* false when this is not the case, or an exception occurred
*/
boolean isItemAdmin(Context context) throws SQLException;
/**
* Checks that the context's current user is a community or collection admin in the site.
*

View File

@@ -14,6 +14,7 @@ import java.util.List;
import org.dspace.content.ProcessStatus;
import org.dspace.core.Context;
import org.dspace.core.GenericDAO;
import org.dspace.eperson.EPerson;
import org.dspace.scripts.Process;
import org.dspace.scripts.ProcessQueryParameterContainer;
@@ -97,4 +98,26 @@ public interface ProcessDAO extends GenericDAO<Process> {
List<Process> findByStatusAndCreationTimeOlderThan(Context context, List<ProcessStatus> statuses, Date date)
throws SQLException;
/**
* Returns a list of all Process objects in the database by the given user.
*
* @param context The relevant DSpace context
* @param user The user to search for
* @param limit The limit for the amount of Processes returned
* @param offset The offset for the Processes to be returned
* @return The list of all Process objects in the Database
* @throws SQLException If something goes wrong
*/
List<Process> findByUser(Context context, EPerson user, int limit, int offset) throws SQLException;
/**
* Count all the processes which is related to the given user.
*
* @param context The relevant DSpace context
* @param user The user to search for
* @return The number of results matching the query
* @throws SQLException If something goes wrong
*/
int countByUser(Context context, EPerson user) throws SQLException;
}

View File

@@ -24,6 +24,7 @@ import org.dspace.content.ProcessStatus;
import org.dspace.content.dao.ProcessDAO;
import org.dspace.core.AbstractHibernateDAO;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.scripts.Process;
import org.dspace.scripts.ProcessQueryParameterContainer;
import org.dspace.scripts.Process_;
@@ -168,6 +169,33 @@ public class ProcessDAOImpl extends AbstractHibernateDAO<Process> implements Pro
return list(context, criteriaQuery, false, Process.class, -1, -1);
}
@Override
public List<Process> findByUser(Context context, EPerson user, int limit, int offset) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery<Process> criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class);
Root<Process> processRoot = criteriaQuery.from(Process.class);
criteriaQuery.select(processRoot);
criteriaQuery.where(criteriaBuilder.equal(processRoot.get(Process_.E_PERSON), user));
List<javax.persistence.criteria.Order> orderList = new LinkedList<>();
orderList.add(criteriaBuilder.desc(processRoot.get(Process_.PROCESS_ID)));
criteriaQuery.orderBy(orderList);
return list(context, criteriaQuery, false, Process.class, limit, offset);
}
@Override
public int countByUser(Context context, EPerson user) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery<Process> criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class);
Root<Process> processRoot = criteriaQuery.from(Process.class);
criteriaQuery.select(processRoot);
criteriaQuery.where(criteriaBuilder.equal(processRoot.get(Process_.E_PERSON), user));
return count(context, criteriaQuery, criteriaBuilder, processRoot);
}
}

View File

@@ -8,12 +8,15 @@
package org.dspace.curate;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.handle.factory.HandleServiceFactory;
import org.dspace.handle.service.HandleService;
import org.dspace.scripts.DSpaceCommandLineParameter;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link Curation} script
@@ -22,9 +25,6 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public class CurationScriptConfiguration<T extends Curation> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
@@ -38,16 +38,37 @@ public class CurationScriptConfiguration<T extends Curation> extends ScriptConfi
}
/**
* Only admin can run Curation script via the scripts and processes endpoints.
* Only repository admins or admins of the target object can run Curation script via the scripts
* and processes endpoints.
*
* @param context The relevant DSpace context
* @return True if currentUser is admin, otherwise false
* @param commandLineParameters the parameters that will be used to start the process if known,
* <code>null</code> otherwise
* @return true if the currentUser is allowed to run the script with the specified parameters or
* at least in some case if the parameters are not yet known
*/
@Override
public boolean isAllowedToExecute(Context context) {
public boolean isAllowedToExecute(Context context, List<DSpaceCommandLineParameter> commandLineParameters) {
try {
if (commandLineParameters == null) {
return authorizeService.isAdmin(context) || authorizeService.isComColAdmin(context)
|| authorizeService.isItemAdmin(context);
} else if (commandLineParameters.stream()
.map(DSpaceCommandLineParameter::getName)
.noneMatch("-i"::equals)) {
return authorizeService.isAdmin(context);
} else {
String dspaceObjectID = commandLineParameters.stream()
.filter(parameter -> "-i".equals(parameter.getName()))
.map(DSpaceCommandLineParameter::getValue)
.findFirst()
.get();
HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
DSpaceObject dso = handleService.resolveToObject(context, dspaceObjectID);
return authorizeService.isAdmin(context, dso);
}
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
throw new RuntimeException(e);
}
}

View File

@@ -7,22 +7,14 @@
*/
package org.dspace.discovery;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link IndexClient} script
*/
public class IndexDiscoveryScriptConfiguration<T extends IndexClient> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
@@ -30,15 +22,6 @@ public class IndexDiscoveryScriptConfiguration<T extends IndexClient> extends Sc
return dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (options == null) {

View File

@@ -7,13 +7,8 @@
*/
package org.dspace.orcid.script;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Script configuration for {@link OrcidBulkPush}.
@@ -24,20 +19,8 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public class OrcidBulkPushScriptConfiguration<T extends OrcidBulkPush> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Class<T> getDspaceRunnableClass() {
return dspaceRunnableClass;

View File

@@ -129,6 +129,11 @@ public class ProcessServiceImpl implements ProcessService {
return processes;
}
@Override
public List<Process> findByUser(Context context, EPerson eperson, int limit, int offset) throws SQLException {
return processDAO.findByUser(context, eperson, limit, offset);
}
@Override
public void start(Context context, Process process) throws SQLException {
process.setProcessStatus(ProcessStatus.RUNNING);
@@ -311,6 +316,11 @@ public class ProcessServiceImpl implements ProcessService {
return this.processDAO.findByStatusAndCreationTimeOlderThan(context, statuses, date);
}
@Override
public int countByUser(Context context, EPerson user) throws SQLException {
return processDAO.countByUser(context, user);
}
private String formatLogLine(int processId, String scriptName, String output, ProcessLogLevel processLogLevel) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
StringBuilder sb = new StringBuilder();

View File

@@ -37,7 +37,7 @@ public class ScriptServiceImpl implements ScriptService {
@Override
public List<ScriptConfiguration> getScriptConfigurations(Context context) {
return serviceManager.getServicesByType(ScriptConfiguration.class).stream().filter(
scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context))
scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context, null))
.sorted(Comparator.comparing(ScriptConfiguration::getName))
.collect(Collectors.toList());
}

View File

@@ -7,17 +7,28 @@
*/
package org.dspace.scripts.configuration;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceCommandLineParameter;
import org.dspace.scripts.DSpaceRunnable;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
/**
* This class represents an Abstract class that a ScriptConfiguration can inherit to further implement this
* and represent a script's configuration
* and represent a script's configuration.
* By default script are available only to repository administrators script that have a broader audience
* must override the {@link #isAllowedToExecute(Context, List)} method.
*/
public abstract class ScriptConfiguration<T extends DSpaceRunnable> implements BeanNameAware {
@Autowired
protected AuthorizeService authorizeService;
/**
* The possible options for this script
*/
@@ -70,6 +81,7 @@ public abstract class ScriptConfiguration<T extends DSpaceRunnable> implements B
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this IndexDiscoveryScriptConfiguration
*/
public abstract void setDspaceRunnableClass(Class<T> dspaceRunnableClass);
/**
* This method will return if the script is allowed to execute in the given context. This is by default set
* to the currentUser in the context being an admin, however this can be overwritten by each script individually
@@ -77,7 +89,13 @@ public abstract class ScriptConfiguration<T extends DSpaceRunnable> implements B
* @param context The relevant DSpace context
* @return A boolean indicating whether the script is allowed to execute or not
*/
public abstract boolean isAllowedToExecute(Context context);
public boolean isAllowedToExecute(Context context, List<DSpaceCommandLineParameter> commandLineParameters) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
/**
* The getter for the options of the Script

View File

@@ -255,4 +255,26 @@ public interface ProcessService {
*/
List<Process> findByStatusAndCreationTimeOlderThan(Context context, List<ProcessStatus> statuses, Date date)
throws SQLException;
/**
* Returns a list of all Process objects in the database by the given user.
*
* @param context The relevant DSpace context
* @param user The user to search for
* @param limit The limit for the amount of Processes returned
* @param offset The offset for the Processes to be returned
* @return The list of all Process objects in the Database
* @throws SQLException If something goes wrong
*/
List<Process> findByUser(Context context, EPerson user, int limit, int offset) throws SQLException;
/**
* Count all the processes which is related to the given user.
*
* @param context The relevant DSpace context
* @param user The user to search for
* @return The number of results matching the query
* @throws SQLException If something goes wrong
*/
int countByUser(Context context, EPerson user) throws SQLException;
}

View File

@@ -7,13 +7,8 @@
*/
package org.dspace.statistics.export;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link RetryFailedOpenUrlTracker} script
@@ -21,9 +16,6 @@ import org.springframework.beans.factory.annotation.Autowired;
public class RetryFailedOpenUrlTrackerScriptConfiguration<T extends RetryFailedOpenUrlTracker>
extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
@@ -41,15 +33,6 @@ public class RetryFailedOpenUrlTrackerScriptConfiguration<T extends RetryFailedO
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (options == null) {

View File

@@ -7,13 +7,8 @@
*/
package org.dspace.submit.migration;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link SubmissionFormsMigration} script
@@ -23,9 +18,6 @@ import org.springframework.beans.factory.annotation.Autowired;
public class SubmissionFormsMigrationCliScriptConfiguration<T extends SubmissionFormsMigration>
extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
@@ -38,15 +30,6 @@ public class SubmissionFormsMigrationCliScriptConfiguration<T extends Submission
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (options == null) {

View File

@@ -7,7 +7,12 @@
*/
package org.dspace.submit.migration;
import java.util.List;
import org.apache.commons.cli.Options;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceCommandLineParameter;
import org.dspace.scripts.configuration.ScriptConfiguration;
/**
* Subclass of {@link SubmissionFormsMigrationCliScriptConfiguration} to be use in rest/scripts.xml configuration so
@@ -15,10 +20,37 @@ import org.dspace.core.Context;
*
* @author Maria Verdonck (Atmire) on 05/01/2021
*/
public class SubmissionFormsMigrationScriptConfiguration extends SubmissionFormsMigrationCliScriptConfiguration {
public class SubmissionFormsMigrationScriptConfiguration<T extends SubmissionFormsMigration>
extends ScriptConfiguration<T> {
private Class<T> dspaceRunnableClass;
@Override
public boolean isAllowedToExecute(Context context) {
public Class<T> getDspaceRunnableClass() {
return this.dspaceRunnableClass;
}
@Override
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public Options getOptions() {
if (options == null) {
Options options = new Options();
options.addOption("f", "input-forms", true, "Path to source input-forms.xml file location");
options.addOption("s", "item-submission", true, "Path to source item-submission.xml file location");
options.addOption("h", "help", false, "help");
super.options = options;
}
return options;
}
@Override
public boolean isAllowedToExecute(Context context, List<DSpaceCommandLineParameter> commandLineParameters) {
// Script is not allowed to be executed from REST side
return false;
}

View File

@@ -8,15 +8,11 @@
package org.dspace.subscriptions;
import java.sql.SQLException;
import java.util.Objects;
import org.apache.commons.cli.Options;
import org.dspace.authorize.AuthorizeServiceImpl;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them
@@ -26,18 +22,6 @@ public class SubscriptionEmailNotificationConfiguration<T
private Class<T> dspaceRunnableClass;
@Autowired
private AuthorizeServiceImpl authorizeService;
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (Objects.isNull(options)) {

View File

@@ -162,8 +162,8 @@ public abstract class AbstractDSpaceObjectBuilder<T extends DSpaceObject>
return (B) this;
}
/**
* Support method to grant the {@link Constants#READ} permission over an object only to a specific group. Any other
* READ permissions will be removed
* Support method to grant the {@link Constants#ADMIN} permission over an object only to a specific eperson.
* If another ADMIN policy is in place for an eperson it will be replaced
*
* @param dso
* the DSpaceObject on which grant the permission

View File

@@ -353,9 +353,9 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
}
/**
* Create an admin group for the collection with the specified members
* Assign the admin permission to the specified eperson
*
* @param ePerson epersons to add to the admin group
* @param ePerson the eperson that will get the ADMIN permission on the item
* @return this builder
* @throws SQLException
* @throws AuthorizeException

View File

@@ -113,6 +113,9 @@ public class ProcessBuilder extends AbstractBuilder<Process, ProcessService> {
}
public static void deleteProcess(Integer integer) throws SQLException, IOException {
if (integer == null) {
return;
}
try (Context c = new Context()) {
c.turnOffAuthorisationSystem();
Process process = processService.find(c, integer);

View File

@@ -8,21 +8,13 @@
package org.dspace.scripts;
import java.io.InputStream;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.dspace.scripts.impl.MockDSpaceRunnableScript;
import org.springframework.beans.factory.annotation.Autowired;
public class MockDSpaceRunnableScriptConfiguration<T extends MockDSpaceRunnableScript> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
@@ -39,15 +31,6 @@ public class MockDSpaceRunnableScriptConfiguration<T extends MockDSpaceRunnableS
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (options == null) {

View File

@@ -12,6 +12,7 @@ import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.exception.DSpaceBadRequestException;
import org.dspace.app.rest.model.ProcessRest;
import org.dspace.app.rest.model.ScriptRest;
import org.dspace.app.rest.model.hateoas.ProcessResource;
@@ -24,6 +25,7 @@ import org.springframework.data.rest.webmvc.ControllerUtils;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@@ -59,8 +61,8 @@ public class ScriptProcessesController {
* @return The ProcessResource object for the created process
* @throws Exception If something goes wrong
*/
@RequestMapping(method = RequestMethod.POST)
@PreAuthorize("hasAuthority('ADMIN')")
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public ResponseEntity<RepresentationModel<?>> startProcess(
@PathVariable(name = "name") String scriptName,
@RequestParam(name = "file", required = false) List<MultipartFile> files)
@@ -75,4 +77,13 @@ public class ScriptProcessesController {
return ControllerUtils.toResponseEntity(HttpStatus.ACCEPTED, new HttpHeaders(), processResource);
}
@RequestMapping(method = RequestMethod.POST, consumes = "!" + MediaType.MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public ResponseEntity<RepresentationModel<?>> startProcessInvalidMimeType(
@PathVariable(name = "name") String scriptName,
@RequestParam(name = "file", required = false) List<MultipartFile> files)
throws Exception {
throw new DSpaceBadRequestException("Invalid mimetype");
}
}

View File

@@ -94,6 +94,22 @@ public class ProcessRestRepository extends DSpaceRestRepository<ProcessRest, Int
}
}
@SearchRestMethod(name = "own")
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public Page<ProcessRest> findByCurrentUser(Pageable pageable) {
try {
Context context = obtainContext();
long total = processService.countByUser(context, context.getCurrentUser());
List<Process> processes = processService.findByUser(context, context.getCurrentUser(),
pageable.getPageSize(),
Math.toIntExact(pageable.getOffset()));
return converter.toRestPage(processes, pageable, total, utils.obtainProjection());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* Calls on the getBitstreams method to retrieve all the Bitstreams of this process
* @param processId The processId of the Process to retrieve the Bitstreams for

View File

@@ -37,6 +37,7 @@ import org.dspace.scripts.service.ScriptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
@@ -56,29 +57,24 @@ public class ScriptRestRepository extends DSpaceRestRepository<ScriptRest, Strin
@Autowired
private DSpaceRunnableParameterConverter dSpaceRunnableParameterConverter;
// TODO: findOne() currently requires site ADMIN permissions as all scripts are admin-only at this time.
// If scripts ever need to be accessible to Comm/Coll Admins, we would likely need to create a new GrantedAuthority
// for Comm/Coll Admins in EPersonRestAuthenticationProvider to use on this endpoint
@Override
@PreAuthorize("hasAuthority('ADMIN')")
// authorization is verified inside the method
@PreAuthorize("permitAll()")
public ScriptRest findOne(Context context, String name) {
ScriptConfiguration scriptConfiguration = scriptService.getScriptConfiguration(name);
if (scriptConfiguration != null) {
if (scriptConfiguration.isAllowedToExecute(context)) {
if (scriptConfiguration.isAllowedToExecute(context, null)) {
return converter.toRest(scriptConfiguration, utils.obtainProjection());
} else {
throw new AccessDeniedException("The current user was not authorized to access this script");
}
}
throw new DSpaceBadRequestException("The script with name: " + name + " could not be found");
return null;
}
// TODO: findAll() currently requires site ADMIN permissions as all scripts are admin-only at this time.
// If scripts ever need to be accessible to Comm/Coll Admins, we would likely need to create a new GrantedAuthority
// for Comm/Coll Admins in EPersonRestAuthenticationProvider to use on this endpoint
@Override
@PreAuthorize("hasAuthority('ADMIN')")
// authorization check is performed inside the script service
@PreAuthorize("permitAll()")
public Page<ScriptRest> findAll(Context context, Pageable pageable) {
List<ScriptConfiguration> scriptConfigurations =
scriptService.getScriptConfigurations(context);
@@ -104,11 +100,17 @@ public class ScriptRestRepository extends DSpaceRestRepository<ScriptRest, Strin
List<DSpaceCommandLineParameter> dSpaceCommandLineParameters =
processPropertiesToDSpaceCommandLineParameters(properties);
ScriptConfiguration scriptToExecute = scriptService.getScriptConfiguration(scriptName);
if (scriptToExecute == null) {
throw new DSpaceBadRequestException("The script for name: " + scriptName + " wasn't found");
throw new ResourceNotFoundException("The script for name: " + scriptName + " wasn't found");
}
if (!scriptToExecute.isAllowedToExecute(context)) {
throw new AuthorizeException("Current user is not eligible to execute script with name: " + scriptName);
try {
if (!scriptToExecute.isAllowedToExecute(context, dSpaceCommandLineParameters)) {
throw new AuthorizeException("Current user is not eligible to execute script with name: " + scriptName
+ " and the specified parameters " + StringUtils.join(dSpaceCommandLineParameters, ", "));
}
} catch (IllegalArgumentException e) {
throw new DSpaceBadRequestException("missed handle");
}
RestDSpaceRunnableHandler restDSpaceRunnableHandler = new RestDSpaceRunnableHandler(
context.getCurrentUser(), scriptToExecute.getName(), dSpaceCommandLineParameters,

View File

@@ -7,6 +7,8 @@
*/
package org.dspace.app.rest;
import static org.dspace.app.rest.matcher.ProcessMatcher.matchProcess;
import static org.dspace.content.ProcessStatus.SCHEDULED;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
@@ -783,6 +785,30 @@ public class ProcessRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(status().isBadRequest());
}
@Test
public void testFindByCurrentUser() throws Exception {
Process process1 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters)
.withStartAndEndTime("10/01/1990", "20/01/1990")
.build();
ProcessBuilder.createProcess(context, admin, "mock-script", parameters)
.withStartAndEndTime("11/01/1990", "19/01/1990")
.build();
Process process3 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters)
.withStartAndEndTime("12/01/1990", "18/01/1990")
.build();
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/system/processes/search/own"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.processes", contains(
matchProcess(process3.getName(), eperson.getID().toString(), process3.getID(), parameters, SCHEDULED),
matchProcess(process1.getName(), eperson.getID().toString(), process1.getID(), parameters, SCHEDULED))))
.andExpect(jsonPath("$.page", is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 2))));
}
@Test
public void getProcessOutput() throws Exception {
try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) {

View File

@@ -12,6 +12,7 @@ import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
@@ -44,6 +45,7 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.EPersonBuilder;
import org.dspace.builder.GroupBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.builder.ProcessBuilder;
@@ -53,6 +55,7 @@ import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.ProcessStatus;
import org.dspace.content.service.BitstreamService;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.scripts.DSpaceCommandLineParameter;
import org.dspace.scripts.Process;
@@ -123,12 +126,65 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void findAllScriptsUnauthorizedTest() throws Exception {
public void findAllScriptsGenericLoggedInUserTest() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/system/scripts"))
.andExpect(status().isForbidden());
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", is(0)));
}
@Test
public void findAllScriptsLocalAdminsTest() throws Exception {
context.turnOffAuthorisationSystem();
EPerson comAdmin = EPersonBuilder.createEPerson(context)
.withEmail("comAdmin@example.com")
.withPassword(password).build();
EPerson colAdmin = EPersonBuilder.createEPerson(context)
.withEmail("colAdmin@example.com")
.withPassword(password).build();
EPerson itemAdmin = EPersonBuilder.createEPerson(context)
.withEmail("itemAdmin@example.com")
.withPassword(password).build();
Community community = CommunityBuilder.createCommunity(context)
.withName("Community")
.withAdminGroup(comAdmin)
.build();
Collection collection = CollectionBuilder.createCollection(context, community)
.withName("Collection")
.withAdminGroup(colAdmin)
.build();
ItemBuilder.createItem(context, collection).withAdminUser(itemAdmin)
.withTitle("Test item to curate").build();
context.restoreAuthSystemState();
ScriptConfiguration curateScriptConfiguration =
scriptConfigurations.stream().filter(scriptConfiguration
-> scriptConfiguration.getName().equals("curate"))
.findAny().get();
// the local admins have at least access to the curate script
// and not access to process-cleaner script
String comAdminToken = getAuthToken(comAdmin.getEmail(), password);
getClient(comAdminToken).perform(get("/api/system/scripts").param("size", "100"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.scripts", Matchers.hasItem(
ScriptMatcher.matchScript(curateScriptConfiguration.getName(),
curateScriptConfiguration.getDescription()))))
.andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1)));
String colAdminToken = getAuthToken(colAdmin.getEmail(), password);
getClient(colAdminToken).perform(get("/api/system/scripts").param("size", "100"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.scripts", Matchers.hasItem(
ScriptMatcher.matchScript(curateScriptConfiguration.getName(),
curateScriptConfiguration.getDescription()))))
.andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1)));
String itemAdminToken = getAuthToken(itemAdmin.getEmail(), password);
getClient(itemAdminToken).perform(get("/api/system/scripts").param("size", "100"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.scripts", Matchers.hasItem(
ScriptMatcher.matchScript(curateScriptConfiguration.getName(),
curateScriptConfiguration.getDescription()))))
.andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1)));
}
@Test
@@ -222,6 +278,63 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
));
}
@Test
public void findOneScriptByNameLocalAdminsTest() throws Exception {
context.turnOffAuthorisationSystem();
EPerson comAdmin = EPersonBuilder.createEPerson(context)
.withEmail("comAdmin@example.com")
.withPassword(password).build();
EPerson colAdmin = EPersonBuilder.createEPerson(context)
.withEmail("colAdmin@example.com")
.withPassword(password).build();
EPerson itemAdmin = EPersonBuilder.createEPerson(context)
.withEmail("itemAdmin@example.com")
.withPassword(password).build();
Community community = CommunityBuilder.createCommunity(context)
.withName("Community")
.withAdminGroup(comAdmin)
.build();
Collection collection = CollectionBuilder.createCollection(context, community)
.withName("Collection")
.withAdminGroup(colAdmin)
.build();
ItemBuilder.createItem(context, collection).withAdminUser(itemAdmin)
.withTitle("Test item to curate").build();
context.restoreAuthSystemState();
ScriptConfiguration curateScriptConfiguration =
scriptConfigurations.stream().filter(scriptConfiguration
-> scriptConfiguration.getName().equals("curate"))
.findAny().get();
String comAdminToken = getAuthToken(comAdmin.getEmail(), password);
String colAdminToken = getAuthToken(colAdmin.getEmail(), password);
String itemAdminToken = getAuthToken(itemAdmin.getEmail(), password);
getClient(comAdminToken).perform(get("/api/system/scripts/" + curateScriptConfiguration.getName()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", ScriptMatcher
.matchScript(
curateScriptConfiguration.getName(),
curateScriptConfiguration.getDescription())));
getClient(colAdminToken).perform(get("/api/system/scripts/" + curateScriptConfiguration.getName()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", ScriptMatcher
.matchScript(
curateScriptConfiguration.getName(),
curateScriptConfiguration.getDescription())));
getClient(itemAdminToken).perform(get("/api/system/scripts/" + curateScriptConfiguration.getName()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", ScriptMatcher
.matchScript(
curateScriptConfiguration.getName(),
curateScriptConfiguration.getDescription())));
}
@Test
public void findOneScriptByNameNotAuthenticatedTest() throws Exception {
getClient().perform(get("/api/system/scripts/mock-script"))
.andExpect(status().isUnauthorized());
}
@Test
public void findOneScriptByNameTestAccessDenied() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
@@ -235,7 +348,7 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/system/scripts/mock-script-invalid"))
.andExpect(status().isBadRequest());
.andExpect(status().isNotFound());
}
@Test
@@ -277,16 +390,6 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void postProcessAdminNoOptionsFailedStatus() throws Exception {
// List<ParameterValueRest> list = new LinkedList<>();
//
// ParameterValueRest parameterValueRest = new ParameterValueRest();
// parameterValueRest.setName("-z");
// parameterValueRest.setValue("test");
// ParameterValueRest parameterValueRest1 = new ParameterValueRest();
// parameterValueRest1.setName("-q");
// list.add(parameterValueRest);
// list.add(parameterValueRest1);
LinkedList<DSpaceCommandLineParameter> parameters = new LinkedList<>();
parameters.add(new DSpaceCommandLineParameter("-z", "test"));
@@ -322,7 +425,7 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(multipart("/api/system/scripts/mock-script-invalid/processes"))
.andExpect(status().isBadRequest());
.andExpect(status().isNotFound());
}
@Test
@@ -434,6 +537,8 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
}
@Test
public void postProcessAdminWithWrongContentTypeBadRequestException() throws Exception {
@@ -601,9 +706,9 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
}
}
@After
public void destroy() throws Exception {
context.turnOffAuthorisationSystem();
CollectionUtils.emptyIfNull(processService.findAll(context)).stream().forEach(process -> {
try {
processService.delete(context, process);
@@ -611,6 +716,7 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
throw new RuntimeException(e);
}
});
context.restoreAuthSystemState();
super.destroy();
}

View File

@@ -11,7 +11,6 @@ import java.io.InputStream;
import org.apache.commons.cli.Options;
import org.dspace.app.rest.converter.ScriptConverter;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
/**
@@ -28,10 +27,6 @@ public class TypeConversionTestScriptConfiguration<T extends TypeConversionTestS
}
public boolean isAllowedToExecute(final Context context) {
return true;
}
public Options getOptions() {
Options options = new Options();

View File

@@ -14,6 +14,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.io.File;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@@ -29,13 +30,19 @@ import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.EPersonBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.builder.ProcessBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.ProcessStatus;
import org.dspace.content.Site;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.eperson.EPerson;
import org.dspace.scripts.DSpaceCommandLineParameter;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.dspace.scripts.service.ScriptService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -49,6 +56,9 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest {
@Autowired
private DSpaceRunnableParameterConverter dSpaceRunnableParameterConverter;
@Autowired
private ScriptService scriptService;
private final static String SCRIPTS_ENDPOINT = "/api/" + ScriptRest.CATEGORY + "/" + ScriptRest.PLURAL_NAME;
private final static String CURATE_SCRIPT_ENDPOINT = SCRIPTS_ENDPOINT + "/curate/" + ProcessRest.PLURAL_NAME;
@@ -371,6 +381,263 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest {
}
}
/**
* This test will create a basic structure of communities, collections and items with some local admins at each
* level and verify that the local admins can only run the curate script on their own objects
*/
@Test
public void securityCurateTest() throws Exception {
context.turnOffAuthorisationSystem();
EPerson comAdmin = EPersonBuilder.createEPerson(context)
.withEmail("comAdmin@example.com")
.withPassword(password).build();
EPerson colAdmin = EPersonBuilder.createEPerson(context)
.withEmail("colAdmin@example.com")
.withPassword(password).build();
EPerson itemAdmin = EPersonBuilder.createEPerson(context)
.withEmail("itemAdmin@example.com")
.withPassword(password).build();
Community community = CommunityBuilder.createCommunity(context)
.withName("Community")
.withAdminGroup(comAdmin)
.build();
Community anotherCommunity = CommunityBuilder.createCommunity(context)
.withName("Another Community")
.build();
Collection collection = CollectionBuilder.createCollection(context, community)
.withName("Collection")
.withAdminGroup(colAdmin)
.build();
Collection anotherCollection = CollectionBuilder.createCollection(context, anotherCommunity)
.withName("AnotherCollection")
.build();
Item item = ItemBuilder.createItem(context, collection).withAdminUser(itemAdmin)
.withTitle("Test item to curate").build();
Item anotherItem = ItemBuilder.createItem(context, anotherCollection)
.withTitle("Another Test item to curate").build();
Site site = ContentServiceFactory.getInstance().getSiteService().findSite(context);
context.restoreAuthSystemState();
LinkedList<DSpaceCommandLineParameter> siteParameters = new LinkedList<>();
siteParameters.add(new DSpaceCommandLineParameter("-i", site.getHandle()));
siteParameters.add(new DSpaceCommandLineParameter("-t", "noop"));
LinkedList<DSpaceCommandLineParameter> comParameters = new LinkedList<>();
comParameters.add(new DSpaceCommandLineParameter("-i", community.getHandle()));
comParameters.add(new DSpaceCommandLineParameter("-t", "noop"));
LinkedList<DSpaceCommandLineParameter> anotherComParameters = new LinkedList<>();
anotherComParameters.add(new DSpaceCommandLineParameter("-i", anotherCommunity.getHandle()));
anotherComParameters.add(new DSpaceCommandLineParameter("-t", "noop"));
LinkedList<DSpaceCommandLineParameter> colParameters = new LinkedList<>();
colParameters.add(new DSpaceCommandLineParameter("-i", collection.getHandle()));
colParameters.add(new DSpaceCommandLineParameter("-t", "noop"));
LinkedList<DSpaceCommandLineParameter> anotherColParameters = new LinkedList<>();
anotherColParameters.add(new DSpaceCommandLineParameter("-i", anotherCollection.getHandle()));
anotherColParameters.add(new DSpaceCommandLineParameter("-t", "noop"));
LinkedList<DSpaceCommandLineParameter> itemParameters = new LinkedList<>();
itemParameters.add(new DSpaceCommandLineParameter("-i", item.getHandle()));
itemParameters.add(new DSpaceCommandLineParameter("-t", "noop"));
LinkedList<DSpaceCommandLineParameter> anotherItemParameters = new LinkedList<>();
anotherItemParameters.add(new DSpaceCommandLineParameter("-i", anotherItem.getHandle()));
anotherItemParameters.add(new DSpaceCommandLineParameter("-t", "noop"));
String comAdminToken = getAuthToken(comAdmin.getEmail(), password);
String colAdminToken = getAuthToken(colAdmin.getEmail(), password);
String itemAdminToken = getAuthToken(itemAdmin.getEmail(), password);
List<ParameterValueRest> listCurateSite = siteParameters.stream()
.map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter
.convert(dSpaceCommandLineParameter, Projection.DEFAULT))
.collect(Collectors.toList());
List<ParameterValueRest> listCom = comParameters.stream()
.map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter
.convert(dSpaceCommandLineParameter, Projection.DEFAULT))
.collect(Collectors.toList());
List<ParameterValueRest> listAnotherCom = anotherComParameters.stream()
.map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter
.convert(dSpaceCommandLineParameter, Projection.DEFAULT))
.collect(Collectors.toList());
List<ParameterValueRest> listCol = colParameters.stream()
.map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter
.convert(dSpaceCommandLineParameter, Projection.DEFAULT))
.collect(Collectors.toList());
List<ParameterValueRest> listAnotherCol = anotherColParameters.stream()
.map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter
.convert(dSpaceCommandLineParameter, Projection.DEFAULT))
.collect(Collectors.toList());
List<ParameterValueRest> listItem = itemParameters.stream()
.map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter
.convert(dSpaceCommandLineParameter, Projection.DEFAULT))
.collect(Collectors.toList());
List<ParameterValueRest> listAnotherItem = anotherItemParameters.stream()
.map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter
.convert(dSpaceCommandLineParameter, Projection.DEFAULT))
.collect(Collectors.toList());
String adminToken = getAuthToken(admin.getEmail(), password);
List<ProcessStatus> acceptableProcessStatuses = new LinkedList<>();
acceptableProcessStatuses.addAll(Arrays.asList(ProcessStatus.SCHEDULED,
ProcessStatus.RUNNING,
ProcessStatus.COMPLETED));
AtomicReference<Integer> idSiteRef = new AtomicReference<>();
AtomicReference<Integer> idComRef = new AtomicReference<>();
AtomicReference<Integer> idComColRef = new AtomicReference<>();
AtomicReference<Integer> idComItemRef = new AtomicReference<>();
AtomicReference<Integer> idColRef = new AtomicReference<>();
AtomicReference<Integer> idColItemRef = new AtomicReference<>();
AtomicReference<Integer> idItemRef = new AtomicReference<>();
ScriptConfiguration curateScriptConfiguration = scriptService.getScriptConfiguration("curate");
// we should be able to start the curate script with all our admins on the respective dso
try {
// start a process as general admin
getClient(adminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listCurateSite)))
.andExpect(status().isAccepted())
.andExpect(jsonPath("$", is(
ProcessMatcher.matchProcess("curate",
String.valueOf(admin.getID()),
siteParameters,
acceptableProcessStatuses))))
.andDo(result -> idSiteRef
.set(read(result.getResponse().getContentAsString(), "$.processId")));
// check with the com admin
getClient(comAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listCom)))
.andExpect(status().isAccepted())
.andExpect(jsonPath("$", is(
ProcessMatcher.matchProcess("curate",
String.valueOf(comAdmin.getID()),
comParameters,
acceptableProcessStatuses))))
.andDo(result -> idComRef
.set(read(result.getResponse().getContentAsString(), "$.processId")));
// the com admin should be able to run the curate also over the children collection and item
getClient(comAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listCol)))
.andExpect(status().isAccepted())
.andExpect(jsonPath("$", is(
ProcessMatcher.matchProcess("curate",
String.valueOf(comAdmin.getID()),
colParameters,
acceptableProcessStatuses))))
.andDo(result -> idComColRef
.set(read(result.getResponse().getContentAsString(), "$.processId")));
getClient(comAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listItem)))
.andExpect(status().isAccepted())
.andExpect(jsonPath("$", is(
ProcessMatcher.matchProcess("curate",
String.valueOf(comAdmin.getID()),
itemParameters,
acceptableProcessStatuses))))
.andDo(result -> idComItemRef
.set(read(result.getResponse().getContentAsString(), "$.processId")));
// the com admin should be NOT able to run the curate over other com, col or items
getClient(comAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listCurateSite)))
.andExpect(status().isForbidden());
getClient(comAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listAnotherCom)))
.andExpect(status().isForbidden());
getClient(comAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listAnotherCol)))
.andExpect(status().isForbidden());
getClient(comAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listAnotherItem)))
.andExpect(status().isForbidden());
// check with the col admin
getClient(colAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listCol)))
.andExpect(status().isAccepted())
.andExpect(jsonPath("$", is(
ProcessMatcher.matchProcess("curate",
String.valueOf(colAdmin.getID()),
colParameters,
acceptableProcessStatuses))))
.andDo(result -> idColRef
.set(read(result.getResponse().getContentAsString(), "$.processId")));
// the col admin should be able to run the curate also over the owned item
getClient(colAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listItem)))
.andExpect(status().isAccepted())
.andExpect(jsonPath("$", is(
ProcessMatcher.matchProcess("curate",
String.valueOf(colAdmin.getID()),
itemParameters,
acceptableProcessStatuses))))
.andDo(result -> idColItemRef
.set(read(result.getResponse().getContentAsString(), "$.processId")));
// the col admin should be NOT able to run the curate over the community nor another collection nor
// on a not owned item
getClient(colAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listCurateSite)))
.andExpect(status().isForbidden());
getClient(colAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listCom)))
.andExpect(status().isForbidden());
getClient(colAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listAnotherCol)))
.andExpect(status().isForbidden());
getClient(colAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listAnotherItem)))
.andExpect(status().isForbidden());
// check with the item admin
getClient(itemAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listItem)))
.andExpect(status().isAccepted())
.andExpect(jsonPath("$", is(
ProcessMatcher.matchProcess("curate",
String.valueOf(itemAdmin.getID()),
itemParameters,
acceptableProcessStatuses))))
.andDo(result -> idItemRef
.set(read(result.getResponse().getContentAsString(), "$.processId")));
// the item admin should be NOT able to run the curate over the community nor the collection nor
// on a not owned item
getClient(itemAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listCurateSite)))
.andExpect(status().isForbidden());
getClient(itemAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listCom)))
.andExpect(status().isForbidden());
getClient(itemAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listCol)))
.andExpect(status().isForbidden());
getClient(itemAdminToken)
.perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes")
.param("properties", new ObjectMapper().writeValueAsString(listAnotherItem)))
.andExpect(status().isForbidden());
} finally {
ProcessBuilder.deleteProcess(idSiteRef.get());
ProcessBuilder.deleteProcess(idComRef.get());
ProcessBuilder.deleteProcess(idComColRef.get());
ProcessBuilder.deleteProcess(idComItemRef.get());
ProcessBuilder.deleteProcess(idColRef.get());
ProcessBuilder.deleteProcess(idColItemRef.get());
ProcessBuilder.deleteProcess(idItemRef.get());
}
}
}

View File

@@ -8,21 +8,13 @@
package org.dspace.scripts;
import java.io.InputStream;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.dspace.scripts.impl.MockDSpaceRunnableScript;
import org.springframework.beans.factory.annotation.Autowired;
public class MockDSpaceRunnableScriptConfiguration<T extends MockDSpaceRunnableScript> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
@@ -39,15 +31,6 @@ public class MockDSpaceRunnableScriptConfiguration<T extends MockDSpaceRunnableS
this.dspaceRunnableClass = dspaceRunnableClass;
}
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (options == null) {