Merge remote-tracking branch 'refs/remotes/origin/main'

Conflicts:
	dspace-api/src/main/java/org/dspace/submit/lookup/ArXivService.java
	dspace-api/src/main/java/org/dspace/submit/lookup/PubmedService.java
This commit is contained in:
Pasquale Cavallo
2020-07-29 15:14:07 +02:00
188 changed files with 9708 additions and 1271 deletions

22
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,22 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug, needs triage
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem. Link to examples if they are public.
**To Reproduce**
Steps to reproduce the behavior:
1. Do this
2. Then this...
**Expected behavior**
A clear and concise description of what you expected to happen.
**Related work**
Link to any related tickets or PRs here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest a new feature for this project
title: ''
labels: new feature, needs triage
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives or workarounds you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,26 @@
# This workflow runs whenever a new pull request is created
# TEMPORARILY DISABLED. Unfortunately this doesn't work for PRs created from forked repositories (which is how we tend to create PRs).
# There is no known workaround yet. See https://github.community/t/how-to-use-github-token-for-prs-from-forks/16818
name: Pull Request opened
# Only run for newly opened PRs against the "main" branch
on:
pull_request:
types: [opened]
branches:
- main
jobs:
automation:
runs-on: ubuntu-latest
steps:
# Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
# See https://github.com/marketplace/actions/pull-request-assigner
- name: Assign PR to creator
uses: thomaseizinger/assign-pr-creator-action@v1.0.0
# Note, this authentication token is created automatically
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Ignore errors. It is possible the PR was created by someone who cannot be assigned
continue-on-error: true

View File

@@ -1,8 +1,7 @@
## References
_Add references/links to any related tickets or PRs. These may include:_
* Link to [JIRA](https://jira.lyrasis.org/projects/DS/summary) ticket(s), if any
* Link to [REST Contract](https://github.com/DSpace/Rest7Contract) or an open REST Contract PR, if any
* Link to [Angular issue or PR](https://github.com/DSpace/dspace-angular/issues) related to this PR, if any
_Add references/links to any related issues or PRs. These may include:_
* Related to [REST Contract](https://github.com/DSpace/Rest7Contract) or an open REST Contract PR, if any
* Fixes [GitHub issue](https://github.com/DSpace/DSpace/issues), if any
## Description
Short summary of changes (1-2 sentences).
@@ -23,5 +22,5 @@ _This checklist provides a reminder of what we are going to look for when review
- [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide).
- [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods.
- [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
- [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/master/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
- [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
- [ ] If my PR modifies the REST API, I've linked to the REST Contract page (or open PR) related to this change.

29
.github/workflows/issue_opened.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
# This workflow runs whenever a new issue is created
name: Issue opened
on:
issues:
types: [opened]
jobs:
automation:
runs-on: ubuntu-latest
steps:
# Add the new issue to a project board, if it needs triage
# See https://github.com/marketplace/actions/create-project-card-action
- name: Add issue to project board
# Only add to project board if issue is flagged as "needs triage" or has no labels
# NOTE: By default we flag new issues as "needs triage" in our issue template
if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '')
uses: technote-space/create-project-card-action@v1
# Note, the authentication token below is an ORG level Secret.
# It must be created/recreated manually via a personal access token with "public_repo" and "admin:org" permissions
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token
# This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific)
with:
GITHUB_TOKEN: ${{ secrets.ORG_PROJECT_TOKEN }}
PROJECT: DSpace Backlog
COLUMN: Triage
CHECK_ORG_PROJECT: true
# Ignore errors.
continue-on-error: true

9
.lgtm.yml Normal file
View File

@@ -0,0 +1,9 @@
# LGTM Settings (https://lgtm.com/)
# For reference, see https://lgtm.com/help/lgtm/lgtm.yml-configuration-file
# or template at https://lgtm.com/static/downloads/lgtm.template.yml
extraction:
java:
index:
# Specify the Java version required to build the project
java_version: 11

View File

@@ -1,5 +1,5 @@
# This image will be published as dspace/dspace
# See https://github.com/DSpace/DSpace/tree/master/dspace/src/main/docker for usage details
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
#
# This version is JDK11 compatible
# - tomcat:8-jdk11

View File

@@ -1,5 +1,5 @@
# This image will be published as dspace/dspace-cli
# See https://github.com/DSpace/DSpace/tree/master/dspace/src/main/docker for usage details
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
#
# This version is JDK11 compatible
# - openjdk:11

View File

@@ -1,5 +1,5 @@
# This image will be published as dspace/dspace
# See https://github.com/DSpace/DSpace/tree/master/dspace/src/main/docker for usage details
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
#
# This version is JDK11 compatible
# - tomcat:8-jdk11

View File

@@ -1,24 +1,24 @@
# DSpace
[![Build Status](https://travis-ci.org/DSpace/DSpace.png?branch=master)](https://travis-ci.org/DSpace/DSpace)
[![Build Status](https://travis-ci.com/DSpace/DSpace.png?branch=main)](https://travis-ci.com/DSpace/DSpace)
[DSpace Documentation](https://wiki.duraspace.org/display/DSDOC/) |
[DSpace Documentation](https://wiki.lyrasis.org/display/DSDOC/) |
[DSpace Releases](https://github.com/DSpace/DSpace/releases) |
[DSpace Wiki](https://wiki.duraspace.org/display/DSPACE/Home) |
[Support](https://wiki.duraspace.org/display/DSPACE/Support)
[DSpace Wiki](https://wiki.lyrasis.org/display/DSPACE/Home) |
[Support](https://wiki.lyrasis.org/display/DSPACE/Support)
DSpace open source software is a turnkey repository application used by more than
2,000 organizations and institutions worldwide to provide durable access to digital resources.
For more information, visit http://www.dspace.org/
***
:warning: **Work on DSpace 7 has begun on our `master` branch.** This means that there is temporarily NO user interface on this `master` branch. DSpace 7 will feature a new, unified [Angular](https://angular.io/) user interface, along with an enhanced, rebuilt REST API. The latest status of this work can be found on the [DSpace 7 UI Working Group](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group) page. Additionally, the codebases can be found in the following places:
* DSpace 7 REST API work is occurring on the [`master` branch](https://github.com/DSpace/DSpace/tree/master/dspace-server-webapp) of this repository.
* The REST Contract is being documented at https://github.com/DSpace/Rest7Contract
:warning: **Work on DSpace 7 has begun on our `main` branch.** This means that there is NO user interface on this `main` branch. DSpace 7 will feature a new, unified [Angular](https://angular.io/) user interface, along with an enhanced, rebuilt REST API. The latest status of this work can be found on the [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) page. Additionally, the codebases can be found in the following places:
* DSpace 7 REST API work is occurring on the [`main` branch](https://github.com/DSpace/DSpace/tree/main/dspace-server-webapp) of this repository.
* The REST Contract is at https://github.com/DSpace/Rest7Contract
* DSpace 7 Angular UI work is occurring at https://github.com/DSpace/dspace-angular
**If you would like to get involved in our DSpace 7 development effort, we welcome new contributors.** Just join one of our meetings or get in touch via Slack. See the [DSpace 7 UI Working Group](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group) wiki page for more info.
**If you would like to get involved in our DSpace 7 development effort, we welcome new contributors.** Just join one of our meetings or get in touch via Slack. See the [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) wiki page for more info.
**If you are looking for the ongoing maintenance work for DSpace 6 (or prior releases)**, you can find that work on the corresponding maintenance branch (e.g. [`dspace-6_x`](https://github.com/DSpace/DSpace/tree/dspace-6_x)) in this repository.
***
@@ -31,10 +31,10 @@ Past releases are all available via GitHub at https://github.com/DSpace/DSpace/r
## Documentation / Installation
Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.duraspace.org/display/DSDOC/).
Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.lyrasis.org/display/DSDOC/).
The latest DSpace Installation instructions are available at:
https://wiki.duraspace.org/display/DSDOC6x/Installing+DSpace
https://wiki.lyrasis.org/display/DSDOC6x/Installing+DSpace
Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL or Oracle)
and a servlet container (usually Tomcat) in order to function.
@@ -49,14 +49,14 @@ DSpace is a community built and supported project. We do not have a centralized
but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc.
We welcome contributions of any type. Here's a few basic guides that provide suggestions for contributing to DSpace:
* [How to Contribute to DSpace](https://wiki.duraspace.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc)
* [Code Contribution Guidelines](https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc.
* [DSpace Community Advisory Team (DCAT)](https://wiki.duraspace.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam).
* [How to Contribute to DSpace](https://wiki.lyrasis.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc)
* [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc.
* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam).
We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.duraspace.org/display/DSPACE/Development+with+Git) guide for more info.
We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.lyrasis.org/display/DSPACE/Development+with+Git) guide for more info.
In addition, a listing of all known contributors to DSpace software can be
found online at: https://wiki.duraspace.org/display/DSPACE/DSpaceContributors
found online at: https://wiki.lyrasis.org/display/DSPACE/DSpaceContributors
## Getting Help
@@ -64,12 +64,12 @@ DSpace provides public mailing lists where you can post questions or raise topic
We welcome everyone to participate in these lists:
* [dspace-community@googlegroups.com](https://groups.google.com/d/forum/dspace-community) : General discussion about DSpace platform, announcements, sharing of best practices
* [dspace-tech@googlegroups.com](https://groups.google.com/d/forum/dspace-tech) : Technical support mailing list. See also our guide for [How to troubleshoot an error](https://wiki.duraspace.org/display/DSPACE/Troubleshoot+an+error).
* [dspace-tech@googlegroups.com](https://groups.google.com/d/forum/dspace-tech) : Technical support mailing list. See also our guide for [How to troubleshoot an error](https://wiki.lyrasis.org/display/DSPACE/Troubleshoot+an+error).
* [dspace-devel@googlegroups.com](https://groups.google.com/d/forum/dspace-devel) : Developers / Development mailing list
Great Q&A is also available under the [DSpace tag on Stackoverflow](http://stackoverflow.com/questions/tagged/dspace)
Additional support options are listed at https://wiki.duraspace.org/display/DSPACE/Support
Additional support options are at https://wiki.lyrasis.org/display/DSPACE/Support
DSpace also has an active service provider network. If you'd rather hire a service provider to
install, upgrade, customize or host DSpace, then we recommend getting in touch with one of our
@@ -77,14 +77,16 @@ install, upgrade, customize or host DSpace, then we recommend getting in touch w
## Issue Tracker
The DSpace Issue Tracker can be found at: https://jira.duraspace.org/projects/DS/summary
DSpace uses GitHub to track issues:
* Backend (REST API) issues: https://github.com/DSpace/DSpace/issues
* Frontend (User Interface) issues: https://github.com/DSpace/dspace-angular/issues
## Testing
### Running Tests
By default, in DSpace, Unit Tests and Integration Tests are disabled. However, they are
run automatically by [Travis CI](https://travis-ci.org/DSpace/DSpace/) for all Pull Requests and code commits.
run automatically by [Travis CI](https://travis-ci.com/DSpace/DSpace/) for all Pull Requests and code commits.
* How to run both Unit Tests (via `maven-surefire-plugin`) and Integration Tests (via `maven-failsafe-plugin`):
```
@@ -130,4 +132,4 @@ run automatically by [Travis CI](https://travis-ci.org/DSpace/DSpace/) for all P
## License
DSpace source code is freely available under a standard [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause).
The full license is available at http://www.dspace.org/license/
The full license is available in the [LICENSE](LICENSE) file or online at http://www.dspace.org/license/

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.dspace</groupId>
<artifactId>dspace-parent</artifactId>
<version>7.0-beta3-SNAPSHOT</version>
<version>7.0-beta4-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
@@ -291,9 +291,20 @@
</dependency>
<dependency>
<groupId>org.dspace</groupId>
<groupId>net.handle</groupId>
<artifactId>handle</artifactId>
</dependency>
<dependency>
<groupId>net.cnri</groupId>
<artifactId>cnri-servlet-container</artifactId>
<exclusions>
<!-- Newer versions provided in our parent POM -->
<exclusion>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Jetty is needed to run Handle Server -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
@@ -471,11 +482,59 @@
<artifactId>solr-cell</artifactId>
<version>${solr.client.version}</version>
<exclusions>
<!-- Newer version provided in our parent POM -->
<!-- Newer versions provided in our parent POM -->
<exclusion>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-xml</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-http</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-deploy</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-continuation</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-io</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
</exclusion>
</exclusions>
</dependency>

View File

@@ -115,7 +115,7 @@ public final class CreateAdministrator {
String lastName = null;
char[] password1 = null;
char[] password2 = null;
String language = I18nUtil.DEFAULTLOCALE.getLanguage();
String language = I18nUtil.getDefaultLocale().getLanguage();
while (!dataOK) {
System.out.print("E-mail address: ");

View File

@@ -1519,6 +1519,12 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
if (!dir.exists() && !dir.mkdirs()) {
log.error("Unable to create directory: " + dir.getAbsolutePath());
}
// Verify that the directory the entry is using is a subpath of zipDir (and not somewhere else!)
if (!dir.toPath().normalize().startsWith(zipDir)) {
throw new IOException("Bad zip entry: '" + entry.getName()
+ "' in file '" + zipfile.getAbsolutePath() + "'!"
+ " Cannot process this file.");
}
//Entries could have too many directories, and we need to adjust the sourcedir
// file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|...
@@ -1539,9 +1545,16 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
}
byte[] buffer = new byte[1024];
int len;
File outFile = new File(zipDir + entry.getName());
// Verify that this file will be created in our zipDir (and not somewhere else!)
if (!outFile.toPath().normalize().startsWith(zipDir)) {
throw new IOException("Bad zip entry: '" + entry.getName()
+ "' in file '" + zipfile.getAbsolutePath() + "'!"
+ " Cannot process this file.");
}
InputStream in = zf.getInputStream(entry);
BufferedOutputStream out = new BufferedOutputStream(
new FileOutputStream(zipDir + entry.getName()));
new FileOutputStream(outFile));
while ((len = in.read(buffer)) >= 0) {
out.write(buffer, 0, len);
}

View File

@@ -48,6 +48,9 @@ public class SHERPAResponse {
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder db = factory.newDocumentBuilder();
Document inDoc = db.parse(xmlData);

View File

@@ -9,7 +9,10 @@ package org.dspace.app.util;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.Logger;
import org.dspace.authenticate.factory.AuthenticateServiceFactory;
import org.dspace.authorize.AuthorizeConfiguration;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
@@ -26,9 +29,12 @@ import org.dspace.content.service.CollectionService;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.utils.DSpace;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.storedcomponents.CollectionRole;
import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService;
@@ -41,6 +47,7 @@ import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService;
*/
public class AuthorizeUtil {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(AuthorizeUtil.class);
/**
* Default constructor
*/
@@ -605,6 +612,50 @@ public class AuthorizeUtil {
throw new AuthorizeException("not authorized to manage this group");
}
/**
* This method will return a boolean indicating whether the current user is allowed to register a new
* account or not
* @param context The relevant DSpace context
* @param request The current request
* @return A boolean indicating whether the current user can register a new account or not
* @throws SQLException If something goes wrong
*/
public static boolean authorizeNewAccountRegistration(Context context, HttpServletRequest request)
throws SQLException {
if (DSpaceServicesFactory.getInstance().getConfigurationService()
.getBooleanProperty("user.registration", true)) {
// This allowSetPassword is currently the only mthod that would return true only when it's
// actually expected to be returning true.
// For example the LDAP canSelfRegister will return true due to auto-register, while that
// does not imply a new user can register explicitly
return AuthenticateServiceFactory.getInstance().getAuthenticationService()
.allowSetPassword(context, request, null);
}
return false;
}
/**
* This method will return a boolean indicating whether it's allowed to update the password for the EPerson
* with the given email and canLogin property
* @param context The relevant DSpace context
* @param email The email to be checked
* @return A boolean indicating if the password can be updated or not
*/
public static boolean authorizeUpdatePassword(Context context, String email) {
try {
EPerson eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, email);
if (eperson != null && eperson.canLogIn()) {
HttpServletRequest request = new DSpace().getRequestService().getCurrentRequest()
.getHttpServletRequest();
return AuthenticateServiceFactory.getInstance().getAuthenticationService()
.allowSetPassword(context, request, null);
}
} catch (SQLException e) {
log.error("Something went wrong trying to retrieve EPerson for email: " + email, e);
}
return false;
}
/**
* This method checks if the community Admin can manage accounts
*

View File

@@ -10,6 +10,7 @@ package org.dspace.app.util;
import java.util.List;
import java.util.Map;
import org.dspace.core.Utils;
/**
* Class representing all DC inputs required for a submission, organized into pages
*
@@ -107,12 +108,24 @@ public class DCInputSet {
for (int i = 0; i < inputs.length; i++) {
for (int j = 0; j < inputs[i].length; j++) {
DCInput field = inputs[i][j];
// If this is a "qualdrop_value" field, then the full field name is the field + dropdown qualifier
if (field.getInputType().equals("qualdrop_value")) {
List<String> pairs = field.getPairs();
for (int k = 0; k < pairs.size(); k += 2) {
String qualifier = pairs.get(k + 1);
String fullName = Utils.standardize(field.getSchema(), field.getElement(), qualifier, ".");
if (fullName.equals(fieldName)) {
return true;
}
}
} else {
String fullName = field.getFieldName();
if (fullName.equals(fieldName)) {
return true;
}
}
}
}
return false;
}

View File

@@ -250,12 +250,8 @@ public class IndexVersion {
} else if (firstMinor > secondMinor) {
// If we get here, major versions must be EQUAL. Now, time to check our minor versions
return GREATER_THAN;
} else if (firstMinor < secondMinor) {
return LESS_THAN;
} else {
// This is an impossible scenario.
// This 'else' should never be triggered since we've checked for equality above already
return EQUAL;
return LESS_THAN;
}
}

View File

@@ -87,13 +87,16 @@ public class IPMatcher {
+ ipSpec);
}
int maskBytes = maskBits / 8;
for (int i = 0; i < maskBytes; i++) {
netmask[i] = (byte) 0Xff;
}
netmask[maskBytes] = (byte) ((byte) 0Xff << 8 - (maskBits % 8)); // FIXME test!
for (int i = maskBytes + 1; i < (128 / 8); i++) {
for (int i = 0; i < netmask.length; i++) {
if (maskBits <= 0) {
netmask[i] = 0;
} else if (maskBits > 8) {
netmask[i] = (byte) 0Xff;
} else {
netmask[i] = (byte) ((byte) 0Xff << 8 - maskBits);
}
maskBits = maskBits - 8;
}
break;
case 1: // No explicit mask: fill the mask with 1s

View File

@@ -153,7 +153,7 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService {
// If the exception was thrown, unknown will == null so goahead and
// load s. If not, check that the unknown's registry's name is not
// being reset.
if (unknown == null || unknown.getID() != bitstreamFormat.getID()) {
if (unknown == null || !unknown.getID().equals(bitstreamFormat.getID())) {
bitstreamFormat.setShortDescriptionInternal(shortDescription);
}
}
@@ -208,7 +208,7 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService {
// Find "unknown" type
BitstreamFormat unknown = findUnknown(context);
if (unknown.getID() == bitstreamFormat.getID()) {
if (unknown.getID().equals(bitstreamFormat.getID())) {
throw new IllegalArgumentException("The Unknown bitstream format may not be deleted.");
}

View File

@@ -168,11 +168,11 @@ public class MetadataField implements ReloadableEntity<Integer> {
return false;
}
Class<?> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj);
if (getClass() != objClass) {
if (!getClass().equals(objClass)) {
return false;
}
final MetadataField other = (MetadataField) obj;
if (this.getID() != other.getID()) {
if (!this.getID().equals(other.getID())) {
return false;
}
if (!getMetadataSchema().equals(other.getMetadataSchema())) {

View File

@@ -67,11 +67,11 @@ public class MetadataSchema implements ReloadableEntity<Integer> {
return false;
}
Class<?> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj);
if (getClass() != objClass) {
if (!getClass().equals(objClass)) {
return false;
}
final MetadataSchema other = (MetadataSchema) obj;
if (this.id != other.id) {
if (!this.id.equals(other.id)) {
return false;
}
if ((this.namespace == null) ? (other.namespace != null) : !this.namespace.equals(other.namespace)) {

View File

@@ -239,17 +239,17 @@ public class MetadataValue implements ReloadableEntity<Integer> {
return false;
}
Class<?> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj);
if (getClass() != objClass) {
if (!getClass().equals(objClass)) {
return false;
}
final MetadataValue other = (MetadataValue) obj;
if (this.id != other.id) {
if (!this.id.equals(other.id)) {
return false;
}
if (this.getID() != other.getID()) {
if (!this.getID().equals(other.getID())) {
return false;
}
if (this.getDSpaceObject().getID() != other.getDSpaceObject().getID()) {
if (!this.getDSpaceObject().getID().equals(other.getDSpaceObject().getID())) {
return false;
}
return true;

View File

@@ -156,11 +156,11 @@ public class WorkspaceItem
return true;
}
Class<?> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(o);
if (getClass() != objClass) {
if (!getClass().equals(objClass)) {
return false;
}
final WorkspaceItem that = (WorkspaceItem) o;
if (this.getID() != that.getID()) {
if (!this.getID().equals(that.getID())) {
return false;
}

View File

@@ -272,13 +272,17 @@ public class METSManifest {
// Set validation feature
if (validate) {
builder.setFeature("http://apache.org/xml/features/validation/schema", true);
}
// Tell the parser where local copies of schemas are, to speed up
// validation. Local XSDs are identified in the configuration file.
// validation & avoid XXE attacks from remote schemas. Local XSDs are identified in the configuration file.
if (localSchemas.length() > 0) {
builder.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", localSchemas);
}
} else {
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
}
// Parse the METS file
Document metsDocument;

View File

@@ -179,7 +179,7 @@ public class Context implements AutoCloseable {
}
currentUser = null;
currentLocale = I18nUtil.DEFAULTLOCALE;
currentLocale = I18nUtil.getDefaultLocale();
extraLogInfo = "";
ignoreAuth = false;
@@ -876,4 +876,5 @@ public class Context implements AutoCloseable {
private void reloadContextBoundEntities() throws SQLException {
currentUser = reloadEntity(currentUser);
}
}

View File

@@ -37,9 +37,6 @@ import org.dspace.services.factory.DSpaceServicesFactory;
public class I18nUtil {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(I18nUtil.class);
// the default Locale of this DSpace Instance
public static final Locale DEFAULTLOCALE = getDefaultLocale();
// delimiters between elements of UNIX/POSIX locale spec, e.g. en_US.UTF-8
private static final String LOCALE_DELIMITERS = " _.";
@@ -127,7 +124,7 @@ public class I18nUtil {
return parseLocales(locales);
} else {
Locale[] availableLocales = new Locale[1];
availableLocales[0] = DEFAULTLOCALE;
availableLocales[0] = getDefaultLocale();
return availableLocales;
}
}
@@ -148,7 +145,7 @@ public class I18nUtil {
Locale supportedLocale = null;
String testLocale = "";
if (availableLocales == null) {
supportedLocale = DEFAULTLOCALE;
supportedLocale = getDefaultLocale();
} else {
if (!locale.getVariant().equals("")) {
testLocale = locale.toString();
@@ -188,7 +185,7 @@ public class I18nUtil {
}
}
if (!isSupported) {
supportedLocale = DEFAULTLOCALE;
supportedLocale = getDefaultLocale();
}
}
return supportedLocale;
@@ -220,7 +217,7 @@ public class I18nUtil {
* String of the message
*/
public static String getMessage(String key) {
return getMessage(key.trim(), DEFAULTLOCALE);
return getMessage(key.trim(), getDefaultLocale());
}
/**
@@ -233,7 +230,7 @@ public class I18nUtil {
*/
public static String getMessage(String key, Locale locale) {
if (locale == null) {
locale = DEFAULTLOCALE;
locale = getDefaultLocale();
}
ResourceBundle.Control control =
ResourceBundle.Control.getNoFallbackControl(
@@ -384,4 +381,23 @@ public class I18nUtil {
}
return resultList.toArray(new Locale[resultList.size()]);
}
/**
* Check if the input locale is in the list of supported locales
* @param locale
* @return true if locale is supported, false otherwise
*/
public static boolean isSupportedLocale(Locale locale) {
boolean isSupported = false;
Locale[] supportedLocales = getSupportedLocales();
if (supportedLocales != null) {
for (Locale sLocale: supportedLocales) {
if (locale.getLanguage().equals(sLocale.getLanguage()) ) {
isSupported = true;
break;
}
}
}
return isSupported;
}
}

View File

@@ -199,6 +199,9 @@ public class MetadataWebService extends AbstractCurationTask implements Namespac
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
try {
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
docBuilder = factory.newDocumentBuilder();
} catch (ParserConfigurationException pcE) {
log.error("caught exception: " + pcE);

View File

@@ -38,7 +38,7 @@ public interface CitationDocumentService {
* Citation enabled globally (all citable bitstreams will get "watermarked") modules/disseminate-citation:
* enable_globally
* OR
* The container is this object is whitelist enabled.
* The container is this object is "allow list" enabled.
* - community: modules/disseminate-citation: enabled_communities
* - collection: modules/disseminate-citation: enabled_collections
* AND

View File

@@ -12,6 +12,7 @@ import java.sql.SQLException;
import java.util.Locale;
import javax.mail.MessagingException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.ConfigurationManager;
@@ -22,6 +23,7 @@ import org.dspace.core.Utils;
import org.dspace.eperson.service.AccountService;
import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.RegistrationDataService;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -47,6 +49,8 @@ public class AccountServiceImpl implements AccountService {
protected EPersonService ePersonService;
@Autowired(required = true)
protected RegistrationDataService registrationDataService;
@Autowired
private ConfigurationService configurationService;
protected AccountServiceImpl() {
@@ -67,6 +71,9 @@ public class AccountServiceImpl implements AccountService {
public void sendRegistrationInfo(Context context, String email)
throws SQLException, IOException, MessagingException,
AuthorizeException {
if (!configurationService.getBooleanProperty("user.registration", true)) {
throw new IllegalStateException("The user.registration parameter was set to false");
}
sendInfo(context, email, true, true);
}
@@ -155,6 +162,14 @@ public class AccountServiceImpl implements AccountService {
registrationDataService.deleteByToken(context, token);
}
@Override
public boolean verifyPasswordStructure(String password) {
if (StringUtils.length(password) < 6) {
return false;
}
return true;
}
/**
* THIS IS AN INTERNAL METHOD. THE SEND PARAMETER ALLOWS IT TO BE USED FOR
* TESTING PURPOSES.
@@ -233,8 +248,8 @@ public class AccountServiceImpl implements AccountService {
// Note change from "key=" to "token="
String specialLink = new StringBuffer().append(base).append(
base.endsWith("/") ? "" : "/").append(
isRegister ? "register" : "forgot").append("?")
.append("token=").append(rd.getToken())
isRegister ? "register" : "forgot").append("/")
.append(rd.getToken())
.toString();
Locale locale = context.getCurrentLocale();
Email bean = Email.getEmail(I18nUtil.getEmailFilename(locale, isRegister ? "register"

View File

@@ -189,7 +189,8 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
return false;
// special, everyone is member of group 0 (anonymous)
} else if (StringUtils.equals(group.getName(), Group.ANONYMOUS)) {
} else if (StringUtils.equals(group.getName(), Group.ANONYMOUS) ||
isParentOf(context, group, findByName(context, Group.ANONYMOUS))) {
return true;
} else {

View File

@@ -46,4 +46,11 @@ public interface AccountService {
public void deleteToken(Context context, String token)
throws SQLException;
/**
* This method verifies that a certain String adheres to the password rules for DSpace
* @param password The String to be checked
* @return A boolean indicating whether or not the given String adheres to the password rules
*/
public boolean verifyPasswordStructure(String password);
}

View File

@@ -134,11 +134,13 @@ public class HarvestScheduler implements Runnable {
if (maxActiveThreads == 0) {
maxActiveThreads = 3;
}
minHeartbeat = ConfigurationManager.getIntProperty("oai", "harvester.minHeartbeat") * 1000;
minHeartbeat = ConfigurationManager.getIntProperty("oai", "harvester.minHeartbeat");
minHeartbeat = minHeartbeat * 1000; // multiple by 1000 to turn seconds to ms
if (minHeartbeat == 0) {
minHeartbeat = 30000;
}
maxHeartbeat = ConfigurationManager.getIntProperty("oai", "harvester.maxHeartbeat") * 1000;
maxHeartbeat = ConfigurationManager.getIntProperty("oai", "harvester.maxHeartbeat");
maxHeartbeat = maxHeartbeat * 1000; // multiple by 1000 to turn seconds to ms
if (maxHeartbeat == 0) {
maxHeartbeat = 3600000;
}

View File

@@ -83,7 +83,7 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor<OMEl
* @param query query string
* @param prefixToNamespaceMapping metadata prefix to namespace mapping
* @param field
* <a href="https://github.com/DSpace/DSpace/tree/master/dspace-api/src/main/java/org/dspace/importer/external#metadata-mapping-">MetadataFieldConfig</a>
* <a href="https://github.com/DSpace/DSpace/tree/main/dspace-api/src/main/java/org/dspace/importer/external#metadata-mapping-">MetadataFieldConfig</a>
*/
public SimpleXpathMetadatumContributor(String query, Map<String, String> prefixToNamespaceMapping,
MetadataFieldConfig field) {

View File

@@ -8,6 +8,8 @@
package org.dspace.license;
import java.util.List;
/**
* @author wbossons
*/
@@ -15,17 +17,17 @@ public class CCLicense {
private String licenseName;
private String licenseId;
private int order = 0;
private List<CCLicenseField> ccLicenseFieldList;
public CCLicense() {
super();
}
public CCLicense(String licenseId, String licenseName, int order) {
public CCLicense(String licenseId, String licenseName, List<CCLicenseField> ccLicenseFieldList) {
super();
this.licenseId = licenseId;
this.licenseName = licenseName;
this.order = order;
this.ccLicenseFieldList = ccLicenseFieldList;
}
public String getLicenseName() {
@@ -44,13 +46,19 @@ public class CCLicense {
this.licenseId = licenseId;
}
public int getOrder() {
return this.order;
/**
* Gets the list of CC License Fields
* @return the list of CC License Fields
*/
public List<CCLicenseField> getCcLicenseFieldList() {
return ccLicenseFieldList;
}
public void setOrder(int order) {
this.order = order;
/**
* Sets the list of CC License Fields
* @param ccLicenseFieldList
*/
public void setCcLicenseFieldList(final List<CCLicenseField> ccLicenseFieldList) {
this.ccLicenseFieldList = ccLicenseFieldList;
}
}

View File

@@ -0,0 +1,60 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.license;
import java.io.IOException;
import java.util.Map;
import org.jdom.Document;
/**
* Service interface class for the Creative commons license connector service.
* The implementation of this class is responsible for all the calls to the CC license API and parsing the response
* The service is autowired by spring
*/
public interface CCLicenseConnectorService {
/**
* Retrieves the CC Licenses for the provided language from the CC License API
*
* @param language - the language to retrieve the licenses for
* @return a map of licenses with the id and the license for the provided language
*/
public Map<String, CCLicense> retrieveLicenses(String language);
/**
* Retrieve the CC License URI based on the provided license id, language and answers to the field questions from
* the CC License API
*
* @param licenseId - the ID of the license
* @param language - the language for which to retrieve the full answerMap
* @param answerMap - the answers to the different field questions
* @return the CC License URI
*/
public String retrieveRightsByQuestion(String licenseId,
String language,
Map<String, String> answerMap);
/**
* Retrieve the license RDF document based on the license URI
*
* @param licenseURI - The license URI for which to retrieve the license RDF document
* @return the license RDF document
* @throws IOException
*/
public Document retrieveLicenseRDFDoc(String licenseURI) throws IOException;
/**
* Retrieve the license Name from the license document
*
* @param doc - The license document from which to retrieve the license name
* @return the license name
*/
public String retrieveLicenseName(final Document doc);
}

View File

@@ -0,0 +1,375 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.license;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.services.ConfigurationService;
import org.jaxen.JaxenException;
import org.jaxen.jdom.JDOMXPath;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.xml.sax.InputSource;
/**
* Implementation for the Creative commons license connector service.
* This class is responsible for all the calls to the CC license API and parsing the response
*/
public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, InitializingBean {
private Logger log = org.apache.logging.log4j.LogManager.getLogger(CCLicenseConnectorServiceImpl.class);
private CloseableHttpClient client;
protected SAXBuilder parser = new SAXBuilder();
private String postArgument = "answers";
private String postAnswerFormat =
"<answers> " +
"<locale>{1}</locale>" +
"<license-{0}>" +
"{2}" +
"</license-{0}>" +
"</answers>";
@Autowired
private ConfigurationService configurationService;
@Override
public void afterPropertiesSet() throws Exception {
HttpClientBuilder builder = HttpClientBuilder.create();
client = builder
.disableAutomaticRetries()
.setMaxConnTotal(5)
.build();
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
parser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
}
/**
* Retrieves the CC Licenses for the provided language from the CC License API
*
* @param language - the language to retrieve the licenses for
* @return a map of licenses with the id and the license for the provided language
*/
public Map<String, CCLicense> retrieveLicenses(String language) {
String ccLicenseUrl = configurationService.getProperty("cc.api.rooturl");
String uri = ccLicenseUrl + "/?locale=" + language;
HttpGet httpGet = new HttpGet(uri);
List<String> licenses;
try (CloseableHttpResponse response = client.execute(httpGet)) {
licenses = retrieveLicenses(response);
} catch (JDOMException | JaxenException | IOException e) {
log.error("Error while retrieving the license details using url: " + uri, e);
licenses = Collections.emptyList();
}
Map<String, CCLicense> ccLicenses = new HashMap<>();
for (String license : licenses) {
String licenseUri = ccLicenseUrl + "/license/" + license;
HttpGet licenseHttpGet = new HttpGet(licenseUri);
try (CloseableHttpResponse response = client.execute(licenseHttpGet)) {
CCLicense ccLicense = retrieveLicenseObject(license, response);
ccLicenses.put(ccLicense.getLicenseId(), ccLicense);
} catch (JaxenException | JDOMException | IOException e) {
log.error("Error while retrieving the license details using url: " + licenseUri, e);
}
}
return ccLicenses;
}
/**
* Retrieve the list of licenses from the response from the CC License API and remove the licenses configured
* to be excluded
*
* @param response The response from the API
* @return a list of license identifiers for which details need to be retrieved
* @throws IOException
* @throws JaxenException
* @throws JDOMException
*/
private List<String> retrieveLicenses(CloseableHttpResponse response)
throws IOException, JaxenException, JDOMException {
List<String> domains = new LinkedList<>();
String[] excludedLicenses = configurationService.getArrayProperty("cc.license.classfilter");
String responseString = EntityUtils.toString(response.getEntity());
JDOMXPath licenseClassXpath = new JDOMXPath("//licenses/license");
try (StringReader stringReader = new StringReader(responseString)) {
InputSource is = new InputSource(stringReader);
org.jdom.Document classDoc = this.parser.build(is);
List<Element> elements = licenseClassXpath.selectNodes(classDoc);
for (Element element : elements) {
String licenseId = getSingleNodeValue(element, "@id");
if (StringUtils.isNotBlank(licenseId) && !ArrayUtils.contains(excludedLicenses, licenseId)) {
domains.add(licenseId);
}
}
}
return domains;
}
/**
* Parse the response for a single CC License and return the corresponding CC License Object
*
* @param licenseId the license id of the CC License to retrieve
* @param response for a specific CC License response
* @return the corresponding CC License Object
* @throws IOException
* @throws JaxenException
* @throws JDOMException
*/
private CCLicense retrieveLicenseObject(final String licenseId, CloseableHttpResponse response)
throws IOException, JaxenException, JDOMException {
String responseString = EntityUtils.toString(response.getEntity());
JDOMXPath licenseClassXpath = new JDOMXPath("//licenseclass");
JDOMXPath licenseFieldXpath = new JDOMXPath("field");
try (StringReader stringReader = new StringReader(responseString)) {
InputSource is = new InputSource(stringReader);
org.jdom.Document classDoc = this.parser.build(is);
Object element = licenseClassXpath.selectSingleNode(classDoc);
String licenseLabel = getSingleNodeValue(element, "label");
List<CCLicenseField> ccLicenseFields = new LinkedList<>();
List<Element> licenseFields = licenseFieldXpath.selectNodes(element);
for (Element licenseField : licenseFields) {
CCLicenseField ccLicenseField = parseLicenseField(licenseField);
ccLicenseFields.add(ccLicenseField);
}
return new CCLicense(licenseId, licenseLabel, ccLicenseFields);
}
}
private CCLicenseField parseLicenseField(final Element licenseField) throws JaxenException {
String id = getSingleNodeValue(licenseField, "@id");
String label = getSingleNodeValue(licenseField, "label");
String description = getSingleNodeValue(licenseField, "description");
JDOMXPath enumXpath = new JDOMXPath("enum");
List<Element> enums = enumXpath.selectNodes(licenseField);
List<CCLicenseFieldEnum> ccLicenseFieldEnumList = new LinkedList<>();
for (Element enumElement : enums) {
CCLicenseFieldEnum ccLicenseFieldEnum = parseEnum(enumElement);
ccLicenseFieldEnumList.add(ccLicenseFieldEnum);
}
return new CCLicenseField(id, label, description, ccLicenseFieldEnumList);
}
private CCLicenseFieldEnum parseEnum(final Element enumElement) throws JaxenException {
String id = getSingleNodeValue(enumElement, "@id");
String label = getSingleNodeValue(enumElement, "label");
String description = getSingleNodeValue(enumElement, "description");
return new CCLicenseFieldEnum(id, label, description);
}
private String getNodeValue(final Object el) {
if (el instanceof Element) {
return ((Element) el).getValue();
} else if (el instanceof Attribute) {
return ((Attribute) el).getValue();
} else if (el instanceof String) {
return (String) el;
} else {
return null;
}
}
private String getSingleNodeValue(final Object t, String query) throws JaxenException {
JDOMXPath xpath = new JDOMXPath(query);
Object singleNode = xpath.selectSingleNode(t);
return getNodeValue(singleNode);
}
/**
* Retrieve the CC License URI based on the provided license id, language and answers to the field questions from
* the CC License API
*
* @param licenseId - the ID of the license
* @param language - the language for which to retrieve the full answerMap
* @param answerMap - the answers to the different field questions
* @return the CC License URI
*/
public String retrieveRightsByQuestion(String licenseId,
String language,
Map<String, String> answerMap) {
String ccLicenseUrl = configurationService.getProperty("cc.api.rooturl");
HttpPost httpPost = new HttpPost(ccLicenseUrl + "/license/" + licenseId + "/issue");
String answers = createAnswerString(answerMap);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
String text = MessageFormat.format(postAnswerFormat, licenseId, language, answers);
builder.addTextBody(postArgument, text);
HttpEntity multipart = builder.build();
httpPost.setEntity(multipart);
try (CloseableHttpResponse response = client.execute(httpPost)) {
return retrieveLicenseUri(response);
} catch (JDOMException | JaxenException | IOException e) {
log.error("Error while retrieving the license uri for license : " + licenseId + " with answers "
+ answerMap.toString(), e);
}
return null;
}
/**
* Parse the response for the CC License URI request and return the corresponding CC License URI
*
* @param response for a specific CC License URI response
* @return the corresponding CC License URI as a string
* @throws IOException
* @throws JaxenException
* @throws JDOMException
*/
private String retrieveLicenseUri(final CloseableHttpResponse response)
throws IOException, JaxenException, JDOMException {
String responseString = EntityUtils.toString(response.getEntity());
JDOMXPath licenseClassXpath = new JDOMXPath("//result/license-uri");
try (StringReader stringReader = new StringReader(responseString)) {
InputSource is = new InputSource(stringReader);
org.jdom.Document classDoc = this.parser.build(is);
Object node = licenseClassXpath.selectSingleNode(classDoc);
String nodeValue = getNodeValue(node);
if (StringUtils.isNotBlank(nodeValue)) {
return nodeValue;
}
}
return null;
}
private String createAnswerString(final Map<String, String> parameterMap) {
StringBuilder sb = new StringBuilder();
for (String key : parameterMap.keySet()) {
sb.append("<");
sb.append(key);
sb.append(">");
sb.append(parameterMap.get(key));
sb.append("</");
sb.append(key);
sb.append(">");
}
return sb.toString();
}
/**
* Retrieve the license RDF document based on the license URI
*
* @param licenseURI - The license URI for which to retrieve the license RDF document
* @return the license RDF document
* @throws IOException
*/
@Override
public Document retrieveLicenseRDFDoc(String licenseURI) throws IOException {
String ccLicenseUrl = configurationService.getProperty("cc.api.rooturl");
String issueUrl = ccLicenseUrl + "/details?license-uri=" + licenseURI;
URL request_url;
try {
request_url = new URL(issueUrl);
} catch (MalformedURLException e) {
return null;
}
URLConnection connection = request_url.openConnection();
connection.setDoOutput(true);
try {
// parsing document from input stream
InputStream stream = connection.getInputStream();
Document doc = parser.build(stream);
return doc;
} catch (Exception e) {
log.error("Error while retrieving the license document for URI: " + licenseURI, e);
}
return null;
}
/**
* Retrieve the license Name from the license document
*
* @param doc - The license document from which to retrieve the license name
* @return the license name
*/
public String retrieveLicenseName(final Document doc) {
try {
return getSingleNodeValue(doc, "//result/license-name");
} catch (JaxenException e) {
log.error("Error while retrieving the license name from the license document", e);
}
return null;
}
}

View File

@@ -7,8 +7,7 @@
*/
package org.dspace.license;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
/**
* Wrapper class for representation of a license field declaration.
@@ -22,7 +21,7 @@ public class CCLicenseField {
private String description = "";
private String type = "";
private HashMap fieldEnum = null;
private List<CCLicenseFieldEnum> fieldEnum = null;
/**
* Construct a new LicenseField class. Note that after construction,
@@ -31,13 +30,11 @@ public class CCLicenseField {
* @param id The unique identifier for this field; this value will be used in constructing the answers XML.
* @param label The label to use when generating the user interface.
*/
public CCLicenseField(String id, String label) {
super();
this.fieldEnum = new HashMap();
public CCLicenseField(String id, String label, String description, List<CCLicenseFieldEnum> fieldEnum) {
this.id = id;
this.label = label;
this.description = description;
this.fieldEnum = fieldEnum;
}
/**
@@ -90,16 +87,12 @@ public class CCLicenseField {
}
/**
* @return Returns an instance implementing the Map interface;
* the instance contains a mapping from identifiers to
* labels for the enumeration values.
* @see Map
* Returns the list of enums of this field
* @return the list of enums of this field
*/
public Map<String, String> getEnum() {
return this.fieldEnum;
public List<CCLicenseFieldEnum> getFieldEnum() {
return fieldEnum;
}
}

View File

@@ -0,0 +1,82 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.license;
import org.apache.commons.lang3.StringUtils;
/**
* Wrapper class for representation of a license field enum declaration.
* A field enum is a single "answer" to the field question
*/
public class CCLicenseFieldEnum {
private String id = "";
private String label = "";
private String description = "";
public CCLicenseFieldEnum(String id, String label, String description) {
if (StringUtils.isNotBlank(id)) {
this.id = id;
}
if (StringUtils.isNotBlank(label)) {
this.label = label;
}
if (StringUtils.isNotBlank(description)) {
this.description = description;
}
}
/**
* Get the id of this enum
* @return the id of this enum
*/
public String getId() {
return id;
}
/**
* Set the id of this enum
* @param id
*/
public void setId(final String id) {
this.id = id;
}
/**
* Get the label of this enum
* @return the label of this enum
*/
public String getLabel() {
return label;
}
/**
* Set the label of this enum
* @param label
*/
public void setLabel(final String label) {
this.label = label;
}
/**
* Get the description of this enum
* @return the description of this enum
*/
public String getDescription() {
return description;
}
/**
* Set the description of this enum
* @param description
*/
public void setDescription(final String description) {
this.description = description;
}
}

View File

@@ -1,435 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.license;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.apache.logging.log4j.Logger;
import org.dspace.license.factory.LicenseServiceFactory;
import org.dspace.license.service.CreativeCommonsService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.jaxen.JaxenException;
import org.jaxen.jdom.JDOMXPath;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
/**
* A wrapper around Creative Commons REST web services.
*
* @author Wendy Bossons
*/
public class CCLookup {
/**
* log4j logger
*/
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(CCLookup.class);
private String cc_root;
private String jurisdiction;
private List<String> lcFilter = new ArrayList<String>();
private Document license_doc = null;
private String rdfString = null;
private String errorMessage = null;
private boolean success = false;
private SAXBuilder parser = new SAXBuilder();
private List<CCLicense> licenses = new ArrayList<CCLicense>();
private List<CCLicenseField> licenseFields = new ArrayList<CCLicenseField>();
protected CreativeCommonsService creativeCommonsService = LicenseServiceFactory.getInstance()
.getCreativeCommonsService();
/**
* Constructs a new instance with the default web services root.
*/
public CCLookup() {
super();
ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
cc_root = configurationService.getProperty("cc.api.rooturl");
String jurisProp = configurationService.getProperty("cc.license.jurisdiction");
jurisdiction = (jurisProp != null) ? jurisProp : "";
String[] filters = configurationService.getArrayProperty("cc.license.classfilter");
if (filters != null) {
for (String name : filters) {
lcFilter.add(name.trim());
}
}
}
/**
* Returns the id for a particular CCLicense label. Returns an
* empty string if no match is found.
*
* @param class_label The CCLicense label to find.
* @return Returns a String containing the License class ID if the label
* is found; if not found, returns an empty string.
* @see CCLicense
*/
public String getLicenseId(String class_label) {
for (int i = 0; i < this.licenses.size(); i++) {
if (((CCLicense) this.licenses.get(i)).getLicenseName().equals(class_label)) {
return ((CCLicense) this.licenses.get(i)).getLicenseId();
}
}
return "";
}
/**
* Queries the web service for the available licenses.
*
* @param language The language to request labels and description strings in.
* @return Returns a Map of CCLicense objects.
* @see Map
* @see CCLicense
*/
public Collection<CCLicense> getLicenses(String language) {
// create XPath expressions
try {
JDOMXPath xp_Licenses = new JDOMXPath("//licenses/license");
JDOMXPath xp_LicenseID = new JDOMXPath("@id");
URL classUrl = new URL(this.cc_root + "/?locale=" + language);
Document classDoc = this.parser.build(classUrl);
// extract the identifiers and labels using XPath
List<Element> results = xp_Licenses.selectNodes(classDoc);
// populate licenses container
this.licenses.clear();
for (int i = 0; i < results.size(); i++) {
Element license = results.get(i);
// add if not filtered
String liD = ((Attribute) xp_LicenseID.selectSingleNode(license)).getValue();
if (!lcFilter.contains(liD)) {
this.licenses.add(new CCLicense(liD, license.getText(), i));
}
}
} catch (JaxenException jaxen_e) {
return null;
} catch (JDOMException jdom_e) {
return null;
} catch (IOException io_e) {
return null;
} catch (Exception e) {
// do nothing... but we should
return null;
}
return licenses;
}
/**
* Queries the web service for a set of licenseFields for a particular license class.
*
* @param license A String specifying the CCLicense identifier to
* retrieve fields for.
* @param language the locale string
* @return A Collection of LicenseField objects.
* @see CCLicense
*/
public Collection<CCLicenseField> getLicenseFields(String license, String language) {
JDOMXPath xp_LicenseField;
JDOMXPath xp_LicenseID;
JDOMXPath xp_FieldType;
JDOMXPath xp_Description;
JDOMXPath xp_Label;
JDOMXPath xp_Enum;
Document fieldDoc;
URL classUrl;
List results = null;
List enumOptions = null;
// create XPath expressions
try {
xp_LicenseField = new JDOMXPath("//field");
xp_LicenseID = new JDOMXPath("@id");
xp_Description = new JDOMXPath("description");
xp_Label = new JDOMXPath("label");
xp_FieldType = new JDOMXPath("type");
xp_Enum = new JDOMXPath("enum");
} catch (JaxenException e) {
return null;
}
// retrieve and parse the license class document
try {
classUrl = new URL(this.cc_root + "/license/" + license + "?locale=" + language);
} catch (Exception err) {
// do nothing... but we should
return null;
}
// parse the licenses document
try {
fieldDoc = this.parser.build(classUrl);
} catch (JDOMException e) {
return null;
} catch (IOException e) {
return null;
}
// reset the field definition container
this.licenseFields.clear();
// extract the identifiers and labels using XPath
try {
results = xp_LicenseField.selectNodes(fieldDoc);
} catch (JaxenException e) {
return null;
}
for (int i = 0; i < results.size(); i++) {
Element field = (Element) results.get(i);
try {
// create the field object
CCLicenseField cclicensefield = new CCLicenseField(
((Attribute) xp_LicenseID.selectSingleNode(field)).getValue(),
((Element) xp_Label.selectSingleNode(field)).getText());
// extract additional properties
cclicensefield.setDescription(((Element) xp_Description.selectSingleNode(field)).getText());
cclicensefield.setType(((Element) xp_FieldType.selectSingleNode(field)).getText());
enumOptions = xp_Enum.selectNodes(field);
for (int j = 0; j < enumOptions.size(); j++) {
String id = ((Attribute) xp_LicenseID.selectSingleNode(enumOptions.get(j))).getValue();
String label = ((Element) xp_Label.selectSingleNode(enumOptions.get(j))).getText();
cclicensefield.getEnum().put(id, label);
} // for each enum option
this.licenseFields.add(cclicensefield);
} catch (JaxenException e) {
return null;
}
}
return licenseFields;
} // licenseFields
/**
* Passes a set of "answers" to the web service and retrieves a license.
*
* @param licenseId The identifier of the license class being requested.
* @param answers A Map containing the answers to the license fields;
* each key is the identifier of a LicenseField, with the value
* containing the user-supplied answer.
* @param lang The language to request localized elements in.
* @throws IOException if IO error
* @see CCLicense
* @see Map
*/
public void issue(String licenseId, Map answers, String lang)
throws IOException {
// Determine the issue URL
String issueUrl = this.cc_root + "/license/" + licenseId + "/issue";
// Assemble the "answers" document
String answer_doc = "<answers>\n<locale>" + lang + "</locale>\n" + "<license-" + licenseId + ">\n";
Iterator keys = answers.keySet().iterator();
try {
String current = (String) keys.next();
while (true) {
answer_doc += "<" + current + ">" + (String) answers.get(current) + "</" + current + ">\n";
current = (String) keys.next();
}
} catch (NoSuchElementException e) {
// exception indicates we've iterated through the
// entire collection; just swallow and continue
}
// answer_doc += "<jurisdiction></jurisidiction>\n"; FAILS with jurisdiction argument
answer_doc += "</license-" + licenseId + ">\n</answers>\n";
String post_data;
try {
post_data = URLEncoder.encode("answers", "UTF-8") + "=" + URLEncoder.encode(answer_doc, "UTF-8");
} catch (UnsupportedEncodingException e) {
return;
}
URL post_url;
try {
post_url = new URL(issueUrl);
} catch (MalformedURLException e) {
return;
}
URLConnection connection = post_url.openConnection();
// this will not be needed after I'm done TODO: remove
connection.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
writer.write(post_data);
writer.flush();
// end TODO
try {
// parsing document from input stream
java.io.InputStream stream = connection.getInputStream();
this.license_doc = this.parser.build(stream);
} catch (JDOMException jde) {
log.warn(jde.getMessage());
} catch (Exception e) {
log.warn(e.getCause());
}
return;
} // issue
/**
* Passes a set of "answers" to the web service and retrieves a license.
*
* @param licenseURI The uri of the license.
*
* Note: does not support localization in 1.5 -- not yet
* @throws IOException if IO error
* @see CCLicense
* @see Map
*/
public void issue(String licenseURI)
throws IOException {
// Determine the issue URL
// Example: http://api.creativecommons.org/rest/1.5/details?
// license-uri=http://creativecommons.org/licenses/by-nc-sa/3.0/
String issueUrl = cc_root + "/details?license-uri=" + licenseURI;
URL request_url;
try {
request_url = new URL(issueUrl);
} catch (MalformedURLException e) {
return;
}
URLConnection connection = request_url.openConnection();
// this will not be needed after I'm done TODO: remove
connection.setDoOutput(true);
try {
// parsing document from input stream
java.io.InputStream stream = connection.getInputStream();
license_doc = this.parser.build(stream);
} catch (JDOMException jde) {
log.warn(jde.getMessage());
} catch (Exception e) {
log.warn(e.getCause());
}
return;
} // issue
/**
* Retrieves the URI for the license issued.
*
* @return A String containing the URI for the license issued.
*/
public String getLicenseUrl() {
String text = null;
try {
JDOMXPath xp_LicenseName = new JDOMXPath("//result/license-uri");
text = ((Element) xp_LicenseName.selectSingleNode(this.license_doc)).getText();
} catch (Exception e) {
log.warn(e.getMessage());
setSuccess(false);
text = "An error occurred getting the license - uri.";
} finally {
return text;
}
} // getLicenseUrl
/**
* Retrieves the human readable name for the license issued.
*
* @return A String containing the license name.
*/
public String getLicenseName() {
String text = null;
try {
JDOMXPath xp_LicenseName = new JDOMXPath("//result/license-name");
text = ((Element) xp_LicenseName.selectSingleNode(this.license_doc)).getText();
} catch (Exception e) {
log.warn(e.getMessage());
setSuccess(false);
text = "An error occurred on the license name.";
} finally {
return text;
}
} // getLicenseName
public org.jdom.Document getLicenseDocument() {
return this.license_doc;
}
public String getRdf()
throws IOException {
String result = "";
try {
result = creativeCommonsService.fetchLicenseRDF(license_doc);
} catch (Exception e) {
log.warn("An error occurred getting the rdf . . ." + e.getMessage());
setSuccess(false);
}
return result;
}
public boolean isSuccess() {
setSuccess(false);
JDOMXPath xp_Success;
String text = null;
try {
xp_Success = new JDOMXPath("//message");
text = ((Element) xp_Success.selectSingleNode(this.license_doc)).getText();
setErrorMessage(text);
} catch (Exception e) {
log.warn("There was an issue . . . " + text);
setSuccess(true);
}
return this.success;
}
private void setSuccess(boolean success) {
this.success = success;
}
public String getErrorMessage() {
return this.errorMessage;
}
private void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}

View File

@@ -13,7 +13,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
@@ -82,9 +85,18 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
protected BundleService bundleService;
@Autowired(required = true)
protected ItemService itemService;
@Autowired
protected CCLicenseConnectorService ccLicenseConnectorService;
protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
private String defaultLanguage;
private String jurisdiction;
private static final String JURISDICTION_KEY = "jurisdiction";
private Map<String, Map<String, CCLicense>> ccLicenses;
protected CreativeCommonsServiceImpl() {
}
@@ -101,6 +113,10 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
System.setProperty("http.proxyPort", proxyPort);
}
ccLicenses = new HashMap<>();
defaultLanguage = configurationService.getProperty("cc.license.locale", "en");
jurisdiction = configurationService.getProperty("cc.license.jurisdiction", "");
try {
templates = TransformerFactory.newInstance().newTemplates(
new StreamSource(CreativeCommonsServiceImpl.class
@@ -112,11 +128,6 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
}
@Override
public boolean isEnabled() {
return true;
}
// create the CC bundle if it doesn't exist
// If it does, remove it and create a new one.
protected Bundle getCcBundle(Context context, Item item)
@@ -168,8 +179,17 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
}
/**
* Removes the license file from the item
*
* @param context - The relevant DSpace Context
* @param item - The item from which the license file needs to be removed
* @throws SQLException
* @throws IOException
* @throws AuthorizeException
*/
@Override
public void removeLicense(Context context, Item item)
public void removeLicenseFile(Context context, Item item)
throws SQLException, IOException, AuthorizeException {
// remove CC license bundle if one exists
List<Bundle> bundles = itemService.getBundles(item, CC_BUNDLE_NAME);
@@ -179,34 +199,6 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
}
}
@Override
public boolean hasLicense(Context context, Item item)
throws SQLException, IOException {
// try to find CC license bundle
List<Bundle> bundles = itemService.getBundles(item, CC_BUNDLE_NAME);
if (bundles.size() == 0) {
return false;
}
// verify it has correct contents
try {
if ((getLicenseURL(context, item) == null)) {
return false;
}
} catch (AuthorizeException ae) {
return false;
}
return true;
}
@Override
public String getLicenseRDF(Context context, Item item) throws SQLException,
IOException, AuthorizeException {
return getStringFromBitstream(context, item, BSN_LICENSE_RDF);
}
@Override
public Bitstream getLicenseRdfBitstream(Item item) throws SQLException,
IOException, AuthorizeException {
@@ -222,15 +214,51 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
@Override
public String getLicenseURL(Context context, Item item) throws SQLException, IOException, AuthorizeException {
String licenseUri = getCCField("uri").ccItemValue(item);
String licenseUri = getCCField("uri");
if (StringUtils.isNotBlank(licenseUri)) {
return licenseUri;
return getLicenseURI(item);
}
// JSPUI backward compatibility see https://jira.duraspace.org/browse/DS-2604
return getStringFromBitstream(context, item, BSN_LICENSE_URL);
}
/**
* Returns the stored license uri of the item
*
* @param item - The item for which to retrieve the stored license uri
* @return the stored license uri of the item
*/
@Override
public String getLicenseURI(Item item) {
String licenseUriField = getCCField("uri");
if (StringUtils.isNotBlank(licenseUriField)) {
String metadata = itemService.getMetadata(item, licenseUriField);
if (StringUtils.isNotBlank(metadata)) {
return metadata;
}
}
return null;
}
/**
* Returns the stored license name of the item
*
* @param item - The item for which to retrieve the stored license name
* @return the stored license name of the item
*/
@Override
public String getLicenseName( Item item) {
String licenseNameField = getCCField("name");
if (StringUtils.isNotBlank(licenseNameField)) {
String metadata = itemService.getMetadata(item, licenseNameField);
if (StringUtils.isNotBlank(metadata)) {
return metadata;
}
}
return null;
}
@Override
public String fetchLicenseRDF(Document license) {
StringWriter result = new StringWriter();
@@ -361,26 +389,322 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
* Returns a metadata field handle for given field Id
*/
@Override
public LicenseMetadataValue getCCField(String fieldId) {
return new LicenseMetadataValue(configurationService.getProperty("cc.license." + fieldId));
public String getCCField(String fieldId) {
return configurationService.getProperty("cc.license." + fieldId);
}
/**
* Remove license information, delete also the bitstream
*
* @param context - DSpace Context
* @param item - the item
* @throws AuthorizeException Exception indicating the current user of the context does not have permission
* to perform a particular action.
* @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
@Override
public void removeLicense(Context context, LicenseMetadataValue uriField,
LicenseMetadataValue nameField, Item item)
public void removeLicense(Context context, Item item)
throws AuthorizeException, IOException, SQLException {
String uriField = getCCField("uri");
String nameField = getCCField("name");
String licenseUri = itemService.getMetadata(item, uriField);
// only remove any previous licenses
String licenseUri = uriField.ccItemValue(item);
if (licenseUri != null) {
uriField.removeItemValue(context, item, licenseUri);
removeLicenseField(context, item, uriField);
if (configurationService.getBooleanProperty("cc.submit.setname")) {
String licenseName = nameField.keyedItemValue(item, licenseUri);
nameField.removeItemValue(context, item, licenseName);
removeLicenseField(context, item, nameField);
}
if (configurationService.getBooleanProperty("cc.submit.addbitstream")) {
removeLicenseFile(context, item);
}
}
}
private void removeLicenseField(Context context, Item item, String field) throws SQLException {
String[] params = splitField(field);
itemService.clearMetadata(context, item, params[0], params[1], params[2], params[3]);
}
private void addLicenseField(Context context, Item item, String field, String value) throws SQLException {
String[] params = splitField(field);
itemService.addMetadata(context, item, params[0], params[1], params[2], params[3], value);
}
/**
* Find all CC Licenses using the default language found in the configuration
*
* @return A list of available CC Licenses
*/
@Override
public List<CCLicense> findAllCCLicenses() {
return findAllCCLicenses(defaultLanguage);
}
/**
* Find all CC Licenses for the provided language
*
* @param language - the language for which to find the CC Licenses
* @return A list of available CC Licenses for the provided language
*/
@Override
public List<CCLicense> findAllCCLicenses(String language) {
if (!ccLicenses.containsKey(language)) {
initLicenses(language);
}
return new LinkedList<>(ccLicenses.get(language).values());
}
/**
* Find the CC License corresponding to the provided ID using the default language found in the configuration
*
* @param id - the ID of the license to be found
* @return the corresponding license if found or null when not found
*/
@Override
public CCLicense findOne(String id) {
return findOne(id, defaultLanguage);
}
/**
* Find the CC License corresponding to the provided ID and provided language
*
* @param id - the ID of the license to be found
* @param language - the language for which to find the CC License
* @return the corresponding license if found or null when not found
*/
@Override
public CCLicense findOne(String id, String language) {
if (!ccLicenses.containsKey(language)) {
initLicenses(language);
}
Map<String, CCLicense> licenseMap = ccLicenses.get(language);
if (licenseMap.containsKey(id)) {
return licenseMap.get(id);
}
return null;
}
/**
* Retrieves the licenses for a specific language and cache them in this service
*
* @param language - the language for which to find the CC Licenses
*/
private void initLicenses(final String language) {
Map<String, CCLicense> licenseMap = ccLicenseConnectorService.retrieveLicenses(language);
ccLicenses.put(language, licenseMap);
}
/**
* Retrieve the CC License URI for the provided license ID, based on the provided answers, using the default
* language found in the configuration
*
* @param licenseId - the ID of the license
* @param answerMap - the answers to the different field questions
* @return the corresponding license URI
*/
@Override
public String retrieveLicenseUri(String licenseId, Map<String, String> answerMap) {
return retrieveLicenseUri(licenseId, defaultLanguage, answerMap);
}
/**
* Retrieve the CC License URI for the provided license ID and language based on the provided answers
*
* @param licenseId - the ID of the license
* @param language - the language for which to find the CC License URI
* @param answerMap - the answers to the different field questions
* @return the corresponding license URI
*/
@Override
public String retrieveLicenseUri(String licenseId, String language, Map<String, String> answerMap) {
return ccLicenseConnectorService.retrieveRightsByQuestion(licenseId, language, answerMap);
}
/**
* Verify whether the answer map contains a valid response to all field questions and no answers that don't have a
* corresponding question in the license, using the default language found in the config to check the license
*
* @param licenseId - the ID of the license
* @param fullAnswerMap - the answers to the different field questions
* @return whether the information is valid
*/
@Override
public boolean verifyLicenseInformation(String licenseId, Map<String, String> fullAnswerMap) {
return verifyLicenseInformation(licenseId, defaultLanguage, fullAnswerMap);
}
/**
* Verify whether the answer map contains a valid response to all field questions and no answers that don't have a
* corresponding question in the license, using the provided language to check the license
*
* @param licenseId - the ID of the license
* @param language - the language for which to retrieve the full answerMap
* @param fullAnswerMap - the answers to the different field questions
* @return whether the information is valid
*/
@Override
public boolean verifyLicenseInformation(String licenseId, String language, Map<String, String> fullAnswerMap) {
CCLicense ccLicense = findOne(licenseId, language);
List<CCLicenseField> ccLicenseFieldList = ccLicense.getCcLicenseFieldList();
for (String field : fullAnswerMap.keySet()) {
CCLicenseField ccLicenseField = findCCLicenseField(field, ccLicenseFieldList);
if (ccLicenseField == null) {
return false;
}
if (!containsAnswerEnum(fullAnswerMap.get(field), ccLicenseField)) {
return false;
}
}
return true;
}
/**
* Retrieve the full answer map containing empty values when an answer for a field was not provided in the
* answerMap, using the default language found in the configuration
*
* @param licenseId - the ID of the license
* @param answerMap - the answers to the different field questions
* @return the answerMap supplemented with all other license fields with a blank answer
*/
@Override
public Map<String, String> retrieveFullAnswerMap(String licenseId, Map<String, String> answerMap) {
return retrieveFullAnswerMap(licenseId, defaultLanguage, answerMap);
}
/**
* Retrieve the full answer map for a provided language, containing empty values when an answer for a field was not
* provided in the answerMap.
*
* @param licenseId - the ID of the license
* @param language - the language for which to retrieve the full answerMap
* @param answerMap - the answers to the different field questions
* @return the answerMap supplemented with all other license fields with a blank answer for the provided language
*/
@Override
public Map<String, String> retrieveFullAnswerMap(String licenseId, String language, Map<String, String> answerMap) {
CCLicense ccLicense = findOne(licenseId, language);
if (ccLicense == null) {
return null;
}
Map<String, String> fullParamMap = new HashMap<>(answerMap);
List<CCLicenseField> ccLicenseFieldList = ccLicense.getCcLicenseFieldList();
for (CCLicenseField ccLicenseField : ccLicenseFieldList) {
if (!fullParamMap.containsKey(ccLicenseField.getId())) {
fullParamMap.put(ccLicenseField.getId(), "");
}
}
updateJurisdiction(fullParamMap);
return fullParamMap;
}
private void updateJurisdiction(final Map<String, String> fullParamMap) {
if (fullParamMap.containsKey(JURISDICTION_KEY)) {
fullParamMap.put(JURISDICTION_KEY, jurisdiction);
}
}
private boolean containsAnswerEnum(final String enumAnswer, final CCLicenseField ccLicenseField) {
List<CCLicenseFieldEnum> fieldEnums = ccLicenseField.getFieldEnum();
for (CCLicenseFieldEnum fieldEnum : fieldEnums) {
if (StringUtils.equals(fieldEnum.getId(), enumAnswer)) {
return true;
}
}
return false;
}
private CCLicenseField findCCLicenseField(final String field, final List<CCLicenseField> ccLicenseFieldList) {
for (CCLicenseField ccLicenseField : ccLicenseFieldList) {
if (StringUtils.equals(ccLicenseField.getId(), field)) {
return ccLicenseField;
}
}
return null;
}
/**
* Update the license of the item with a new one based on the provided license URI
*
* @param context - The relevant DSpace context
* @param licenseUri - The license URI to be used in the update
* @param item - The item for which to update the license
* @return true when the update was successful, false when not
* @throws AuthorizeException
* @throws SQLException
*/
@Override
public boolean updateLicense(final Context context, final String licenseUri, final Item item)
throws AuthorizeException, SQLException {
try {
Document doc = ccLicenseConnectorService.retrieveLicenseRDFDoc(licenseUri);
if (doc == null) {
return false;
}
String licenseName = ccLicenseConnectorService.retrieveLicenseName(doc);
if (StringUtils.isBlank(licenseName)) {
return false;
}
removeLicense(context, item);
addLicense(context, item, licenseUri, licenseName, doc);
return true;
} catch (IOException e) {
log.error("Error while updating the license of item: " + item.getID(), e);
}
return false;
}
/**
* Add a new license to the item
*
* @param context - The relevant Dspace context
* @param item - The item to which the license will be added
* @param licenseUri - The license URI to add
* @param licenseName - The license name to add
* @param doc - The license to document to add
* @throws SQLException
* @throws IOException
* @throws AuthorizeException
*/
@Override
public void addLicense(Context context, Item item, String licenseUri, String licenseName, Document doc)
throws SQLException, IOException, AuthorizeException {
String uriField = getCCField("uri");
String nameField = getCCField("name");
addLicenseField(context, item, uriField, licenseUri);
if (configurationService.getBooleanProperty("cc.submit.addbitstream")) {
setLicenseRDF(context, item, fetchLicenseRDF(doc));
}
if (configurationService.getBooleanProperty("cc.submit.setname")) {
addLicenseField(context, item, nameField, licenseName);
}
}
private String[] splitField(String fieldName) {
String[] params = new String[4];
String[] fParams = fieldName.split("\\.");
for (int i = 0; i < fParams.length; i++) {
params[i] = fParams[i];
}
params[3] = Item.ANY;
return params;
}
}

View File

@@ -1,129 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.license;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
/**
* Helper class for using CC-related Metadata fields
*
* @author kevinvandevelde at atmire.com
*/
public class LicenseMetadataValue {
protected final ItemService itemService;
// Shibboleth for Creative Commons license data - i.e. characters that reliably indicate CC in a URI
protected static final String ccShib = "creativecommons";
private String[] params = new String[4];
public LicenseMetadataValue(String fieldName) {
if (fieldName != null && fieldName.length() > 0) {
String[] fParams = fieldName.split("\\.");
for (int i = 0; i < fParams.length; i++) {
params[i] = fParams[i];
}
params[3] = Item.ANY;
}
itemService = ContentServiceFactory.getInstance().getItemService();
}
/**
* Returns first value that matches Creative Commons 'shibboleth',
* or null if no matching values.
* NB: this method will succeed only for metadata fields holding CC URIs
*
* @param item - the item to read
* @return value - the first CC-matched value, or null if no such value
*/
public String ccItemValue(Item item) {
List<MetadataValue> dcvalues = itemService.getMetadata(item, params[0], params[1], params[2], params[3]);
for (MetadataValue dcvalue : dcvalues) {
if ((dcvalue.getValue()).indexOf(ccShib) != -1) {
// return first value that matches the shib
return dcvalue.getValue();
}
}
return null;
}
/**
* Returns the value that matches the value mapped to the passed key if any.
* NB: this only delivers a license name (if present in field) given a license URI
*
* @param item - the item to read
* @param key - the key for desired value
* @return value - the value associated with key or null if no such value
* @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
* @throws SQLException An exception that provides information on a database access error or other errors.
* @throws AuthorizeException Exception indicating the current user of the context does not have permission
* to perform a particular action.
*/
public String keyedItemValue(Item item, String key)
throws AuthorizeException, IOException, SQLException {
CCLookup ccLookup = new CCLookup();
ccLookup.issue(key);
String matchValue = ccLookup.getLicenseName();
List<MetadataValue> dcvalues = itemService.getMetadata(item, params[0], params[1], params[2], params[3]);
for (MetadataValue dcvalue : dcvalues) {
if (dcvalue.getValue().equals(matchValue)) {
return dcvalue.getValue();
}
}
return null;
}
/**
* Removes the passed value from the set of values for the field in passed item.
*
* @param context The relevant DSpace Context.
* @param item - the item to update
* @param value - the value to remove
* @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
* @throws SQLException An exception that provides information on a database access error or other errors.
* @throws AuthorizeException Exception indicating the current user of the context does not have permission
* to perform a particular action.
*/
public void removeItemValue(Context context, Item item, String value)
throws AuthorizeException, IOException, SQLException {
if (value != null) {
List<MetadataValue> dcvalues = itemService.getMetadata(item, params[0], params[1], params[2], params[3]);
ArrayList<String> arrayList = new ArrayList<String>();
for (MetadataValue dcvalue : dcvalues) {
if (!dcvalue.getValue().equals(value)) {
arrayList.add(dcvalue.getValue());
}
}
itemService.clearMetadata(context, item, params[0], params[1], params[2], params[3]);
itemService.addMetadata(context, item, params[0], params[1], params[2], params[3], arrayList);
}
}
/**
* Adds passed value to the set of values for the field in passed item.
*
* @param context The relevant DSpace Context.
* @param item - the item to update
* @param value - the value to add in this field
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public void addItemValue(Context context, Item item, String value) throws SQLException {
itemService.addMetadata(context, item, params[0], params[1], params[2], params[3], value);
}
}

View File

@@ -10,12 +10,14 @@ package org.dspace.license.service;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.license.LicenseMetadataValue;
import org.dspace.license.CCLicense;
import org.jdom.Document;
/**
@@ -29,13 +31,6 @@ public interface CreativeCommonsService {
public static final String CC_BUNDLE_NAME = "CC-LICENSE";
/**
* Simple accessor for enabling of CC
*
* @return is CC enabled?
*/
public boolean isEnabled();
/**
* setLicenseRDF
*
@@ -74,17 +69,38 @@ public interface CreativeCommonsService {
InputStream licenseStm, String mimeType)
throws SQLException, IOException, AuthorizeException;
public void removeLicense(Context context, Item item)
/**
* Removes the license file from the item
*
* @param context - The relevant DSpace Context
* @param item - The item from which the license file needs to be removed
* @throws SQLException
* @throws IOException
* @throws AuthorizeException
*/
public void removeLicenseFile(Context context, Item item)
throws SQLException, IOException, AuthorizeException;
public boolean hasLicense(Context context, Item item)
throws SQLException, IOException;
public String getLicenseURL(Context context, Item item)
throws SQLException, IOException, AuthorizeException;
public String getLicenseRDF(Context context, Item item)
throws SQLException, IOException, AuthorizeException;
/**
* Returns the stored license uri of the item
*
* @param item - The item for which to retrieve the stored license uri
* @return the stored license uri of the item
*/
public String getLicenseURI(Item item);
/**
* Returns the stored license name of the item
*
* @param item - The item for which to retrieve the stored license name
* @return the stored license name of the item
*/
public String getLicenseName(Item item);
/**
* Get Creative Commons license RDF, returning Bitstream object.
@@ -121,7 +137,7 @@ public interface CreativeCommonsService {
* @param fieldId name of the property.
* @return its value.
*/
public LicenseMetadataValue getCCField(String fieldId);
public String getCCField(String fieldId);
/**
* Apply same transformation on the document to retrieve only the most
@@ -138,15 +154,134 @@ public interface CreativeCommonsService {
* Remove license information, delete also the bitstream
*
* @param context - DSpace Context
* @param uriField - the metadata field for license uri
* @param nameField - the metadata field for license name
* @param item - the item
* @throws AuthorizeException Exception indicating the current user of the context does not have permission
* to perform a particular action.
* @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public void removeLicense(Context context, LicenseMetadataValue uriField,
LicenseMetadataValue nameField, Item item)
public void removeLicense(Context context, Item item)
throws AuthorizeException, IOException, SQLException;
/**
* Find all CC Licenses using the default language found in the configuration
*
* @return A list of available CC Licenses
*/
public List<CCLicense> findAllCCLicenses();
/**
* Find all CC Licenses for the provided language
*
* @param language - the language for which to find the CC Licenses
* @return A list of available CC Licenses for the provided language
*/
public List<CCLicense> findAllCCLicenses(String language);
/**
* Find the CC License corresponding to the provided ID using the default language found in the configuration
*
* @param id - the ID of the license to be found
* @return the corresponding license if found or null when not found
*/
public CCLicense findOne(String id);
/**
* Find the CC License corresponding to the provided ID and provided language
*
* @param id - the ID of the license to be found
* @param language - the language for which to find the CC License
* @return the corresponding license if found or null when not found
*/
public CCLicense findOne(String id, String language);
/**
* Retrieve the CC License URI for the provided license ID, based on the provided answers, using the default
* language found in the configuration
*
* @param licenseId - the ID of the license
* @param answerMap - the answers to the different field questions
* @return the corresponding license URI
*/
public String retrieveLicenseUri(String licenseId, Map<String, String> answerMap);
/**
* Retrieve the CC License URI for the provided license ID and language based on the provided answers
*
* @param licenseId - the ID of the license
* @param language - the language for which to find the CC License URI
* @param answerMap - the answers to the different field questions
* @return the corresponding license URI
*/
public String retrieveLicenseUri(String licenseId, String language, Map<String, String> answerMap);
/**
* Retrieve the full answer map containing empty values when an answer for a field was not provided in the
* answerMap, using the default language found in the configuration
*
* @param licenseId - the ID of the license
* @param answerMap - the answers to the different field questions
* @return the answerMap supplemented with all other license fields with a blank answer
*/
public Map<String, String> retrieveFullAnswerMap(String licenseId, Map<String, String> answerMap);
/**
* Retrieve the full answer map for a provided language, containing empty values when an answer for a field was not
* provided in the answerMap.
*
* @param licenseId - the ID of the license
* @param language - the language for which to retrieve the full answerMap
* @param answerMap - the answers to the different field questions
* @return the answerMap supplemented with all other license fields with a blank answer for the provided language
*/
public Map<String, String> retrieveFullAnswerMap(String licenseId, String language, Map<String, String> answerMap);
/**
* Verify whether the answer map contains a valid response to all field questions and no answers that don't have a
* corresponding question in the license, using the default language found in the config to check the license
*
* @param licenseId - the ID of the license
* @param fullAnswerMap - the answers to the different field questions
* @return whether the information is valid
*/
public boolean verifyLicenseInformation(String licenseId, Map<String, String> fullAnswerMap);
/**
* Verify whether the answer map contains a valid response to all field questions and no answers that don't have a
* corresponding question in the license, using the provided language to check the license
*
* @param licenseId - the ID of the license
* @param language - the language for which to retrieve the full answerMap
* @param fullAnswerMap - the answers to the different field questions
* @return whether the information is valid
*/
public boolean verifyLicenseInformation(String licenseId, String language, Map<String, String> fullAnswerMap);
/**
* Update the license of the item with a new one based on the provided license URI
*
* @param context - The relevant DSpace context
* @param licenseUri - The license URI to be used in the update
* @param item - The item for which to update the license
* @return true when the update was successful, false when not
* @throws AuthorizeException
* @throws SQLException
*/
public boolean updateLicense(final Context context, String licenseUri, final Item item)
throws AuthorizeException, SQLException;
/**
* Add a new license to the item
*
* @param context - The relevant Dspace context
* @param item - The item to which the license will be added
* @param licenseUri - The license URI to add
* @param licenseName - The license name to add
* @param doc - The license to document to add
* @throws SQLException
* @throws IOException
* @throws AuthorizeException
*/
public void addLicense(Context context, Item item, String licenseUri, String licenseName, Document doc)
throws SQLException, IOException, AuthorizeException;
}

View File

@@ -15,6 +15,7 @@ import java.util.Iterator;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.validator.routines.UrlValidator;
import org.apache.logging.log4j.Logger;
import org.dspace.rdf.RDFUtil;
import org.dspace.services.factory.DSpaceServicesFactory;
@@ -197,6 +198,7 @@ public class Negotiator {
if (extraPathInfo == null) {
extraPathInfo = "";
}
UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS);
StringBuilder urlBuilder = new StringBuilder();
String lang = null;
@@ -256,12 +258,15 @@ public class Negotiator {
urlBuilder.append(handle).append("/").append(extraPathInfo);
}
String url = urlBuilder.toString();
if (urlValidator.isValid(url)) {
log.debug("Will forward to '" + url + "'.");
response.setStatus(HttpServletResponse.SC_SEE_OTHER);
response.setHeader("Location", url);
response.flushBuffer();
return true;
} else {
throw new IOException("Invalid URL '" + url + "', cannot redirect.");
}
}
// currently we cannot serve statistics as rdf
@@ -287,10 +292,14 @@ public class Negotiator {
urlBuilder.append("/handle/").append(handle);
urlBuilder.append("/").append(lang);
String url = urlBuilder.toString();
if (urlValidator.isValid(url)) {
log.debug("Will forward to '" + url + "'.");
response.setStatus(HttpServletResponse.SC_SEE_OTHER);
response.setHeader("Location", url);
response.flushBuffer();
return true;
} else {
throw new IOException("Invalid URL '" + url + "', cannot redirect.");
}
}
}

View File

@@ -0,0 +1,162 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.submit.lookup;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import gr.ekt.bte.core.Record;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.dspace.app.util.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* @author Andrea Bollini
* @author Kostas Stamatis
* @author Luigi Andrea Pascarelli
* @author Panagiotis Koutsourakis
*/
public class ArXivService {
private int timeout = 1000;
/**
* How long to wait for a connection to be established.
*
* @param timeout milliseconds
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public List<Record> getByDOIs(Set<String> dois) throws HttpException,
IOException {
if (dois != null && dois.size() > 0) {
String doisQuery = StringUtils.join(dois.iterator(), " OR ");
return search(doisQuery, null, 100);
}
return null;
}
public List<Record> searchByTerm(String title, String author, int year)
throws HttpException, IOException {
StringBuffer query = new StringBuffer();
if (StringUtils.isNotBlank(title)) {
query.append("ti:\"").append(title).append("\"");
}
if (StringUtils.isNotBlank(author)) {
// [FAU]
if (query.length() > 0) {
query.append(" AND ");
}
query.append("au:\"").append(author).append("\"");
}
return search(query.toString(), "", 10);
}
protected List<Record> search(String query, String arxivid, int max_result)
throws IOException, HttpException {
List<Record> results = new ArrayList<Record>();
HttpGet method = null;
try {
HttpClient client = new DefaultHttpClient();
HttpParams params = client.getParams();
params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
try {
URIBuilder uriBuilder = new URIBuilder("http://export.arxiv.org/api/query");
uriBuilder.addParameter("id_list", arxivid);
uriBuilder.addParameter("search_query", query);
uriBuilder.addParameter("max_results", String.valueOf(max_result));
method = new HttpGet(uriBuilder.build());
} catch (URISyntaxException ex) {
throw new HttpException(ex.getMessage());
}
// Execute the method.
HttpResponse response = client.execute(method);
StatusLine responseStatus = response.getStatusLine();
int statusCode = responseStatus.getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
if (statusCode == HttpStatus.SC_BAD_REQUEST) {
throw new RuntimeException("arXiv query is not valid");
} else {
throw new RuntimeException("Http call failed: "
+ responseStatus);
}
}
try {
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder db = factory.newDocumentBuilder();
Document inDoc = db.parse(response.getEntity().getContent());
Element xmlRoot = inDoc.getDocumentElement();
List<Element> dataRoots = XMLUtils.getElementList(xmlRoot,
"entry");
for (Element dataRoot : dataRoots) {
Record crossitem = ArxivUtils
.convertArxixDomToRecord(dataRoot);
if (crossitem != null) {
results.add(crossitem);
}
}
} catch (Exception e) {
throw new RuntimeException(
"ArXiv identifier is not valid or not exist");
}
} finally {
if (method != null) {
method.releaseConnection();
}
}
return results;
}
public Record getByArXivIDs(String raw) throws HttpException, IOException {
if (StringUtils.isNotBlank(raw)) {
raw = raw.trim();
if (raw.startsWith("http://arxiv.org/abs/")) {
raw = raw.substring("http://arxiv.org/abs/".length());
} else if (raw.toLowerCase().startsWith("arxiv:")) {
raw = raw.substring("arxiv:".length());
}
List<Record> result = search("", raw, 1);
if (result != null && result.size() > 0) {
return result.get(0);
}
}
return null;
}
}

View File

@@ -102,6 +102,9 @@ public class CiNiiService {
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder db = factory.newDocumentBuilder();
Document inDoc = db.parse(response.getEntity().getContent());
@@ -178,6 +181,9 @@ public class CiNiiService {
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder db = factory.newDocumentBuilder();
Document inDoc = db.parse(response.getEntity().getContent());

View File

@@ -99,6 +99,9 @@ public class CrossRefService {
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder db = factory
.newDocumentBuilder();

View File

@@ -0,0 +1,274 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.submit.lookup;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import gr.ekt.bte.core.Record;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.XMLUtils;
import org.dspace.core.ConfigurationManager;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
/**
* @author Andrea Bollini
* @author Kostas Stamatis
* @author Luigi Andrea Pascarelli
* @author Panagiotis Koutsourakis
*/
public class PubmedService {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(PubmedService.class);
protected int timeout = 1000;
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public Record getByPubmedID(String pubmedid) throws HttpException,
IOException, ParserConfigurationException, SAXException {
List<String> ids = new ArrayList<String>();
ids.add(pubmedid.trim());
List<Record> items = getByPubmedIDs(ids);
if (items != null && items.size() > 0) {
return items.get(0);
}
return null;
}
public List<Record> search(String title, String author, int year)
throws HttpException, IOException {
StringBuffer query = new StringBuffer();
if (StringUtils.isNotBlank(title)) {
query.append("((").append(title).append("[TI]) OR (");
// [TI] does not always work, book chapter title
query.append("(").append(title).append("[book]))");
}
if (StringUtils.isNotBlank(author)) {
// [FAU]
if (query.length() > 0) {
query.append(" AND ");
}
query.append("(").append(author).append("[AU])");
}
if (year != -1) {
// [DP]
if (query.length() > 0) {
query.append(" AND ");
}
query.append(year).append("[DP]");
}
return search(query.toString());
}
public List<Record> search(String query) throws IOException, HttpException {
List<Record> results = new ArrayList<>();
if (!ConfigurationManager.getBooleanProperty(SubmissionLookupService.CFG_MODULE, "remoteservice.demo")) {
HttpGet method = null;
try {
HttpClient client = new DefaultHttpClient();
client.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
URIBuilder uriBuilder = new URIBuilder(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi");
uriBuilder.addParameter("db", "pubmed");
uriBuilder.addParameter("datetype", "edat");
uriBuilder.addParameter("retmax", "10");
uriBuilder.addParameter("term", query);
method = new HttpGet(uriBuilder.build());
// Execute the method.
HttpResponse response = client.execute(method);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
throw new RuntimeException("WS call failed: "
+ statusLine);
}
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder;
try {
builder = factory.newDocumentBuilder();
Document inDoc = builder.parse(response.getEntity().getContent());
Element xmlRoot = inDoc.getDocumentElement();
Element idList = XMLUtils.getSingleElement(xmlRoot,
"IdList");
List<String> pubmedIDs = XMLUtils.getElementValueList(
idList, "Id");
results = getByPubmedIDs(pubmedIDs);
} catch (ParserConfigurationException e1) {
log.error(e1.getMessage(), e1);
} catch (SAXException e1) {
log.error(e1.getMessage(), e1);
}
} catch (Exception e1) {
log.error(e1.getMessage(), e1);
} finally {
if (method != null) {
method.releaseConnection();
}
}
} else {
InputStream stream = null;
try {
File file = new File(
ConfigurationManager.getProperty("dspace.dir")
+ "/config/crosswalks/demo/pubmed-search.xml");
stream = new FileInputStream(file);
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document inDoc = builder.parse(stream);
Element xmlRoot = inDoc.getDocumentElement();
Element idList = XMLUtils.getSingleElement(xmlRoot, "IdList");
List<String> pubmedIDs = XMLUtils.getElementValueList(idList,
"Id");
results = getByPubmedIDs(pubmedIDs);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return results;
}
public List<Record> getByPubmedIDs(List<String> pubmedIDs)
throws HttpException, IOException, ParserConfigurationException,
SAXException {
List<Record> results = new ArrayList<Record>();
HttpGet method = null;
try {
HttpClient client = new DefaultHttpClient();
client.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 5 * timeout);
try {
URIBuilder uriBuilder = new URIBuilder(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi");
uriBuilder.addParameter("db", "pubmed");
uriBuilder.addParameter("retmode", "xml");
uriBuilder.addParameter("rettype", "full");
uriBuilder.addParameter("id", StringUtils.join(
pubmedIDs.iterator(), ","));
method = new HttpGet(uriBuilder.build());
} catch (URISyntaxException ex) {
throw new RuntimeException("Request not sent", ex);
}
// Execute the method.
HttpResponse response = client.execute(method);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
throw new RuntimeException("WS call failed: " + statusLine);
}
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
// disallow DTD parsing to ensure no XXE attacks can occur.
// See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document inDoc = builder
.parse(response.getEntity().getContent());
Element xmlRoot = inDoc.getDocumentElement();
List<Element> pubArticles = XMLUtils.getElementList(xmlRoot,
"PubmedArticle");
for (Element xmlArticle : pubArticles) {
Record pubmedItem = null;
try {
pubmedItem = PubmedUtils
.convertPubmedDomToRecord(xmlArticle);
results.add(pubmedItem);
} catch (Exception e) {
throw new RuntimeException(
"PubmedID is not valid or not exist: "
+ e.getMessage(), e);
}
}
return results;
} finally {
if (method != null) {
method.releaseConnection();
}
}
}
public List<Record> search(String doi, String pmid) throws HttpException,
IOException {
StringBuffer query = new StringBuffer();
if (StringUtils.isNotBlank(doi)) {
query.append(doi);
query.append("[AID]");
}
if (StringUtils.isNotBlank(pmid)) {
// [FAU]
if (query.length() > 0) {
query.append(" OR ");
}
query.append(pmid).append("[PMID]");
}
return search(query.toString());
}
}

View File

@@ -135,12 +135,12 @@ public class Version implements ReloadableEntity<Integer> {
return true;
}
Class<?> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(o);
if (getClass() != objClass) {
if (!getClass().equals(objClass)) {
return false;
}
final Version that = (Version) o;
if (this.getID() != that.getID()) {
if (!this.getID().equals(that.getID())) {
return false;
}

View File

@@ -93,12 +93,12 @@ public class VersionHistory implements ReloadableEntity<Integer> {
return true;
}
Class<?> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(o);
if (getClass() != objClass) {
if (!getClass().equals(objClass)) {
return false;
}
final VersionHistory that = (VersionHistory) o;
if (this.getID() != that.getID()) {
if (!this.getID().equals(that.getID())) {
return false;
}

View File

@@ -18,6 +18,7 @@
<!-- for handle "default". -->
<submission-map>
<name-map collection-handle="default" submission-name="traditional"/>
<name-map collection-handle="123456789/language-test-1" submission-name="languagetestprocess"/>
</submission-map>
@@ -82,9 +83,9 @@
<!--Step will be to select a Creative Commons License -->
<!-- Uncomment this step to allow the user to select a Creative Commons
license -->
<!-- <step id="creative-commons"> <heading>submit.progressbar.CClicense</heading>
<processing-class>org.dspace.submit.step.CCLicenseStep</processing-class>
<type>cclicense</type> </step> -->
<step id="cclicense"> <heading>submit.progressbar.CClicense</heading>
<processing-class>org.dspace.app.rest.submit.step.CCLicenseStep</processing-class>
<type>cclicense</type> </step>
<!--Step will be to Check for potential duplicate -->
<!-- <step id="detect-duplicate"> <heading>submit.progressbar.detect.duplicate</heading>
@@ -108,6 +109,12 @@
<processing-class>org.dspace.submit.step.SampleStep</processing-class>
<type>sample</type>
</step>
<step id="languagetest" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
</step-definitions>
<!-- The submission-definitions map lays out the detailed definition of -->
@@ -145,10 +152,14 @@
<!--Step will be to Sign off on the License -->
<step id="license"/>
<!-- <step id="creative-commons"/> -->
<step id="cclicense"/>
<!-- <step id="verify"/> -->
</submission-process>
<submission-process name="languagetestprocess">
<step id="collection"/>
<step id="languagetest"/>
</submission-process>
</submission-definitions>
</item-submission>

View File

@@ -120,3 +120,7 @@ rest.properties.exposed = configuration.not.existing
configuration.not.exposed = secret_value
configuration.exposed.single.value = public_value
configuration.exposed.array.value = public_value_1, public_value_2
# Test config for the authentication ip functionality
authentication-ip.Staff = 5.5.5.5
authentication-ip.Student = 6.6.6.6

View File

@@ -0,0 +1,169 @@
<?xml version="1.0"?>
<!DOCTYPE input-forms SYSTEM "submission-forms.dtd">
<input-forms>
<!-- The form-definitions map lays out the detailed definition of all the -->
<!-- submission forms. Each separate form set has a unique name as an -->
<!-- attribute. This name matches one of the names in the form-map. One -->
<!-- named form set has the name "traditional"; as this name suggests, -->
<!-- it is the old style and is also the default, which gets used when -->
<!-- the specified collection has no correspondingly-named form set. -->
<!-- -->
<!-- Each form set contains an ordered set of pages; each page defines -->
<!-- one submission metadata entry screen. Each page has an ordered list -->
<!-- of field definitions, Each field definition corresponds to one -->
<!-- metadata entry (a so-called row), which has a DC element name, a -->
<!-- displayed label, a text string prompt which is called a hint, and -->
<!-- an input-type. Each field also may hold optional elements: DC -->
<!-- qualifier name, a repeatable flag, and a text string whose presence -->
<!-- serves as a 'this field is required' flag. -->
<form-definitions>
<form name="bitstream-metadata">
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>title</dc-element>
<dc-qualifier></dc-qualifier>
<repeatable>false</repeatable>
<label>Titolo</label>
<input-type>onebox</input-type>
<hint>Inserisci nome del file</hint>
<required>È necessario inserire un titolo principale per questo item</required>
</field>
</row>
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>description</dc-element>
<repeatable>true</repeatable>
<label>Descrizione</label>
<input-type>textarea</input-type>
<hint>Inserisci descrizione per questo file</hint>
<required></required>
</field>
</row>
</form>
<form name="languagetest">
<row>
<relation-field>
<relationship-type>isAuthorOfPublication</relationship-type>
<search-configuration>person</search-configuration>
<repeatable>true</repeatable>
<label>Autore</label>
<hint>Aggiungi un autore</hint>
<linked-metadata-field>
<dc-schema>dc</dc-schema>
<dc-element>contributor</dc-element>
<dc-qualifier>author</dc-qualifier>
<input-type>name</input-type>
</linked-metadata-field>
<required>È richiesto almeno un autore</required>
</relation-field>
</row>
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>title</dc-element>
<dc-qualifier></dc-qualifier>
<repeatable>false</repeatable>
<label>Titolo</label>
<input-type>onebox</input-type>
<hint>Inserisci titolo principale di questo item</hint>
<required>È necessario inserire un titolo principale per questo item</required>
<!-- <language value-pairs-name="common_iso_languages">true</language> -->
</field>
</row>
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>language</dc-element>
<dc-qualifier>iso</dc-qualifier>
<repeatable>false</repeatable>
<label>Lingua</label>
<input-type value-pairs-name="common_iso_languages">dropdown</input-type>
<hint>Selezionare la lingua del contenuto principale dell'item. Se la lingua non compare nell'elenco, selezionare (Altro). Se il contenuto non ha davvero una lingua (ad esempio, se è un set di dati o un'immagine) selezionare (N/A).
</hint>
<required></required>
</field>
</row>
</form>
</form-definitions>
<!-- form-value-pairs populate dropdown and qualdrop-value lists. -->
<!-- The form-value-pairs element holds child elements named 'value-pairs' -->
<!-- A 'value-pairs' element has a value-pairs-name and a dc-term -->
<!-- attribute. The dc-term attribute specifies which to which Dublin Core -->
<!-- Term this set of value-pairs applies. -->
<!-- Current dc-terms are: identifier-pairs, type-pairs, and -->
<!-- language_iso-pairs. The name attribute matches a name -->
<!-- in the form-map, above. -->
<!-- A value-pair contains one 'pair' for each value displayed in the list -->
<!-- Each pair contains a 'displayed-value' element and a 'stored-value' -->
<!-- element. A UI list displays the displayed-values, but the program -->
<!-- stores the associated stored-values in the database. -->
<form-value-pairs>
<!-- default language order: (from dspace 1.2.1)
"en_US", "en", "es", "de", "fr", "it", "ja", "zh", "other", ""
-->
<value-pairs value-pairs-name="common_iso_languages" dc-term="language_iso">
<pair>
<displayed-value>N/A</displayed-value>
<stored-value></stored-value>
</pair>
<pair>
<displayed-value>Inglese (USA)</displayed-value>
<stored-value>en_US</stored-value>
</pair>
<pair>
<displayed-value>Inglese</displayed-value>
<stored-value>en</stored-value>
</pair>
<pair>
<displayed-value>Spagnolo</displayed-value>
<stored-value>es</stored-value>
</pair>
<pair>
<displayed-value>Tedesco</displayed-value>
<stored-value>de</stored-value>
</pair>
<pair>
<displayed-value>Francese</displayed-value>
<stored-value>fr</stored-value>
</pair>
<pair>
<displayed-value>Italiano</displayed-value>
<stored-value>it</stored-value>
</pair>
<pair>
<displayed-value>Giapponese</displayed-value>
<stored-value>ja</stored-value>
</pair>
<pair>
<displayed-value>Cinese</displayed-value>
<stored-value>zh</stored-value>
</pair>
<pair>
<displayed-value>Portogallo</displayed-value>
<stored-value>pt</stored-value>
</pair>
<pair>
<displayed-value>Ucraino</displayed-value>
<stored-value>uk</stored-value>
</pair>
<pair>
<displayed-value>(Altro)</displayed-value>
<stored-value>other</stored-value>
</pair>
</value-pairs>
</form-value-pairs>
</input-forms>

View File

@@ -0,0 +1,166 @@
<?xml version="1.0"?>
<!DOCTYPE input-forms SYSTEM "submission-forms.dtd">
<input-forms>
<!-- The form-definitions map lays out the detailed definition of all the -->
<!-- submission forms. Each separate form set has a unique name as an -->
<!-- attribute. This name matches one of the names in the form-map. One -->
<!-- named form set has the name "traditional"; as this name suggests, -->
<!-- it is the old style and is also the default, which gets used when -->
<!-- the specified collection has no correspondingly-named form set. -->
<!-- -->
<!-- Each form set contains an ordered set of pages; each page defines -->
<!-- one submission metadata entry screen. Each page has an ordered list -->
<!-- of field definitions, Each field definition corresponds to one -->
<!-- metadata entry (a so-called row), which has a DC element name, a -->
<!-- displayed label, a text string prompt which is called a hint, and -->
<!-- an input-type. Each field also may hold optional elements: DC -->
<!-- qualifier name, a repeatable flag, and a text string whose presence -->
<!-- serves as a 'this field is required' flag. -->
<form-definitions>
<form name="bitstream-metadata">
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>title</dc-element>
<dc-qualifier></dc-qualifier>
<repeatable>false</repeatable>
<label>Заголовок</label>
<input-type>onebox</input-type>
<hint>Ввести основний заголовок файла.</hint>
<required>Заговолок файла обов'язковий !</required>
</field>
</row>
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>description</dc-element>
<repeatable>true</repeatable>
<label>Опис</label>
<input-type>textarea</input-type>
<hint>Ввести опис для цього файла</hint>
<required></required>
</field>
</row>
</form>
<form name="languagetest">
<row>
<relation-field>
<relationship-type>isAuthorOfPublication</relationship-type>
<search-configuration>person</search-configuration>
<repeatable>true</repeatable>
<label>Автор</label>
<hint>Додати автора</hint>
<linked-metadata-field>
<dc-schema>dc</dc-schema>
<dc-element>contributor</dc-element>
<dc-qualifier>author</dc-qualifier>
<input-type>name</input-type>
</linked-metadata-field>
<required>Потрібно ввести хочаб одного автора!</required>
</relation-field>
</row>
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>title</dc-element>
<dc-qualifier></dc-qualifier>
<repeatable>false</repeatable>
<label>Заголовок</label>
<input-type>onebox</input-type>
<hint>Ввести основний заголовок файла</hint>
<required>Заговолок файла обов'язковий !</required>
<!-- <language value-pairs-name="common_iso_languages">true</language> -->
</field>
</row>
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>language</dc-element>
<dc-qualifier>iso</dc-qualifier>
<repeatable>false</repeatable>
<label>Мова</label>
<input-type value-pairs-name="common_iso_languages">dropdown</input-type>
<hint>Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша). Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A) </hint>
<required></required>
</field>
</row>
</form>
</form-definitions>
<!-- form-value-pairs populate dropdown and qualdrop-value lists. -->
<!-- The form-value-pairs element holds child elements named 'value-pairs' -->
<!-- A 'value-pairs' element has a value-pairs-name and a dc-term -->
<!-- attribute. The dc-term attribute specifies which to which Dublin Core -->
<!-- Term this set of value-pairs applies. -->
<!-- Current dc-terms are: identifier-pairs, type-pairs, and -->
<!-- language_iso-pairs. The name attribute matches a name -->
<!-- in the form-map, above. -->
<!-- A value-pair contains one 'pair' for each value displayed in the list -->
<!-- Each pair contains a 'displayed-value' element and a 'stored-value' -->
<!-- element. A UI list displays the displayed-values, but the program -->
<!-- stores the associated stored-values in the database. -->
<form-value-pairs>
<!-- default language order: (from dspace 1.2.1)
"en_US", "en", "es", "de", "fr", "it", "ja", "zh", "other", ""
-->
<value-pairs value-pairs-name="common_iso_languages" dc-term="language_iso">
<pair>
<displayed-value>N/A</displayed-value>
<stored-value></stored-value>
</pair>
<pair>
<displayed-value>Американська (USA)</displayed-value>
<stored-value>en_US</stored-value>
</pair>
<pair>
<displayed-value>Англiйська</displayed-value>
<stored-value>en</stored-value>
</pair>
<pair>
<displayed-value>Iспанська</displayed-value>
<stored-value>es</stored-value>
</pair>
<pair>
<displayed-value>Нецька</displayed-value>
<stored-value>de</stored-value>
</pair>
<pair>
<displayed-value>Французька</displayed-value>
<stored-value>fr</stored-value>
</pair>
<pair>
<displayed-value>алiйська</displayed-value>
<stored-value>it</stored-value>
</pair>
<pair>
<displayed-value>Японська</displayed-value>
<stored-value>ja</stored-value>
</pair>
<pair>
<displayed-value>Китайська</displayed-value>
<stored-value>zh</stored-value>
</pair>
<pair>
<displayed-value>Португальська</displayed-value>
<stored-value>pt</stored-value>
</pair>
<pair>
<displayed-value>Турецька</displayed-value>
<stored-value>tr</stored-value>
</pair>
<pair>
<displayed-value>(Iнша)</displayed-value>
<stored-value>other</stored-value>
</pair>
</value-pairs>
</form-value-pairs>
</input-forms>

View File

@@ -153,6 +153,14 @@ public class IPMatcherTest {
assertFalse(ipMatcher.match("0:0:0:0:0:0:0:1"));
}
@Test
public void testIPv6FullMaskMatching() throws Exception {
final IPMatcher ipMatcher = new IPMatcher("::2/128");
assertTrue(ipMatcher.match("0:0:0:0:0:0:0:2"));
assertFalse(ipMatcher.match("0:0:0:0:0:0:0:1"));
}
@Test
public void testAsteriskMatchingSuccess() throws Exception {

View File

@@ -130,7 +130,7 @@ public class ContextTest extends AbstractUnitTest {
public void testGetCurrentLocale() {
//NOTE: CurrentLocale is not initialized in AbstractUnitTest. So it should be DEFAULTLOCALE
assertThat("testGetCurrentLocale 0", context.getCurrentLocale(), notNullValue());
assertThat("testGetCurrentLocale 1", context.getCurrentLocale(), equalTo(I18nUtil.DEFAULTLOCALE));
assertThat("testGetCurrentLocale 1", context.getCurrentLocale(), equalTo(I18nUtil.getDefaultLocale()));
}
/**

View File

@@ -0,0 +1,127 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.license;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.jdom.Document;
import org.jdom.JDOMException;
/**
* Mock implementation for the Creative commons license connector service.
* This class will return a structure of CC Licenses similar to the CC License API but without having to contact it
*/
public class MockCCLicenseConnectorServiceImpl extends CCLicenseConnectorServiceImpl {
/**
* Retrieves mock CC Licenses for the provided language
* @param language - the language
* @return a map of mocked licenses with the id and the license
*/
public Map<String, CCLicense> retrieveLicenses(String language) {
Map<String, CCLicense> ccLicenses = new HashMap<>();
CCLicense mockLicense1 = createMockLicense(1, new int[]{3, 2, 3});
CCLicense mockLicense2 = createMockLicense(2, new int[]{2});
CCLicense mockLicense3 = createMockLicense(3, new int[]{});
ccLicenses.put(mockLicense1.getLicenseId(), mockLicense1);
ccLicenses.put(mockLicense2.getLicenseId(), mockLicense2);
ccLicenses.put(mockLicense3.getLicenseId(), mockLicense3);
return ccLicenses;
}
private CCLicense createMockLicense(int count, int[] amountOfFieldsAndEnums) {
String licenseId = "license" + count;
String licenseName = "License " + count + " - Name";
List<CCLicenseField> mockLicenseFields = createMockLicenseFields(count, amountOfFieldsAndEnums);
return new CCLicense(licenseId, licenseName, mockLicenseFields);
}
private List<CCLicenseField> createMockLicenseFields(int count, int[] amountOfFieldsAndEnums) {
List<CCLicenseField> ccLicenseFields = new LinkedList<>();
for (int index = 0; index < amountOfFieldsAndEnums.length; index++) {
String licenseFieldId = "license" + count + "-field" + index;
String licenseFieldLabel = "License " + count + " - Field " + index + " - Label";
String licenseFieldDescription = "License " + count + " - Field " + index + " - Description";
List<CCLicenseFieldEnum> mockLicenseFields = createMockLicenseFields(count,
index,
amountOfFieldsAndEnums[index]);
ccLicenseFields.add(new CCLicenseField(licenseFieldId,
licenseFieldLabel,
licenseFieldDescription,
mockLicenseFields));
}
return ccLicenseFields;
}
private List<CCLicenseFieldEnum> createMockLicenseFields(int count, int index, int amountOfEnums) {
List<CCLicenseFieldEnum> ccLicenseFieldEnumList = new LinkedList<>();
for (int i = 0; i < amountOfEnums; i++) {
String enumId = "license" + count + "-field" + index + "-enum" + i;
String enumLabel = "License " + count + " - Field " + index + " - Enum " + i + " - Label";
String enumDescription = "License " + count + " - Field " + index + " - Enum " + i + " - " +
"Description";
ccLicenseFieldEnumList.add(new CCLicenseFieldEnum(enumId, enumLabel, enumDescription));
}
return ccLicenseFieldEnumList;
}
/**
* Retrieve a mock CC License URI
*
* @param licenseId - the ID of the license
* @param language - the language for which to retrieve the full answerMap
* @param answerMap - the answers to the different field questions
* @return the CC License URI
*/
public String retrieveRightsByQuestion(final String licenseId,
final String language,
final Map<String, String> answerMap) {
return "mock-license-uri";
}
/**
* Retrieve a mock license RDF document.
* When the uri contains "invalid", null will be returned to simulate that no document was found for the provided
* URI
*
* @param licenseURI - The license URI for which to retrieve the license RDF document
* @return a mock license RDF document or null when the URI contains invalid
* @throws IOException
*/
public Document retrieveLicenseRDFDoc(String licenseURI) throws IOException {
if (!StringUtils.contains(licenseURI, "invalid")) {
InputStream cclicense = null;
try {
cclicense = getClass().getResourceAsStream("cc-license-rdf.xml");
Document doc = parser.build(cclicense);
return doc;
} catch (JDOMException e) {
throw new RuntimeException(e);
} finally {
if (cclicense != null) {
cclicense.close();
}
}
}
return null;
}
}

View File

@@ -8,7 +8,7 @@
<parent>
<artifactId>dspace-parent</artifactId>
<groupId>org.dspace</groupId>
<version>7.0-beta3-SNAPSHOT</version>
<version>7.0-beta4-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.dspace</groupId>
<artifactId>dspace-parent</artifactId>
<version>7.0-beta3-SNAPSHOT</version>
<version>7.0-beta4-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

View File

@@ -86,7 +86,8 @@ public class LocalURIRedirectionServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// use object's reported handle for redirect (just in case user provided handle had odd characters)
handle = dso.getHandle();
// close the context and send forward.
context.abort();
Negotiator.sendRedirect(response, handle, "", requestedMimeType, true);

View File

@@ -3,7 +3,7 @@
<groupId>org.dspace</groupId>
<artifactId>dspace-rest</artifactId>
<packaging>war</packaging>
<version>7.0-beta3-SNAPSHOT</version>
<version>7.0-beta4-SNAPSHOT</version>
<name>DSpace (Deprecated) REST Webapp</name>
<description>DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED.
Please consider using the REST API in the dspace-server-webapp instead!</description>
@@ -12,7 +12,7 @@
<parent>
<groupId>org.dspace</groupId>
<artifactId>dspace-parent</artifactId>
<version>7.0-beta3-SNAPSHOT</version>
<version>7.0-beta4-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

View File

@@ -274,18 +274,18 @@ public class CollectionsResource extends Resource {
headers, request, context);
items = new ArrayList<Item>();
Iterator<org.dspace.content.Item> dspaceItems = itemService.findByCollection(context, dspaceCollection);
for (int i = 0; (dspaceItems.hasNext()) && (i < (limit + offset)); i++) {
Iterator<org.dspace.content.Item> dspaceItems = itemService.findByCollection(context, dspaceCollection,
limit, offset);
while (dspaceItems.hasNext()) {
org.dspace.content.Item dspaceItem = dspaceItems.next();
if (i >= offset) {
if (itemService.isItemListedForUser(context, dspaceItem)) {
items.add(new Item(dspaceItem, servletContext, expand, context));
writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor,
headers, request, context);
}
}
}
context.complete();
} catch (SQLException e) {

View File

@@ -28,7 +28,7 @@
<!-- Inject the SolrLoggerUsageEventListener into the EventService -->
<!--
Temporarily commented out SolrLoggerUsageEventListener to get REST API working on master.
Temporarily commented out SolrLoggerUsageEventListener to get REST API working.
This should be uncommented again once DS-3815 is resolved.
<bean class="org.dspace.statistics.SolrLoggerUsageEventListener">

View File

@@ -661,6 +661,7 @@ var HtmlUtil = function() {
a.append(val);
a.attr("href", href);
a.attr("target", "_blank");
a.attr("rel", "noopener noreferrer");
return a;
}

View File

@@ -15,7 +15,7 @@
<parent>
<groupId>org.dspace</groupId>
<artifactId>dspace-parent</artifactId>
<version>7.0-beta3-SNAPSHOT</version>
<version>7.0-beta4-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
@@ -460,6 +460,14 @@
<artifactId>solr-cell</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-continuation</artifactId>

View File

@@ -140,11 +140,11 @@ public class Application extends SpringBootServletInitializer {
// Set Access-Control-Allow-Credentials to "true" and specify which origins are valid
// for our Access-Control-Allow-Origin header
.allowCredentials(corsAllowCredentials).allowedOrigins(corsAllowedOrigins)
// Whitelist of request preflight headers allowed to be sent to us from the client
// Allow list of request preflight headers allowed to be sent to us from the client
.allowedHeaders("Authorization", "Content-Type", "X-Requested-With", "accept", "Origin",
"Access-Control-Request-Method", "Access-Control-Request-Headers",
"X-On-Behalf-Of")
// Whitelist of response headers allowed to be sent by us (the server)
// Allow list of response headers allowed to be sent by us (the server)
.exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials",
"Authorization");
}

View File

@@ -16,10 +16,13 @@ import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.converter.EPersonConverter;
import org.dspace.app.rest.link.HalLinkService;
import org.dspace.app.rest.model.AuthenticationStatusRest;
import org.dspace.app.rest.model.AuthenticationTokenRest;
import org.dspace.app.rest.model.AuthnRest;
import org.dspace.app.rest.model.EPersonRest;
import org.dspace.app.rest.model.hateoas.AuthenticationStatusResource;
import org.dspace.app.rest.model.hateoas.AuthenticationTokenResource;
import org.dspace.app.rest.model.hateoas.AuthnResource;
import org.dspace.app.rest.model.wrapper.AuthenticationToken;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.security.RestAuthenticationService;
import org.dspace.app.rest.utils.ContextUtil;
@@ -32,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Link;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@@ -118,6 +122,30 @@ public class AuthenticationRestController implements InitializingBean {
"valid.");
}
/**
* This method will generate a short lived token to be used for bitstream downloads among other things.
*
* curl -v -X POST https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo"
*
* Example:
* <pre>
* {@code
* curl -v -X POST https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo"
* }
* </pre>
* @param request The StandardMultipartHttpServletRequest
* @return The created short lived token
*/
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@RequestMapping(value = "/shortlivedtokens", method = RequestMethod.POST)
public AuthenticationTokenResource shortLivedToken(HttpServletRequest request) {
Projection projection = utils.obtainProjection();
AuthenticationToken shortLivedToken =
restAuthenticationService.getShortLivedAuthenticationToken(ContextUtil.obtainContext(request), request);
AuthenticationTokenRest authenticationTokenRest = converter.toRest(shortLivedToken, projection);
return converter.toResource(authenticationTokenRest);
}
@RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.PUT, RequestMethod.PATCH,
RequestMethod.DELETE })
public ResponseEntity login() {

View File

@@ -34,6 +34,7 @@ import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.core.Utils;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.IndexableObject;
@@ -103,7 +104,8 @@ public class OpenSearchController {
// do some sanity checking
if (!openSearchService.getFormats().contains(format)) {
String err = "Format " + format + " is not supported.";
// Since we are returning error response as HTML, escape any HTML in "format" param
String err = "Format " + Utils.addEntities(format) + " is not supported.";
response.setContentType("text/html");
response.setContentLength(err.length());
response.getWriter().write(err);

View File

@@ -0,0 +1,140 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.exception.DSpaceBadRequestException;
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
import org.dspace.app.rest.model.SubmissionCCLicenseUrlRest;
import org.dspace.app.rest.model.wrapper.SubmissionCCLicenseUrl;
import org.dspace.app.rest.repository.DSpaceRestRepository;
import org.dspace.app.rest.utils.Utils;
import org.dspace.core.Context;
import org.dspace.license.service.CreativeCommonsService;
import org.dspace.services.RequestService;
import org.dspace.utils.DSpace;
import org.springframework.beans.factory.InitializingBean;
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.hateoas.Link;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* This Repository is responsible for handling the CC License URIs.
* It only supports a search method
*/
@Component(SubmissionCCLicenseUrlRest.CATEGORY + "." + SubmissionCCLicenseUrlRest.NAME)
public class SubmissionCCLicenseUrlRepository extends DSpaceRestRepository<SubmissionCCLicenseUrlRest, String>
implements InitializingBean {
@Autowired
protected Utils utils;
@Autowired
protected CreativeCommonsService creativeCommonsService;
@Autowired
protected ConverterService converter;
protected RequestService requestService = new DSpace().getRequestService();
@Autowired
DiscoverableEndpointsService discoverableEndpointsService;
/**
* Retrieves the CC License URI based on the license ID and answers in the field questions, provided as parameters
* to this request
*
* @return the CC License URI as a SubmissionCCLicenseUrlRest
*/
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@SearchRestMethod(name = "rightsByQuestions")
public SubmissionCCLicenseUrlRest findByRightsByQuestions() {
ServletRequest servletRequest = requestService.getCurrentRequest()
.getServletRequest();
Map<String, String[]> requestParameterMap = servletRequest
.getParameterMap();
Map<String, String> parameterMap = new HashMap<>();
String licenseId = servletRequest.getParameter("license");
if (StringUtils.isBlank(licenseId)) {
throw new DSpaceBadRequestException(
"A \"license\" parameter needs to be provided.");
}
// Loop through parameters to find answer parameters, adding them to the parameterMap. Zero or more answers
// may exist, as some CC licenses do not require answers
for (String parameter : requestParameterMap.keySet()) {
if (StringUtils.startsWith(parameter, "answer_")) {
String field = StringUtils.substringAfter(parameter, "answer_");
String answer = "";
if (requestParameterMap.get(parameter).length > 0) {
answer = requestParameterMap.get(parameter)[0];
}
parameterMap.put(field, answer);
}
}
Map<String, String> fullParamMap = creativeCommonsService.retrieveFullAnswerMap(licenseId, parameterMap);
if (fullParamMap == null) {
throw new ResourceNotFoundException("No CC License could be matched on the provided ID: " + licenseId);
}
boolean licenseContainsCorrectInfo = creativeCommonsService.verifyLicenseInformation(licenseId, fullParamMap);
if (!licenseContainsCorrectInfo) {
throw new DSpaceBadRequestException(
"The provided answers do not match the required fields for the provided license.");
}
String licenseUri = creativeCommonsService.retrieveLicenseUri(licenseId, fullParamMap);
SubmissionCCLicenseUrl submissionCCLicenseUrl = new SubmissionCCLicenseUrl(licenseUri, licenseUri);
if (StringUtils.isBlank(licenseUri)) {
throw new ResourceNotFoundException("No CC License URI could be found for ID: " + licenseId);
}
return converter.toRest(submissionCCLicenseUrl, utils.obtainProjection());
}
/**
* The findOne method is not supported in this repository
*/
@PreAuthorize("permitAll()")
public SubmissionCCLicenseUrlRest findOne(final Context context, final String s) {
throw new RepositoryMethodNotImplementedException(SubmissionCCLicenseUrlRest.NAME, "findOne");
}
/**
* The findAll method is not supported in this repository
*/
public Page<SubmissionCCLicenseUrlRest> findAll(final Context context, final Pageable pageable) {
throw new RepositoryMethodNotImplementedException(SubmissionCCLicenseUrlRest.NAME, "findAll");
}
public Class<SubmissionCCLicenseUrlRest> getDomainClass() {
return SubmissionCCLicenseUrlRest.class;
}
@Override
public void afterPropertiesSet() {
discoverableEndpointsService.register(this, Arrays.asList(
new Link("/api/" + SubmissionCCLicenseUrlRest.CATEGORY + "/" +
SubmissionCCLicenseUrlRest.NAME + "/search",
SubmissionCCLicenseUrlRest.NAME + "-search")));
}
}

View File

@@ -0,0 +1,52 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.authorization.impl;
import java.sql.SQLException;
import org.dspace.app.rest.authorization.AuthorizationFeature;
import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation;
import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.core.Context;
import org.dspace.services.RequestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* The EPerson Registration feature. It's able to be used on site objects if the user.registration property is set to
* true. If it's set to true, it'll check if the current context is allowed to set the password.
*/
@Component
@AuthorizationFeatureDocumentation(name = EPersonRegistrationFeature.NAME,
description = "It can be used to register an eperson")
public class EPersonRegistrationFeature implements AuthorizationFeature {
public static final String NAME = "epersonRegistration";
@Autowired
private RequestService requestService;
@Override
public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException {
if (!(object instanceof SiteRest)) {
return false;
}
if (!AuthorizeUtil.authorizeNewAccountRegistration(context,
requestService.getCurrentRequest().getHttpServletRequest())) {
return false;
}
return true;
}
@Override
public String[] getSupportedTypes() {
return new String[] {SiteRest.CATEGORY + "." + SiteRest.NAME};
}
}

View File

@@ -0,0 +1,31 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;
import org.dspace.app.rest.model.AuthenticationTokenRest;
import org.dspace.app.rest.model.wrapper.AuthenticationToken;
import org.dspace.app.rest.projection.Projection;
import org.springframework.stereotype.Component;
/**
* This is the converter from the AuthenticationToken to the REST data model
*/
@Component
public class AuthenticationTokenConverter implements DSpaceConverter<AuthenticationToken, AuthenticationTokenRest> {
@Override
public AuthenticationTokenRest convert(AuthenticationToken modelObject, Projection projection) {
AuthenticationTokenRest token = new AuthenticationTokenRest();
token.setToken(modelObject.getToken());
return token;
}
@Override
public Class<AuthenticationToken> getModelClass() {
return AuthenticationToken.class;
}
}

View File

@@ -0,0 +1,60 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;
import java.util.LinkedList;
import java.util.List;
import org.dspace.app.rest.model.SubmissionCCLicenseFieldRest;
import org.dspace.app.rest.model.SubmissionCCLicenseRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.license.CCLicense;
import org.dspace.license.CCLicenseField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* This converter is responsible for transforming the model representation of an CCLicense to the REST
* representation of an CCLicense and vice versa
**/
@Component
public class SubmissionCCLicenseConverter implements DSpaceConverter<CCLicense, SubmissionCCLicenseRest> {
@Autowired
private ConverterService converter;
/**
* Convert a CCLicense to its REST representation
* @param modelObject - the CCLicense to convert
* @param projection - the projection
* @return the corresponding SubmissionCCLicenseRest object
*/
@Override
public SubmissionCCLicenseRest convert(final CCLicense modelObject, final Projection projection) {
SubmissionCCLicenseRest submissionCCLicenseRest = new SubmissionCCLicenseRest();
submissionCCLicenseRest.setProjection(projection);
submissionCCLicenseRest.setId(modelObject.getLicenseId());
submissionCCLicenseRest.setName(modelObject.getLicenseName());
List<CCLicenseField> ccLicenseFieldList = modelObject.getCcLicenseFieldList();
List<SubmissionCCLicenseFieldRest> submissionCCLicenseFieldRests = new LinkedList<>();
if (ccLicenseFieldList != null) {
for (CCLicenseField ccLicenseField : ccLicenseFieldList) {
submissionCCLicenseFieldRests.add(converter.toRest(ccLicenseField, projection));
}
}
submissionCCLicenseRest.setFields(submissionCCLicenseFieldRests);
return submissionCCLicenseRest;
}
@Override
public Class<CCLicense> getModelClass() {
return CCLicense.class;
}
}

View File

@@ -0,0 +1,62 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;
import java.util.LinkedList;
import java.util.List;
import org.dspace.app.rest.model.SubmissionCCLicenseFieldEnumRest;
import org.dspace.app.rest.model.SubmissionCCLicenseFieldRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.license.CCLicenseField;
import org.dspace.license.CCLicenseFieldEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* This converter is responsible for transforming the model representation of an CCLicenseField to the REST
* representation of an CCLicenseField and vice versa
* The CCLicenseField is a sub component of the CCLicense object
**/
@Component
public class SubmissionCCLicenseFieldConverter
implements DSpaceConverter<CCLicenseField, SubmissionCCLicenseFieldRest> {
@Autowired
private ConverterService converter;
/**
* Convert a CCLicenseField to its REST representation
* @param modelObject - the CCLicenseField to convert
* @param projection - the projection
* @return the corresponding SubmissionCCLicenseFieldRest object
*/
@Override
public SubmissionCCLicenseFieldRest convert(final CCLicenseField modelObject, final Projection projection) {
SubmissionCCLicenseFieldRest submissionCCLicenseFieldRest = new SubmissionCCLicenseFieldRest();
submissionCCLicenseFieldRest.setId(modelObject.getId());
submissionCCLicenseFieldRest.setLabel(modelObject.getLabel());
submissionCCLicenseFieldRest.setDescription(modelObject.getDescription());
List<CCLicenseFieldEnum> fieldEnum = modelObject.getFieldEnum();
List<SubmissionCCLicenseFieldEnumRest> submissionCCLicenseFieldEnumRests = new LinkedList<>();
if (fieldEnum != null) {
for (CCLicenseFieldEnum ccLicenseFieldEnum : fieldEnum) {
submissionCCLicenseFieldEnumRests.add(converter.toRest(ccLicenseFieldEnum, projection));
}
}
submissionCCLicenseFieldRest.setEnums(submissionCCLicenseFieldEnumRests);
return submissionCCLicenseFieldRest;
}
@Override
public Class<CCLicenseField> getModelClass() {
return CCLicenseField.class;
}
}

View File

@@ -0,0 +1,46 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;
import org.dspace.app.rest.model.SubmissionCCLicenseFieldEnumRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.license.CCLicenseFieldEnum;
import org.springframework.stereotype.Component;
/**
* This converter is responsible for transforming the model representation of an CCLicenseFieldEnum to the REST
* representation of an CCLicenseFieldEnum and vice versa
* The CCLicenseFieldEnum is a sub component of the CCLicenseField object
**/
@Component
public class SubmissionCCLicenseFieldEnumConverter
implements DSpaceConverter<CCLicenseFieldEnum, SubmissionCCLicenseFieldEnumRest> {
/**
* Convert a CCLicenseFieldEnum to its REST representation
*
* @param modelObject - the CCLicenseField to convert
* @param projection - the projection
* @return the corresponding SubmissionCCLicenseFieldEnumRest object
*/
@Override
public SubmissionCCLicenseFieldEnumRest convert(final CCLicenseFieldEnum modelObject, final Projection projection) {
SubmissionCCLicenseFieldEnumRest submissionCCLicenseFieldEnumRest = new SubmissionCCLicenseFieldEnumRest();
submissionCCLicenseFieldEnumRest.setId(modelObject.getId());
submissionCCLicenseFieldEnumRest.setLabel(modelObject.getLabel());
submissionCCLicenseFieldEnumRest.setDescription(modelObject.getDescription());
return submissionCCLicenseFieldEnumRest;
}
@Override
public Class<CCLicenseFieldEnum> getModelClass() {
return CCLicenseFieldEnum.class;
}
}

View File

@@ -0,0 +1,44 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;
import org.dspace.app.rest.model.SubmissionCCLicenseUrlRest;
import org.dspace.app.rest.model.wrapper.SubmissionCCLicenseUrl;
import org.dspace.app.rest.projection.Projection;
import org.springframework.stereotype.Component;
/**
* This converter is responsible for transforming a Submission CC License Url String to the REST
* representation SubmissionCCLicenseUrlRest and vice versa
*/
@Component
public class SubmissionCCLicenseUrlConverter
implements DSpaceConverter<SubmissionCCLicenseUrl, SubmissionCCLicenseUrlRest> {
/**
* Convert a Submission CC License Url String to its REST representation
* @param modelObject - the CC License Url object to convert
* @param projection - the projection
* @return the corresponding SubmissionCCLicenseUrlRest object
*/
@Override
public SubmissionCCLicenseUrlRest convert(SubmissionCCLicenseUrl modelObject, Projection projection) {
SubmissionCCLicenseUrlRest submissionCCLicenseUrlRest = new SubmissionCCLicenseUrlRest();
submissionCCLicenseUrlRest.setUrl(modelObject.getUrl());
submissionCCLicenseUrlRest.setId(modelObject.getId());
return submissionCCLicenseUrlRest;
}
@Override
public Class<SubmissionCCLicenseUrl> getModelClass() {
return SubmissionCCLicenseUrl.class;
}
}

View File

@@ -14,6 +14,8 @@ import java.sql.SQLException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.security.RestAuthenticationService;
import org.dspace.authorize.AuthorizeException;
import org.springframework.beans.TypeMismatchException;
@@ -43,6 +45,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep
*/
@ControllerAdvice
public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionHandler {
private static final Logger log = LogManager.getLogger(DSpaceApiExceptionControllerAdvice.class);
@Autowired
private RestAuthenticationService restAuthenticationService;
@@ -51,16 +54,16 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
protected void handleAuthorizeException(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws IOException {
if (restAuthenticationService.hasAuthenticationData(request)) {
sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_FORBIDDEN);
sendErrorResponse(request, response, ex, "Access is denied", HttpServletResponse.SC_FORBIDDEN);
} else {
sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_UNAUTHORIZED);
sendErrorResponse(request, response, ex, "Authentication is required", HttpServletResponse.SC_UNAUTHORIZED);
}
}
@ExceptionHandler({IllegalArgumentException.class, MultipartException.class})
protected void handleWrongRequestException(HttpServletRequest request, HttpServletResponse response,
Exception ex) throws IOException {
sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_BAD_REQUEST);
sendErrorResponse(request, response, ex, "Request is invalid or incorrect", HttpServletResponse.SC_BAD_REQUEST);
}
@ExceptionHandler(SQLException.class)
@@ -74,24 +77,24 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
protected void handleIOException(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws IOException {
sendErrorResponse(request, response, ex,
"An internal read or write operation failed (IO Exception)",
"An internal read or write operation failed",
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(MethodNotAllowedException.class)
protected void methodNotAllowedException(HttpServletRequest request, HttpServletResponse response,
Exception ex) throws IOException {
sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_METHOD_NOT_ALLOWED);
sendErrorResponse(request, response, ex, "Method is not allowed or supported",
HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
@ExceptionHandler( {UnprocessableEntityException.class})
protected void handleUnprocessableEntityException(HttpServletRequest request, HttpServletResponse response,
Exception ex) throws IOException {
//422 is not defined in HttpServletResponse. Its meaning is "Unprocessable Entity".
//Using the value from HttpStatus.
sendErrorResponse(request, response, null,
ex.getMessage(),
"Unprocessable or invalid entity",
HttpStatus.UNPROCESSABLE_ENTITY.value());
}
@@ -100,7 +103,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
throws IOException {
// we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428
sendErrorResponse(request, response, null,
ex.getMessage(),
"A required parameter is invalid",
HttpStatus.BAD_REQUEST.value());
}
@@ -109,7 +112,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
throws IOException {
// we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428
sendErrorResponse(request, response, null,
ex.getMessage(),
"A required parameter is missing",
HttpStatus.BAD_REQUEST.value());
}
@@ -139,7 +142,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
} else {
returnCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}
sendErrorResponse(request, response, ex, "An Exception has occured", returnCode);
sendErrorResponse(request, response, ex, "An exception has occurred", returnCode);
}
@@ -149,6 +152,13 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
//Make sure Spring picks up this exception
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
// For now, just logging server errors.
// We don't want to fill logs with bad/invalid REST API requests.
if (statusCode == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {
// Log the full error and status code
log.error("{} (status:{})", message, statusCode, ex);
}
//Exception properties will be set by org.springframework.boot.web.support.ErrorPageFilter
response.sendError(statusCode, message);
}

View File

@@ -0,0 +1,58 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.filter;
import java.io.IOException;
import java.util.Locale;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.dspace.core.I18nUtil;
import org.springframework.stereotype.Component;
/**
* This filter assures that when the dspace instance supports multiple languages
* they are noted in the Content-Language Header of the response. Where
* appropriate the single endpoint can set the Content-Language header directly
* to note that the response is specific for a language
*
* @author Mykhaylo Boychuk (at 4science.it)
*/
@Component
public class ContentLanguageHeaderResponseFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
Locale[] locales = I18nUtil.getSupportedLocales();
StringBuilder locsStr = new StringBuilder();
for (Locale locale : locales) {
if (locsStr.length() > 0) {
locsStr.append(",");
}
locsStr.append(locale.getLanguage());
}
httpServletResponse.setHeader("Content-Language", locsStr.toString());
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}

View File

@@ -0,0 +1,42 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.link;
import java.util.LinkedList;
import org.dspace.app.rest.AuthenticationRestController;
import org.dspace.app.rest.model.hateoas.AuthenticationTokenResource;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component;
/**
* This class adds the self link to the AuthenticationTokenResource.
*/
@Component
public class AuthenticationTokenHalLinkFactory
extends HalLinkFactory<AuthenticationTokenResource, AuthenticationRestController> {
@Override
protected void addLinks(AuthenticationTokenResource halResource, Pageable pageable, LinkedList<Link> list)
throws Exception {
list.add(buildLink(IanaLinkRelations.SELF.value(), getMethodOn().shortLivedToken(null)));
}
@Override
protected Class<AuthenticationRestController> getControllerClass() {
return AuthenticationRestController.class;
}
@Override
protected Class<AuthenticationTokenResource> getResourceClass() {
return AuthenticationTokenResource.class;
}
}

View File

@@ -0,0 +1,73 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.link.process;
import java.util.LinkedList;
import java.util.Map;
import org.dspace.app.rest.RestResourceController;
import org.dspace.app.rest.link.HalLinkFactory;
import org.dspace.app.rest.model.SubmissionCCLicenseUrlRest;
import org.dspace.app.rest.model.hateoas.SubmissionCCLicenseUrlResource;
import org.dspace.services.RequestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
/**
* This class will provide the SubmissionCCLicenseUrlResource with links
*/
@Component
public class SubmissionCCLicenseUrlResourceHalLinkFactory
extends HalLinkFactory<SubmissionCCLicenseUrlResource, RestResourceController> {
@Autowired
RequestService requestService;
/**
* Add a self link based on the search parameters
*
* @param halResource - The halResource
* @param pageable - The page information
* @param list - The list of present links
* @throws Exception
*/
@Override
protected void addLinks(SubmissionCCLicenseUrlResource halResource, final Pageable pageable,
LinkedList<Link> list)
throws Exception {
halResource.removeLinks();
Map<String, String[]> parameterMap = requestService.getCurrentRequest().getHttpServletRequest()
.getParameterMap();
UriComponentsBuilder uriComponentsBuilder = uriBuilder(getMethodOn().executeSearchMethods(
SubmissionCCLicenseUrlRest.CATEGORY, SubmissionCCLicenseUrlRest.PLURAL, "rightsByQuestions", null, null,
null, null, new LinkedMultiValueMap<>()));
for (String key : parameterMap.keySet()) {
uriComponentsBuilder.queryParam(key, parameterMap.get(key));
}
list.add(buildLink("self", uriComponentsBuilder.build().toUriString()));
}
@Override
protected Class<RestResourceController> getControllerClass() {
return RestResourceController.class;
}
@Override
protected Class<SubmissionCCLicenseUrlResource> getResourceClass() {
return SubmissionCCLicenseUrlResource.class;
}
}

View File

@@ -18,7 +18,7 @@ public class AuthenticationStatusRest extends BaseObjectRest<Integer> {
private boolean authenticated;
public static final String NAME = "status";
public static final String CATEGORY = "authn";
public static final String CATEGORY = RestAddressableModel.AUTHENTICATION;
@Override
public String getCategory() {

View File

@@ -0,0 +1,44 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import org.dspace.app.rest.RestResourceController;
/**
* The authentication token REST HAL Resource. The HAL Resource wraps the REST Resource
* adding support for the links and embedded resources
*/
public class AuthenticationTokenRest extends RestAddressableModel {
public static final String NAME = "shortlivedtoken";
public static final String CATEGORY = RestAddressableModel.AUTHENTICATION;
private String token;
@Override
public String getCategory() {
return CATEGORY;
}
@Override
public Class getController() {
return RestResourceController.class;
}
@Override
public String getType() {
return NAME;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}

View File

@@ -18,7 +18,7 @@ import org.dspace.app.rest.AuthenticationRestController;
public class AuthnRest extends BaseObjectRest<Integer> {
public static final String NAME = "authn";
public static final String CATEGORY = "authn";
public static final String CATEGORY = RestAddressableModel.AUTHENTICATION;
public String getCategory() {
return CATEGORY;

View File

@@ -16,6 +16,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
* @author Jelle Pelgrims (jelle.pelgrims at atmire.com)
*/
@LinksRest(links = {
@LinkRest(
name = BundleRest.ITEM,
method = "getItem"
),
@LinkRest(
name = BundleRest.BITSTREAMS,
method = "getBitstreams"
@@ -30,6 +34,7 @@ public class BundleRest extends DSpaceObjectRest {
public static final String PLURAL_NAME = "bundles";
public static final String CATEGORY = RestAddressableModel.CORE;
public static final String ITEM = "item";
public static final String BITSTREAMS = "bitstreams";
public static final String PRIMARY_BITSTREAM = "primaryBitstream";

View File

@@ -41,7 +41,7 @@ public class EPersonRest extends DSpaceObjectRest {
private boolean requireCertificate = false;
private boolean selfRegistered = false;
private Boolean selfRegistered;
@JsonProperty(access = Access.WRITE_ONLY)
private String password;
@@ -92,11 +92,11 @@ public class EPersonRest extends DSpaceObjectRest {
this.requireCertificate = requireCertificate;
}
public boolean isSelfRegistered() {
public Boolean isSelfRegistered() {
return selfRegistered;
}
public void setSelfRegistered(boolean selfRegistered) {
public void setSelfRegistered(Boolean selfRegistered) {
this.selfRegistered = selfRegistered;
}

View File

@@ -0,0 +1,77 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.dspace.app.rest.RestResourceController;
/**
* This class acts as the REST representation of the RegistrationData model class.
* This class acts as a data holder for the RegistrationResource
* Refer to {@link org.dspace.eperson.RegistrationData} for explanation about the properties
*/
public class RegistrationRest extends RestAddressableModel {
public static final String NAME = "registration";
public static final String NAME_PLURAL = "registrations";
public static final String CATEGORY = EPERSON;
private String email;
private UUID user;
/**
* Generic getter for the email
* @return the email value of this RegisterRest
*/
public String getEmail() {
return email;
}
/**
* Generic setter for the email
* @param email The email to be set on this RegisterRest
*/
public void setEmail(String email) {
this.email = email;
}
/**
* Generic getter for the user
* @return the user value of this RegisterRest
*/
public UUID getUser() {
return user;
}
/**
* Generic setter for the user
* @param user The user to be set on this RegisterRest
*/
public void setUser(UUID user) {
this.user = user;
}
@Override
public String getCategory() {
return CATEGORY;
}
@Override
public Class getController() {
return RestResourceController.class;
}
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getType() {
return NAME;
}
}

View File

@@ -32,6 +32,7 @@ public interface RestModel extends Serializable {
public static final String WORKFLOW = "workflow";
public static final String AUTHORIZATION = "authz";
public static final String VERSIONING = "versioning";
public static final String AUTHENTICATION = "authn";
public String getType();

View File

@@ -0,0 +1,44 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
/**
* This class is the REST representation of the CCLicenseFieldEnum model object and acts as a data sub object
* for the SubmissionCCLicenseFieldRest class.
* Refer to {@link org.dspace.license.CCLicenseFieldEnum} for explanation of the properties
*/
public class SubmissionCCLicenseFieldEnumRest {
private String id;
private String label;
private String description;
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(final String label) {
this.label = label;
}
public String getDescription() {
return description;
}
public void setDescription(final String description) {
this.description = description;
}
}

View File

@@ -0,0 +1,59 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import java.util.List;
/**
* This class is the REST representation of the CCLicenseField model object and acts as a data sub object
* for the SubmissionCCLicenseRest class.
* Refer to {@link org.dspace.license.CCLicenseField} for explanation of the properties
*/
public class SubmissionCCLicenseFieldRest {
private String id;
private String label;
private String description;
private List<SubmissionCCLicenseFieldEnumRest> enums;
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(final String label) {
this.label = label;
}
public String getDescription() {
return description;
}
public void setDescription(final String description) {
this.description = description;
}
public List<SubmissionCCLicenseFieldEnumRest> getEnums() {
return enums;
}
public void setEnums(final List<SubmissionCCLicenseFieldEnumRest> enums) {
this.enums = enums;
}
}

View File

@@ -0,0 +1,74 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.dspace.app.rest.RestResourceController;
/**
* This class is the REST representation of the CCLicense model object and acts as a data object
* for the SubmissionCCLicenseResource class.
* Refer to {@link org.dspace.license.CCLicense} for explanation of the properties
*/
public class SubmissionCCLicenseRest extends BaseObjectRest<String> {
public static final String NAME = "submissioncclicense";
public static final String PLURAL = "submissioncclicenses";
public static final String CATEGORY = RestAddressableModel.CONFIGURATION;
private String id;
private String name;
private List<SubmissionCCLicenseFieldRest> fields;
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public List<SubmissionCCLicenseFieldRest> getFields() {
return fields;
}
public void setFields(final List<SubmissionCCLicenseFieldRest> fields) {
this.fields = fields;
}
@JsonIgnore
@Override
public String getCategory() {
return CATEGORY;
}
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getType() {
return NAME;
}
@Override
@JsonIgnore
public Class getController() {
return RestResourceController.class;
}
}

View File

@@ -0,0 +1,60 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.dspace.app.rest.RestResourceController;
/**
* This class is the REST representation of the CCLicense URL String object and acts as a data object
* for the SubmissionCCLicenseUrlRest class.
*/
public class SubmissionCCLicenseUrlRest extends BaseObjectRest<String> {
public static final String NAME = "submissioncclicenseUrl";
public static final String PLURAL = "submissioncclicenseUrls";
public static final String CATEGORY = RestAddressableModel.CONFIGURATION;
private String url;
@JsonIgnore
@Override
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(final String url) {
this.url = url;
}
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getType() {
return NAME;
}
@Override
public String getCategory() {
return SubmissionCCLicenseUrlRest.CATEGORY;
}
@Override
@JsonIgnore
public Class getController() {
return RestResourceController.class;
}
}

View File

@@ -0,0 +1,20 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.AuthenticationTokenRest;
/**
* Token resource, wraps the AuthenticationToken object
*/
public class AuthenticationTokenResource extends HALResource<AuthenticationTokenRest> {
public AuthenticationTokenResource(AuthenticationTokenRest content) {
super(content);
}
}

View File

@@ -0,0 +1,22 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.RegistrationRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
/**
* Registration HAL Resource. This resource adds the data from the REST object together with embedded objects
* and a set of links if applicable
*/
@RelNameDSpaceResource(RegistrationRest.NAME)
public class RegistrationResource extends HALResource<RegistrationRest> {
public RegistrationResource(RegistrationRest content) {
super(content);
}
}

View File

@@ -0,0 +1,23 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.SubmissionCCLicenseRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
import org.dspace.app.rest.utils.Utils;
/**
* CCLicense HAL Resource. This resource adds the data from the REST object together with embedded objects
* and a set of links if applicable
*/
@RelNameDSpaceResource(SubmissionCCLicenseRest.NAME)
public class SubmissionCCLicenseResource extends DSpaceResource<SubmissionCCLicenseRest> {
public SubmissionCCLicenseResource(SubmissionCCLicenseRest submissionCCLicenseRest, Utils utils) {
super(submissionCCLicenseRest, utils);
}
}

View File

@@ -0,0 +1,23 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.SubmissionCCLicenseUrlRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
import org.dspace.app.rest.utils.Utils;
/**
* SubmissionCCLicenseUrl HAL Resource. This resource adds the data from the REST object together with embedded objects
* and a set of links if applicable
*/
@RelNameDSpaceResource(SubmissionCCLicenseUrlRest.NAME)
public class SubmissionCCLicenseUrlResource extends DSpaceResource<SubmissionCCLicenseUrlRest> {
public SubmissionCCLicenseUrlResource(SubmissionCCLicenseUrlRest submissionCCLicenseUrlRest, Utils utils) {
super(submissionCCLicenseUrlRest, utils);
}
}

View File

@@ -0,0 +1,46 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.step;
import org.dspace.app.rest.model.BitstreamRest;
/**
* Java Bean to expose the section creativecommons representing the CC License during in progress submission.
*/
public class DataCCLicense implements SectionData {
private String uri;
private String rights;
private BitstreamRest file;
public String getUri() {
return uri;
}
public void setUri(final String uri) {
this.uri = uri;
}
public String getRights() {
return rights;
}
public void setRights(final String rights) {
this.rights = rights;
}
public BitstreamRest getFile() {
return file;
}
public void setFile(final BitstreamRest file) {
this.file = file;
}
}

View File

@@ -0,0 +1,28 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.wrapper;
/**
* This class represents an authentication token. It acts as a wrapper for a String object to differentiate between
* actual Strings and AuthenticationToken
*/
public class AuthenticationToken {
private String token;
public AuthenticationToken(String token) {
this.token = token;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}

View File

@@ -0,0 +1,68 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.wrapper;
/**
* This class represents a model implementation for {@link org.dspace.app.rest.model.SubmissionCCLicenseUrlRest}
* This will simply store a url and an id. it'll be used to create an object with these variables out of information
* that came from the back-end. This object will then be used in the
* {@link org.dspace.app.rest.converter.SubmissionCCLicenseUrlConverter} to turn it into its REST object
*/
public class SubmissionCCLicenseUrl {
/**
* The url for ths object
*/
private String url;
/**
* The id for this object
*/
private String id;
/**
* Default constructor with two parameters, url and id
* @param url The url of this object
* @param id The id of this object
*/
public SubmissionCCLicenseUrl(String url, String id) {
this.url = url;
this.id = id;
}
/**
* Generic getter for the url
* @return the url value of this SubmissionCCLicenseUrl
*/
public String getUrl() {
return url;
}
/**
* Generic setter for the url
* @param url The url to be set on this SubmissionCCLicenseUrl
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Generic getter for the id
* @return the id value of this SubmissionCCLicenseUrl
*/
public String getId() {
return id;
}
/**
* Generic setter for the id
* @param id The id to be set on this SubmissionCCLicenseUrl
*/
public void setId(String id) {
this.id = id;
}
}

View File

@@ -7,10 +7,15 @@
*/
package org.dspace.app.rest.repository;
import java.util.Enumeration;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.app.rest.utils.Utils;
import org.dspace.core.Context;
import org.dspace.core.I18nUtil;
import org.dspace.services.RequestService;
import org.dspace.services.model.Request;
import org.dspace.utils.DSpace;
@@ -33,11 +38,47 @@ public abstract class AbstractDSpaceRestRepository {
protected RequestService requestService = new DSpace().getRequestService();
protected Context obtainContext() {
Context context = null;
Request currentRequest = requestService.getCurrentRequest();
return ContextUtil.obtainContext(currentRequest.getServletRequest());
context = ContextUtil.obtainContext(currentRequest.getServletRequest());
Locale currentLocale = getLocale(context, currentRequest);
context.setCurrentLocale(currentLocale);
return context;
}
public RequestService getRequestService() {
return requestService;
}
private Locale getLocale(Context context, Request request) {
Locale userLocale = null;
Locale supportedLocale = null;
// Locales requested from client
String locale = request.getHttpServletRequest().getHeader("Accept-Language");
if (StringUtils.isNotBlank(locale)) {
Enumeration<Locale> locales = request.getHttpServletRequest().getLocales();
if (locales != null) {
while (locales.hasMoreElements()) {
Locale current = locales.nextElement();
if (I18nUtil.isSupportedLocale(current)) {
userLocale = current;
break;
}
}
}
}
if (userLocale == null && context.getCurrentUser() != null) {
String userLanguage = context.getCurrentUser().getLanguage();
if (userLanguage != null) {
userLocale = new Locale(userLanguage);
}
}
if (userLocale == null) {
return I18nUtil.getDefaultLocale();
}
supportedLocale = I18nUtil.getSupportedLocale(userLocale);
return supportedLocale;
}
}

View File

@@ -242,25 +242,27 @@ public class AuthorizationRestRepository extends DSpaceRestRepository<Authorizat
*/
private EPerson getUserFromRequestParameter(Context context, UUID epersonUuid)
throws AuthorizeException, SQLException {
EPerson currUser = context.getCurrentUser();
EPerson user = currUser;
if (epersonUuid != null) {
if (epersonUuid == null) {
// no user is specified in the request parameters, check the permissions for the current user
return currUser;
} else {
// a user is specified in the request parameters
if (currUser == null) {
throw new AuthorizeException("attempt to anonymously access the authorization of the eperson "
+ epersonUuid);
} else {
// an user is specified in the request parameters
if (!authorizeService.isAdmin(context) && !epersonUuid.equals(currUser.getID())) {
} else if (!authorizeService.isAdmin(context) && !epersonUuid.equals(currUser.getID())) {
throw new AuthorizeException("attempt to access the authorization of the eperson " + epersonUuid
+ " only system administrators can see the authorization of other users");
+ " as a non-admin; only system administrators can see the authorization of other users");
}
user = epersonService.find(context, epersonUuid);
return epersonService.find(context, epersonUuid);
}
} else {
// the request asks to check the permission for the anonymous user
user = null;
}
return user;
}
@Override

View File

@@ -0,0 +1,62 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.sql.SQLException;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.rest.model.BundleRest;
import org.dspace.app.rest.model.ItemRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.content.Bundle;
import org.dspace.content.Item;
import org.dspace.content.service.BundleService;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Link repository for "item" subresource of an individual bundle.
*/
@Component(BundleRest.CATEGORY + "." + BundleRest.NAME + "." + BundleRest.ITEM)
public class BundleItemLinkRepository extends AbstractDSpaceRestRepository
implements LinkRestRepository {
@Autowired
BundleService bundleService;
/**
* Get the item where the provided bundle resides in
*/
@PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'READ')")
public ItemRest getItem(@Nullable HttpServletRequest request,
UUID bundleId,
@Nullable Pageable optionalPageable,
Projection projection) {
try {
Context context = obtainContext();
Bundle bundle = bundleService.find(context, bundleId);
if (bundle == null) {
throw new ResourceNotFoundException("No such bundle: " + bundleId);
}
Item item = bundle.getItems().get(0);
if (item == null) {
return null;
}
return converter.toRest(item, projection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -16,22 +16,34 @@ import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.dspace.app.rest.DiscoverableEndpointsService;
import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.authorization.AuthorizationFeatureService;
import org.dspace.app.rest.exception.DSpaceBadRequestException;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.EPersonRest;
import org.dspace.app.rest.model.MetadataRest;
import org.dspace.app.rest.model.MetadataValueRest;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.model.patch.Patch;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.service.SiteService;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.RegistrationData;
import org.dspace.eperson.service.AccountService;
import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.RegistrationDataService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
@@ -46,12 +58,26 @@ import org.springframework.stereotype.Component;
public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, EPersonRest>
implements InitializingBean {
private static final Logger log = Logger.getLogger(EPersonRestRepository.class);
@Autowired
AuthorizeService authorizeService;
@Autowired
DiscoverableEndpointsService discoverableEndpointsService;
@Autowired
private AccountService accountService;
@Autowired
private AuthorizationFeatureService authorizationFeatureService;
@Autowired
private SiteService siteService;
@Autowired
private RegistrationDataService registrationDataService;
private final EPersonService es;
@@ -72,7 +98,23 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
} catch (IOException e1) {
throw new UnprocessableEntityException("error parsing the body... maybe this is not the right error code");
}
String token = req.getParameter("token");
// If a token is available, we'll swap to the execution that is token based
if (StringUtils.isNotBlank(token)) {
try {
return createAndReturn(context, epersonRest, token);
} catch (SQLException e) {
log.error("Something went wrong in the creation of an EPerson with token: " + token, e);
throw new RuntimeException("Something went wrong in the creation of an EPerson with token: " + token);
}
}
// If no token is present, we simply do the admin execution
EPerson eperson = createEPersonFromRestObject(context, epersonRest);
return converter.toRest(eperson, utils.obtainProjection());
}
private EPerson createEPersonFromRestObject(Context context, EPersonRest epersonRest) throws AuthorizeException {
EPerson eperson = null;
try {
eperson = es.create(context);
@@ -90,8 +132,81 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
return eperson;
}
return converter.toRest(eperson, utils.obtainProjection());
/**
* This method will perform checks on whether or not the given Request was valid for the creation of an EPerson
* with a token or not.
* It'll check that the token exists, that the token doesn't yet resolve to an actual eperson already,
* that the email in the given json is equal to the email for the token and that other properties are set to
* what we expect in this creation.
* It'll check if all of those constraints hold true and if we're allowed to register new accounts.
* If this is the case, we'll create an EPerson without any authorization checks and delete the token
* @param context The DSpace context
* @param epersonRest The EPersonRest given to be created
* @param token The token to be used
* @return The EPersonRest after the creation of the EPerson object
* @throws AuthorizeException If something goes wrong
* @throws SQLException If something goes wrong
*/
private EPersonRest createAndReturn(Context context, EPersonRest epersonRest, String token)
throws AuthorizeException, SQLException {
if (!AuthorizeUtil.authorizeNewAccountRegistration(context, requestService
.getCurrentRequest().getHttpServletRequest())) {
throw new DSpaceBadRequestException(
"Registration is disabled, you are not authorized to create a new Authorization");
}
RegistrationData registrationData = registrationDataService.findByToken(context, token);
if (registrationData == null) {
throw new DSpaceBadRequestException("The token given as parameter: " + token + " does not exist" +
" in the database");
}
if (es.findByEmail(context, registrationData.getEmail()) != null) {
throw new DSpaceBadRequestException("The token given already contains an email address that resolves" +
" to an eperson");
}
String emailFromJson = epersonRest.getEmail();
if (StringUtils.isNotBlank(emailFromJson)) {
if (!StringUtils.equalsIgnoreCase(registrationData.getEmail(), emailFromJson)) {
throw new DSpaceBadRequestException("The email resulting from the token does not match the email given"
+ " in the json body. Email from token: " +
registrationData.getEmail() + " email from the json body: "
+ emailFromJson);
}
}
if (epersonRest.isSelfRegistered() != null && !epersonRest.isSelfRegistered()) {
throw new DSpaceBadRequestException("The self registered property cannot be set to false using this method"
+ " with a token");
}
checkRequiredProperties(epersonRest);
// We'll turn off authorisation system because this call isn't admin based as it's token based
context.turnOffAuthorisationSystem();
EPerson ePerson = createEPersonFromRestObject(context, epersonRest);
context.restoreAuthSystemState();
// Restoring authorisation state right after the creation call
accountService.deleteToken(context, token);
if (context.getCurrentUser() == null) {
context.setCurrentUser(ePerson);
}
return converter.toRest(ePerson, utils.obtainProjection());
}
private void checkRequiredProperties(EPersonRest epersonRest) {
MetadataRest metadataRest = epersonRest.getMetadata();
if (metadataRest != null) {
List<MetadataValueRest> epersonFirstName = metadataRest.getMap().get("eperson.firstname");
List<MetadataValueRest> epersonLastName = metadataRest.getMap().get("eperson.lastname");
if (epersonFirstName == null || epersonLastName == null ||
epersonFirstName.isEmpty() || epersonLastName.isEmpty()) {
throw new UnprocessableEntityException("The eperson.firstname and eperson.lastname values need to be " +
"filled in");
}
}
String password = epersonRest.getPassword();
if (!accountService.verifyPasswordStructure(password)) {
throw new DSpaceBadRequestException("The given password is invalid");
}
}
@Override
@@ -175,6 +290,18 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
@PreAuthorize("hasPermission(#uuid, 'EPERSON', #patch)")
protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid,
Patch patch) throws AuthorizeException, SQLException {
if (StringUtils.isNotBlank(request.getParameter("token"))) {
boolean passwordChangeFound = false;
for (Operation operation : patch.getOperations()) {
if (StringUtils.equalsIgnoreCase(operation.getPath(), "/password")) {
passwordChangeFound = true;
}
}
if (!passwordChangeFound) {
throw new AccessDeniedException("Refused to perform the EPerson patch based on a token without " +
"changing the password");
}
}
patchDSpaceObject(apiCategory, model, uuid, patch);
}

Some files were not shown because too many files have changed in this diff Show More