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 ## References
_Add references/links to any related tickets or PRs. These may include:_ _Add references/links to any related issues or PRs. These may include:_
* Link to [JIRA](https://jira.lyrasis.org/projects/DS/summary) ticket(s), if any * Related to [REST Contract](https://github.com/DSpace/Rest7Contract) or an open REST Contract PR, if any
* Link 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
* Link to [Angular issue or PR](https://github.com/DSpace/dspace-angular/issues) related to this PR, if any
## Description ## Description
Short summary of changes (1-2 sentences). 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 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 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). - [ ] 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. - [ ] 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 # 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 # This version is JDK11 compatible
# - tomcat:8-jdk11 # - tomcat:8-jdk11

View File

@@ -1,5 +1,5 @@
# This image will be published as dspace/dspace-cli # 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 # This version is JDK11 compatible
# - openjdk:11 # - openjdk:11

View File

@@ -1,5 +1,5 @@
# This image will be published as dspace/dspace # 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 # This version is JDK11 compatible
# - tomcat:8-jdk11 # - tomcat:8-jdk11

View File

@@ -1,24 +1,24 @@
# DSpace # 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 Releases](https://github.com/DSpace/DSpace/releases) |
[DSpace Wiki](https://wiki.duraspace.org/display/DSPACE/Home) | [DSpace Wiki](https://wiki.lyrasis.org/display/DSPACE/Home) |
[Support](https://wiki.duraspace.org/display/DSPACE/Support) [Support](https://wiki.lyrasis.org/display/DSPACE/Support)
DSpace open source software is a turnkey repository application used by more than 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. 2,000 organizations and institutions worldwide to provide durable access to digital resources.
For more information, visit http://www.dspace.org/ 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: :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 [`master` branch](https://github.com/DSpace/DSpace/tree/master/dspace-server-webapp) of this repository. * 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 being documented at https://github.com/DSpace/Rest7Contract * The REST Contract is at https://github.com/DSpace/Rest7Contract
* DSpace 7 Angular UI work is occurring at https://github.com/DSpace/dspace-angular * 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. **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 / 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: 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) 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. 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. 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: 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) * [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.duraspace.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, 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.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). * [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 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 ## 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: 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-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 * [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) 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 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 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 ## 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 ## Testing
### Running Tests ### Running Tests
By default, in DSpace, Unit Tests and Integration Tests are disabled. However, they are 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`): * 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 ## License
DSpace source code is freely available under a standard [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause). 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> <parent>
<groupId>org.dspace</groupId> <groupId>org.dspace</groupId>
<artifactId>dspace-parent</artifactId> <artifactId>dspace-parent</artifactId>
<version>7.0-beta3-SNAPSHOT</version> <version>7.0-beta4-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
@@ -291,9 +291,20 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.dspace</groupId> <groupId>net.handle</groupId>
<artifactId>handle</artifactId> <artifactId>handle</artifactId>
</dependency> </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 --> <!-- Jetty is needed to run Handle Server -->
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
@@ -471,11 +482,59 @@
<artifactId>solr-cell</artifactId> <artifactId>solr-cell</artifactId>
<version>${solr.client.version}</version> <version>${solr.client.version}</version>
<exclusions> <exclusions>
<!-- Newer version provided in our parent POM --> <!-- Newer versions provided in our parent POM -->
<exclusion> <exclusion>
<groupId>org.ow2.asm</groupId> <groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId> <artifactId>asm-commons</artifactId>
</exclusion> </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> </exclusions>
</dependency> </dependency>

View File

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

View File

@@ -1519,6 +1519,12 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
if (!dir.exists() && !dir.mkdirs()) { if (!dir.exists() && !dir.mkdirs()) {
log.error("Unable to create directory: " + dir.getAbsolutePath()); 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 //Entries could have too many directories, and we need to adjust the sourcedir
// file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|... // file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|...
@@ -1539,9 +1545,16 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
} }
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int len; 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); InputStream in = zf.getInputStream(entry);
BufferedOutputStream out = new BufferedOutputStream( BufferedOutputStream out = new BufferedOutputStream(
new FileOutputStream(zipDir + entry.getName())); new FileOutputStream(outFile));
while ((len = in.read(buffer)) >= 0) { while ((len = in.read(buffer)) >= 0) {
out.write(buffer, 0, len); out.write(buffer, 0, len);
} }

View File

@@ -48,6 +48,9 @@ public class SHERPAResponse {
factory.setValidating(false); factory.setValidating(false);
factory.setIgnoringComments(true); factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(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(); DocumentBuilder db = factory.newDocumentBuilder();
Document inDoc = db.parse(xmlData); Document inDoc = db.parse(xmlData);

View File

@@ -9,7 +9,10 @@ package org.dspace.app.util;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; 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.AuthorizeConfiguration;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.ResourcePolicy;
@@ -26,9 +29,12 @@ import org.dspace.content.service.CollectionService;
import org.dspace.content.service.ItemService; import org.dspace.content.service.ItemService;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group; import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService; 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.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.storedcomponents.CollectionRole; import org.dspace.xmlworkflow.storedcomponents.CollectionRole;
import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService; import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService;
@@ -41,6 +47,7 @@ import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService;
*/ */
public class AuthorizeUtil { public class AuthorizeUtil {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(AuthorizeUtil.class);
/** /**
* Default constructor * Default constructor
*/ */
@@ -605,9 +612,53 @@ public class AuthorizeUtil {
throw new AuthorizeException("not authorized to manage this group"); 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 * This method checks if the community Admin can manage accounts
* *
* @return true if is able * @return true if is able
*/ */
public static boolean canCommunityAdminManageAccounts() { public static boolean canCommunityAdminManageAccounts() {
@@ -625,7 +676,7 @@ public class AuthorizeUtil {
/** /**
* This method checks if the Collection Admin can manage accounts * This method checks if the Collection Admin can manage accounts
* *
* @return true if is able * @return true if is able
*/ */
public static boolean canCollectionAdminManageAccounts() { public static boolean canCollectionAdminManageAccounts() {

View File

@@ -10,6 +10,7 @@ package org.dspace.app.util;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.dspace.core.Utils;
/** /**
* Class representing all DC inputs required for a submission, organized into pages * Class representing all DC inputs required for a submission, organized into pages
* *
@@ -107,9 +108,21 @@ public class DCInputSet {
for (int i = 0; i < inputs.length; i++) { for (int i = 0; i < inputs.length; i++) {
for (int j = 0; j < inputs[i].length; j++) { for (int j = 0; j < inputs[i].length; j++) {
DCInput field = inputs[i][j]; DCInput field = inputs[i][j];
String fullName = field.getFieldName(); // If this is a "qualdrop_value" field, then the full field name is the field + dropdown qualifier
if (fullName.equals(fieldName)) { if (field.getInputType().equals("qualdrop_value")) {
return true; 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;
}
} }
} }
} }

View File

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

View File

@@ -87,13 +87,16 @@ public class IPMatcher {
+ ipSpec); + ipSpec);
} }
int maskBytes = maskBits / 8; for (int i = 0; i < netmask.length; i++) {
for (int i = 0; i < maskBytes; i++) { if (maskBits <= 0) {
netmask[i] = (byte) 0Xff; netmask[i] = 0;
} } else if (maskBits > 8) {
netmask[maskBytes] = (byte) ((byte) 0Xff << 8 - (maskBits % 8)); // FIXME test! netmask[i] = (byte) 0Xff;
for (int i = maskBytes + 1; i < (128 / 8); i++) { } else {
netmask[i] = 0; netmask[i] = (byte) ((byte) 0Xff << 8 - maskBits);
}
maskBits = maskBits - 8;
} }
break; break;
case 1: // No explicit mask: fill the mask with 1s 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 // 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 // load s. If not, check that the unknown's registry's name is not
// being reset. // being reset.
if (unknown == null || unknown.getID() != bitstreamFormat.getID()) { if (unknown == null || !unknown.getID().equals(bitstreamFormat.getID())) {
bitstreamFormat.setShortDescriptionInternal(shortDescription); bitstreamFormat.setShortDescriptionInternal(shortDescription);
} }
} }
@@ -208,7 +208,7 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService {
// Find "unknown" type // Find "unknown" type
BitstreamFormat unknown = findUnknown(context); 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."); throw new IllegalArgumentException("The Unknown bitstream format may not be deleted.");
} }
@@ -270,4 +270,4 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService {
} }
return null; return null;
} }
} }

View File

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

View File

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

View File

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

View File

@@ -272,12 +272,16 @@ public class METSManifest {
// Set validation feature // Set validation feature
if (validate) { if (validate) {
builder.setFeature("http://apache.org/xml/features/validation/schema", true); builder.setFeature("http://apache.org/xml/features/validation/schema", true);
}
// Tell the parser where local copies of schemas are, to speed up // 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) { if (localSchemas.length() > 0) {
builder.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", localSchemas); 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 // Parse the METS file

View File

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

View File

@@ -37,9 +37,6 @@ import org.dspace.services.factory.DSpaceServicesFactory;
public class I18nUtil { public class I18nUtil {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(I18nUtil.class); 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 // delimiters between elements of UNIX/POSIX locale spec, e.g. en_US.UTF-8
private static final String LOCALE_DELIMITERS = " _."; private static final String LOCALE_DELIMITERS = " _.";
@@ -127,7 +124,7 @@ public class I18nUtil {
return parseLocales(locales); return parseLocales(locales);
} else { } else {
Locale[] availableLocales = new Locale[1]; Locale[] availableLocales = new Locale[1];
availableLocales[0] = DEFAULTLOCALE; availableLocales[0] = getDefaultLocale();
return availableLocales; return availableLocales;
} }
} }
@@ -148,7 +145,7 @@ public class I18nUtil {
Locale supportedLocale = null; Locale supportedLocale = null;
String testLocale = ""; String testLocale = "";
if (availableLocales == null) { if (availableLocales == null) {
supportedLocale = DEFAULTLOCALE; supportedLocale = getDefaultLocale();
} else { } else {
if (!locale.getVariant().equals("")) { if (!locale.getVariant().equals("")) {
testLocale = locale.toString(); testLocale = locale.toString();
@@ -188,7 +185,7 @@ public class I18nUtil {
} }
} }
if (!isSupported) { if (!isSupported) {
supportedLocale = DEFAULTLOCALE; supportedLocale = getDefaultLocale();
} }
} }
return supportedLocale; return supportedLocale;
@@ -220,7 +217,7 @@ public class I18nUtil {
* String of the message * String of the message
*/ */
public static String getMessage(String key) { 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) { public static String getMessage(String key, Locale locale) {
if (locale == null) { if (locale == null) {
locale = DEFAULTLOCALE; locale = getDefaultLocale();
} }
ResourceBundle.Control control = ResourceBundle.Control control =
ResourceBundle.Control.getNoFallbackControl( ResourceBundle.Control.getNoFallbackControl(
@@ -384,4 +381,23 @@ public class I18nUtil {
} }
return resultList.toArray(new Locale[resultList.size()]); 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(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true); factory.setNamespaceAware(true);
try { 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(); docBuilder = factory.newDocumentBuilder();
} catch (ParserConfigurationException pcE) { } catch (ParserConfigurationException pcE) {
log.error("caught exception: " + 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: * Citation enabled globally (all citable bitstreams will get "watermarked") modules/disseminate-citation:
* enable_globally * enable_globally
* OR * OR
* The container is this object is whitelist enabled. * The container is this object is "allow list" enabled.
* - community: modules/disseminate-citation: enabled_communities * - community: modules/disseminate-citation: enabled_communities
* - collection: modules/disseminate-citation: enabled_collections * - collection: modules/disseminate-citation: enabled_collections
* AND * AND

View File

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

View File

@@ -189,7 +189,8 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
return false; return false;
// special, everyone is member of group 0 (anonymous) // 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; return true;
} else { } else {

View File

@@ -46,4 +46,11 @@ public interface AccountService {
public void deleteToken(Context context, String token) public void deleteToken(Context context, String token)
throws SQLException; 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) { if (maxActiveThreads == 0) {
maxActiveThreads = 3; 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) { if (minHeartbeat == 0) {
minHeartbeat = 30000; 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) { if (maxHeartbeat == 0) {
maxHeartbeat = 3600000; maxHeartbeat = 3600000;
} }

View File

@@ -83,7 +83,7 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor<OMEl
* @param query query string * @param query query string
* @param prefixToNamespaceMapping metadata prefix to namespace mapping * @param prefixToNamespaceMapping metadata prefix to namespace mapping
* @param field * @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, public SimpleXpathMetadatumContributor(String query, Map<String, String> prefixToNamespaceMapping,
MetadataFieldConfig field) { MetadataFieldConfig field) {

View File

@@ -8,6 +8,8 @@
package org.dspace.license; package org.dspace.license;
import java.util.List;
/** /**
* @author wbossons * @author wbossons
*/ */
@@ -15,17 +17,17 @@ public class CCLicense {
private String licenseName; private String licenseName;
private String licenseId; private String licenseId;
private int order = 0; private List<CCLicenseField> ccLicenseFieldList;
public CCLicense() { public CCLicense() {
super(); super();
} }
public CCLicense(String licenseId, String licenseName, int order) { public CCLicense(String licenseId, String licenseName, List<CCLicenseField> ccLicenseFieldList) {
super(); super();
this.licenseId = licenseId; this.licenseId = licenseId;
this.licenseName = licenseName; this.licenseName = licenseName;
this.order = order; this.ccLicenseFieldList = ccLicenseFieldList;
} }
public String getLicenseName() { public String getLicenseName() {
@@ -44,13 +46,19 @@ public class CCLicense {
this.licenseId = licenseId; 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; package org.dspace.license;
import java.util.HashMap; import java.util.List;
import java.util.Map;
/** /**
* Wrapper class for representation of a license field declaration. * Wrapper class for representation of a license field declaration.
@@ -22,7 +21,7 @@ public class CCLicenseField {
private String description = ""; private String description = "";
private String type = ""; private String type = "";
private HashMap fieldEnum = null; private List<CCLicenseFieldEnum> fieldEnum = null;
/** /**
* Construct a new LicenseField class. Note that after construction, * 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 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. * @param label The label to use when generating the user interface.
*/ */
public CCLicenseField(String id, String label) { public CCLicenseField(String id, String label, String description, List<CCLicenseFieldEnum> fieldEnum) {
super();
this.fieldEnum = new HashMap();
this.id = id; this.id = id;
this.label = label; this.label = label;
this.description = description;
this.fieldEnum = fieldEnum;
} }
/** /**
@@ -90,16 +87,12 @@ public class CCLicenseField {
} }
/** /**
* @return Returns an instance implementing the Map interface; * Returns the list of enums of this field
* the instance contains a mapping from identifiers to * @return the list of enums of this field
* labels for the enumeration values.
* @see Map
*/ */
public Map<String, String> getEnum() { public List<CCLicenseFieldEnum> getFieldEnum() {
return this.fieldEnum; 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.InputStream;
import java.io.StringWriter; import java.io.StringWriter;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.xml.transform.Templates; import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerException;
@@ -82,9 +85,18 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
protected BundleService bundleService; protected BundleService bundleService;
@Autowired(required = true) @Autowired(required = true)
protected ItemService itemService; protected ItemService itemService;
@Autowired
protected CCLicenseConnectorService ccLicenseConnectorService;
protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); 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() { protected CreativeCommonsServiceImpl() {
} }
@@ -101,10 +113,14 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
System.setProperty("http.proxyPort", proxyPort); System.setProperty("http.proxyPort", proxyPort);
} }
ccLicenses = new HashMap<>();
defaultLanguage = configurationService.getProperty("cc.license.locale", "en");
jurisdiction = configurationService.getProperty("cc.license.jurisdiction", "");
try { try {
templates = TransformerFactory.newInstance().newTemplates( templates = TransformerFactory.newInstance().newTemplates(
new StreamSource(CreativeCommonsServiceImpl.class new StreamSource(CreativeCommonsServiceImpl.class
.getResourceAsStream("CreativeCommons.xsl"))); .getResourceAsStream("CreativeCommons.xsl")));
} catch (TransformerConfigurationException e) { } catch (TransformerConfigurationException e) {
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e);
} }
@@ -112,15 +128,10 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
} }
@Override
public boolean isEnabled() {
return true;
}
// create the CC bundle if it doesn't exist // create the CC bundle if it doesn't exist
// If it does, remove it and create a new one. // If it does, remove it and create a new one.
protected Bundle getCcBundle(Context context, Item item) protected Bundle getCcBundle(Context context, Item item)
throws SQLException, AuthorizeException, IOException { throws SQLException, AuthorizeException, IOException {
List<Bundle> bundles = itemService.getBundles(item, CC_BUNDLE_NAME); List<Bundle> bundles = itemService.getBundles(item, CC_BUNDLE_NAME);
if ((bundles.size() > 0) && (bundles.get(0) != null)) { if ((bundles.size() > 0) && (bundles.get(0) != null)) {
@@ -131,8 +142,8 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
@Override @Override
public void setLicenseRDF(Context context, Item item, String licenseRdf) public void setLicenseRDF(Context context, Item item, String licenseRdf)
throws SQLException, IOException, throws SQLException, IOException,
AuthorizeException { AuthorizeException {
Bundle bundle = getCcBundle(context, item); Bundle bundle = getCcBundle(context, item);
// set the format // set the format
BitstreamFormat bs_rdf_format = bitstreamFormatService.findByShortDescription(context, "RDF XML"); BitstreamFormat bs_rdf_format = bitstreamFormatService.findByShortDescription(context, "RDF XML");
@@ -144,7 +155,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
@Override @Override
public void setLicense(Context context, Item item, public void setLicense(Context context, Item item,
InputStream licenseStm, String mimeType) InputStream licenseStm, String mimeType)
throws SQLException, IOException, AuthorizeException { throws SQLException, IOException, AuthorizeException {
Bundle bundle = getCcBundle(context, item); Bundle bundle = getCcBundle(context, item);
// set the format // set the format
@@ -160,17 +171,26 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
Bitstream bs = bitstreamService.create(context, bundle, licenseStm); Bitstream bs = bitstreamService.create(context, bundle, licenseStm);
bs.setSource(context, CC_BS_SOURCE); bs.setSource(context, CC_BS_SOURCE);
bs.setName(context, (mimeType != null && bs.setName(context, (mimeType != null &&
(mimeType.equalsIgnoreCase("text/xml") || (mimeType.equalsIgnoreCase("text/xml") ||
mimeType.equalsIgnoreCase("text/rdf"))) ? mimeType.equalsIgnoreCase("text/rdf"))) ?
BSN_LICENSE_RDF : BSN_LICENSE_TEXT); BSN_LICENSE_RDF : BSN_LICENSE_TEXT);
bs.setFormat(context, bs_format); bs.setFormat(context, bs_format);
bitstreamService.update(context, bs); bitstreamService.update(context, bs);
} }
/**
* 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 @Override
public void removeLicense(Context context, Item item) public void removeLicenseFile(Context context, Item item)
throws SQLException, IOException, AuthorizeException { throws SQLException, IOException, AuthorizeException {
// remove CC license bundle if one exists // remove CC license bundle if one exists
List<Bundle> bundles = itemService.getBundles(item, CC_BUNDLE_NAME); List<Bundle> bundles = itemService.getBundles(item, CC_BUNDLE_NAME);
@@ -179,66 +199,74 @@ 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 @Override
public Bitstream getLicenseRdfBitstream(Item item) throws SQLException, public Bitstream getLicenseRdfBitstream(Item item) throws SQLException,
IOException, AuthorizeException { IOException, AuthorizeException {
return getBitstream(item, BSN_LICENSE_RDF); return getBitstream(item, BSN_LICENSE_RDF);
} }
@Deprecated @Deprecated
@Override @Override
public Bitstream getLicenseTextBitstream(Item item) throws SQLException, public Bitstream getLicenseTextBitstream(Item item) throws SQLException,
IOException, AuthorizeException { IOException, AuthorizeException {
return getBitstream(item, BSN_LICENSE_TEXT); return getBitstream(item, BSN_LICENSE_TEXT);
} }
@Override @Override
public String getLicenseURL(Context context, Item item) throws SQLException, IOException, AuthorizeException { 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)) { if (StringUtils.isNotBlank(licenseUri)) {
return licenseUri; return getLicenseURI(item);
} }
// JSPUI backward compatibility see https://jira.duraspace.org/browse/DS-2604 // JSPUI backward compatibility see https://jira.duraspace.org/browse/DS-2604
return getStringFromBitstream(context, item, BSN_LICENSE_URL); 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 @Override
public String fetchLicenseRDF(Document license) { public String fetchLicenseRDF(Document license) {
StringWriter result = new StringWriter(); StringWriter result = new StringWriter();
try { try {
templates.newTransformer().transform( templates.newTransformer().transform(
new JDOMSource(license), new JDOMSource(license),
new StreamResult(result) new StreamResult(result)
); );
} catch (TransformerException e) { } catch (TransformerException e) {
throw new IllegalStateException(e.getMessage(), e); throw new IllegalStateException(e.getMessage(), e);
@@ -267,7 +295,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
*/ */
protected void setBitstreamFromBytes(Context context, Item item, Bundle bundle, protected void setBitstreamFromBytes(Context context, Item item, Bundle bundle,
String bitstream_name, BitstreamFormat format, byte[] bytes) String bitstream_name, BitstreamFormat format, byte[] bytes)
throws SQLException, IOException, AuthorizeException { throws SQLException, IOException, AuthorizeException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Bitstream bs = bitstreamService.create(context, bundle, bais); Bitstream bs = bitstreamService.create(context, bundle, bais);
@@ -297,7 +325,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
*/ */
protected String getStringFromBitstream(Context context, Item item, protected String getStringFromBitstream(Context context, Item item,
String bitstream_name) throws SQLException, IOException, String bitstream_name) throws SQLException, IOException,
AuthorizeException { AuthorizeException {
byte[] bytes = getBytesFromBitstream(context, item, bitstream_name); byte[] bytes = getBytesFromBitstream(context, item, bitstream_name);
if (bytes == null) { if (bytes == null) {
@@ -320,7 +348,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
* to perform a particular action. * to perform a particular action.
*/ */
protected Bitstream getBitstream(Item item, String bitstream_name) protected Bitstream getBitstream(Item item, String bitstream_name)
throws SQLException, IOException, AuthorizeException { throws SQLException, IOException, AuthorizeException {
Bundle cc_bundle = null; Bundle cc_bundle = null;
// look for the CC bundle // look for the CC bundle
@@ -342,7 +370,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
} }
protected byte[] getBytesFromBitstream(Context context, Item item, String bitstream_name) protected byte[] getBytesFromBitstream(Context context, Item item, String bitstream_name)
throws SQLException, IOException, AuthorizeException { throws SQLException, IOException, AuthorizeException {
Bitstream bs = getBitstream(item, bitstream_name); Bitstream bs = getBitstream(item, bitstream_name);
// no such bitstream // no such bitstream
@@ -361,26 +389,322 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
* Returns a metadata field handle for given field Id * Returns a metadata field handle for given field Id
*/ */
@Override @Override
public LicenseMetadataValue getCCField(String fieldId) { public String getCCField(String fieldId) {
return new LicenseMetadataValue(configurationService.getProperty("cc.license." + 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 @Override
public void removeLicense(Context context, LicenseMetadataValue uriField, public void removeLicense(Context context, Item item)
LicenseMetadataValue nameField, Item item) throws AuthorizeException, IOException, SQLException {
throws AuthorizeException, IOException, SQLException {
String uriField = getCCField("uri");
String nameField = getCCField("name");
String licenseUri = itemService.getMetadata(item, uriField);
// only remove any previous licenses // only remove any previous licenses
String licenseUri = uriField.ccItemValue(item);
if (licenseUri != null) { if (licenseUri != null) {
uriField.removeItemValue(context, item, licenseUri); removeLicenseField(context, item, uriField);
if (configurationService.getBooleanProperty("cc.submit.setname")) { if (configurationService.getBooleanProperty("cc.submit.setname")) {
String licenseName = nameField.keyedItemValue(item, licenseUri); removeLicenseField(context, item, nameField);
nameField.removeItemValue(context, item, licenseName);
} }
if (configurationService.getBooleanProperty("cc.submit.addbitstream")) { if (configurationService.getBooleanProperty("cc.submit.addbitstream")) {
removeLicense(context, item); 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream; import org.dspace.content.Bitstream;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.license.LicenseMetadataValue; import org.dspace.license.CCLicense;
import org.jdom.Document; import org.jdom.Document;
/** /**
@@ -29,13 +31,6 @@ public interface CreativeCommonsService {
public static final String CC_BUNDLE_NAME = "CC-LICENSE"; public static final String CC_BUNDLE_NAME = "CC-LICENSE";
/**
* Simple accessor for enabling of CC
*
* @return is CC enabled?
*/
public boolean isEnabled();
/** /**
* setLicenseRDF * setLicenseRDF
* *
@@ -50,7 +45,7 @@ public interface CreativeCommonsService {
* to perform a particular action. * to perform a particular action.
*/ */
public void setLicenseRDF(Context context, Item item, String licenseRdf) public void setLicenseRDF(Context context, Item item, String licenseRdf)
throws SQLException, IOException, AuthorizeException; throws SQLException, IOException, AuthorizeException;
/** /**
@@ -72,19 +67,40 @@ public interface CreativeCommonsService {
*/ */
public void setLicense(Context context, Item item, public void setLicense(Context context, Item item,
InputStream licenseStm, String mimeType) InputStream licenseStm, String mimeType)
throws SQLException, IOException, AuthorizeException; throws SQLException, IOException, AuthorizeException;
public void removeLicense(Context context, Item item) /**
throws SQLException, IOException, AuthorizeException; * 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) public String getLicenseURL(Context context, Item item)
throws SQLException, IOException, AuthorizeException; 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. * Get Creative Commons license RDF, returning Bitstream object.
@@ -97,7 +113,7 @@ public interface CreativeCommonsService {
* to perform a particular action. * to perform a particular action.
*/ */
public Bitstream getLicenseRdfBitstream(Item item) public Bitstream getLicenseRdfBitstream(Item item)
throws SQLException, IOException, AuthorizeException; throws SQLException, IOException, AuthorizeException;
/** /**
* Get Creative Commons license Text, returning Bitstream object. * Get Creative Commons license Text, returning Bitstream object.
@@ -112,7 +128,7 @@ public interface CreativeCommonsService {
* is no longer stored (see https://jira.duraspace.org/browse/DS-2604) * is no longer stored (see https://jira.duraspace.org/browse/DS-2604)
*/ */
public Bitstream getLicenseTextBitstream(Item item) public Bitstream getLicenseTextBitstream(Item item)
throws SQLException, IOException, AuthorizeException; throws SQLException, IOException, AuthorizeException;
/** /**
* Get a few license-specific properties. We expect these to be cached at * Get a few license-specific properties. We expect these to be cached at
@@ -121,7 +137,7 @@ public interface CreativeCommonsService {
* @param fieldId name of the property. * @param fieldId name of the property.
* @return its value. * @return its value.
*/ */
public LicenseMetadataValue getCCField(String fieldId); public String getCCField(String fieldId);
/** /**
* Apply same transformation on the document to retrieve only the most * 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 * Remove license information, delete also the bitstream
* *
* @param context - DSpace Context * @param context - DSpace Context
* @param uriField - the metadata field for license uri
* @param nameField - the metadata field for license name
* @param item - the item * @param item - the item
* @throws AuthorizeException Exception indicating the current user of the context does not have permission * @throws AuthorizeException Exception indicating the current user of the context does not have permission
* to perform a particular action. * to perform a particular action.
* @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. * @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 SQLException An exception that provides information on a database access error or other errors.
*/ */
public void removeLicense(Context context, LicenseMetadataValue uriField, public void removeLicense(Context context, Item item)
LicenseMetadataValue nameField, Item item) throws AuthorizeException, IOException, SQLException;
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 javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.validator.routines.UrlValidator;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.rdf.RDFUtil; import org.dspace.rdf.RDFUtil;
import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.services.factory.DSpaceServicesFactory;
@@ -197,6 +198,7 @@ public class Negotiator {
if (extraPathInfo == null) { if (extraPathInfo == null) {
extraPathInfo = ""; extraPathInfo = "";
} }
UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS);
StringBuilder urlBuilder = new StringBuilder(); StringBuilder urlBuilder = new StringBuilder();
String lang = null; String lang = null;
@@ -256,12 +258,15 @@ public class Negotiator {
urlBuilder.append(handle).append("/").append(extraPathInfo); urlBuilder.append(handle).append("/").append(extraPathInfo);
} }
String url = urlBuilder.toString(); String url = urlBuilder.toString();
if (urlValidator.isValid(url)) {
log.debug("Will forward to '" + url + "'."); log.debug("Will forward to '" + url + "'.");
response.setStatus(HttpServletResponse.SC_SEE_OTHER); response.setStatus(HttpServletResponse.SC_SEE_OTHER);
response.setHeader("Location", url); response.setHeader("Location", url);
response.flushBuffer(); response.flushBuffer();
return true; return true;
} else {
throw new IOException("Invalid URL '" + url + "', cannot redirect.");
}
} }
// currently we cannot serve statistics as rdf // currently we cannot serve statistics as rdf
@@ -287,10 +292,14 @@ public class Negotiator {
urlBuilder.append("/handle/").append(handle); urlBuilder.append("/handle/").append(handle);
urlBuilder.append("/").append(lang); urlBuilder.append("/").append(lang);
String url = urlBuilder.toString(); String url = urlBuilder.toString();
log.debug("Will forward to '" + url + "'."); if (urlValidator.isValid(url)) {
response.setStatus(HttpServletResponse.SC_SEE_OTHER); log.debug("Will forward to '" + url + "'.");
response.setHeader("Location", url); response.setStatus(HttpServletResponse.SC_SEE_OTHER);
response.flushBuffer(); response.setHeader("Location", url);
return true; 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.setValidating(false);
factory.setIgnoringComments(true); factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(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(); DocumentBuilder db = factory.newDocumentBuilder();
Document inDoc = db.parse(response.getEntity().getContent()); Document inDoc = db.parse(response.getEntity().getContent());
@@ -178,6 +181,9 @@ public class CiNiiService {
factory.setValidating(false); factory.setValidating(false);
factory.setIgnoringComments(true); factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(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(); DocumentBuilder db = factory.newDocumentBuilder();
Document inDoc = db.parse(response.getEntity().getContent()); Document inDoc = db.parse(response.getEntity().getContent());

View File

@@ -99,6 +99,9 @@ public class CrossRefService {
factory.setValidating(false); factory.setValidating(false);
factory.setIgnoringComments(true); factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(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 DocumentBuilder db = factory
.newDocumentBuilder(); .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; return true;
} }
Class<?> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(o); Class<?> objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(o);
if (getClass() != objClass) { if (!getClass().equals(objClass)) {
return false; return false;
} }
final Version that = (Version) o; final Version that = (Version) o;
if (this.getID() != that.getID()) { if (!this.getID().equals(that.getID())) {
return false; return false;
} }

View File

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

View File

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

View File

@@ -120,3 +120,7 @@ rest.properties.exposed = configuration.not.existing
configuration.not.exposed = secret_value configuration.not.exposed = secret_value
configuration.exposed.single.value = public_value configuration.exposed.single.value = public_value
configuration.exposed.array.value = public_value_1, public_value_2 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")); 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 @Test
public void testAsteriskMatchingSuccess() throws Exception { public void testAsteriskMatchingSuccess() throws Exception {

View File

@@ -130,7 +130,7 @@ public class ContextTest extends AbstractUnitTest {
public void testGetCurrentLocale() { public void testGetCurrentLocale() {
//NOTE: CurrentLocale is not initialized in AbstractUnitTest. So it should be DEFAULTLOCALE //NOTE: CurrentLocale is not initialized in AbstractUnitTest. So it should be DEFAULTLOCALE
assertThat("testGetCurrentLocale 0", context.getCurrentLocale(), notNullValue()); 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> <parent>
<artifactId>dspace-parent</artifactId> <artifactId>dspace-parent</artifactId>
<groupId>org.dspace</groupId> <groupId>org.dspace</groupId>
<version>7.0-beta3-SNAPSHOT</version> <version>7.0-beta4-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>

View File

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

View File

@@ -86,7 +86,8 @@ public class LocalURIRedirectionServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_NOT_FOUND); response.sendError(HttpServletResponse.SC_NOT_FOUND);
return; 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. // close the context and send forward.
context.abort(); context.abort();
Negotiator.sendRedirect(response, handle, "", requestedMimeType, true); Negotiator.sendRedirect(response, handle, "", requestedMimeType, true);

View File

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

View File

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

View File

@@ -28,7 +28,7 @@
<!-- Inject the SolrLoggerUsageEventListener into the EventService --> <!-- 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. This should be uncommented again once DS-3815 is resolved.
<bean class="org.dspace.statistics.SolrLoggerUsageEventListener"> <bean class="org.dspace.statistics.SolrLoggerUsageEventListener">

View File

@@ -15,7 +15,7 @@ var Report = function() {
this.ROOTPATH = "/xmlui/handle/" this.ROOTPATH = "/xmlui/handle/"
//this.ROOTPATH = "/jspui/handle/" //this.ROOTPATH = "/jspui/handle/"
//this.ROOTPATH = "/handle/" //this.ROOTPATH = "/handle/"
//Indicate if Password Authentication is supported //Indicate if Password Authentication is supported
this.makeAuthLink = function(){return false;}; this.makeAuthLink = function(){return false;};
@@ -27,34 +27,34 @@ var Report = function() {
this.getId = function(obj) { this.getId = function(obj) {
return obj.uuid; return obj.uuid;
} }
//Override this method is sortable.js has been included //Override this method is sortable.js has been included
this.hasSorttable = function() { this.hasSorttable = function() {
return false; return false;
} }
this.getDefaultParameters = function(){ this.getDefaultParameters = function(){
return {}; return {};
} }
this.getCurrentParameters = function(){ this.getCurrentParameters = function(){
return {}; return {};
} }
this.saveUrl = function() { this.saveUrl = function() {
this.myReportParameters.saveAsUrl(this.getCurrentParameters()); this.myReportParameters.saveAsUrl(this.getCurrentParameters());
} }
this.getLoginPayload = function() { this.getLoginPayload = function() {
//Placeholder to allow a customized report to prompt for email/password //Placeholder to allow a customized report to prompt for email/password
//If not enabled, the authenticaton callback will be called immediately //If not enabled, the authenticaton callback will be called immediately
var email = $("#restemail").val(); var email = $("#restemail").val();
var pass = $("#restpass").val(); var pass = $("#restpass").val();
if (email == "" || pass == "") { if (email == "" || pass == "") {
return undefined; return undefined;
} else if (email == null || pass == null) { } else if (email == null || pass == null) {
return undefined; return undefined;
} else { } else {
return {email: email, password: pass}; return {email: email, password: pass};
} }
} }
this.getLangSuffix = function(){ this.getLangSuffix = function(){
@@ -82,15 +82,15 @@ var Report = function() {
className: 'spinner', // The CSS class to assign to the spinner className: 'spinner', // The CSS class to assign to the spinner
zIndex: 2e9, // The z-index (defaults to 2000000000) zIndex: 2e9, // The z-index (defaults to 2000000000)
top: '400px', // Top position relative to parent top: '400px', // Top position relative to parent
left: '600px' // Left position relative to parent left: '600px' // Left position relative to parent
}); });
this.displayItems = function(itemsTitle, offset, limit, total, funcdec, funcinc) { this.displayItems = function(itemsTitle, offset, limit, total, funcdec, funcinc) {
var count = $("#itemtable tr.data").length; var count = $("#itemtable tr.data").length;
var last = offset + limit; var last = offset + limit;
var suff = ""; var suff = "";
if (total == null) { if (total == null) {
last = offset + count; last = offset + count;
suff = (count == limit) ? " of " + last + "+ " : " of " + last; suff = (count == limit) ? " of " + last + "+ " : " of " + last;
@@ -102,7 +102,7 @@ var Report = function() {
suff = " of " + total; suff = " of " + total;
} }
suff += " unfiltered; displaying " + count + " filtered" ; suff += " unfiltered; displaying " + count + " filtered" ;
itemsTitle += " (" + (offset+1) + " - " + last + suff + ")"; itemsTitle += " (" + (offset+1) + " - " + last + suff + ")";
$("#prev,#next").attr("disabled",true); $("#prev,#next").attr("disabled",true);
$("#itemdiv h3").text(itemsTitle); $("#itemdiv h3").text(itemsTitle);
@@ -110,34 +110,34 @@ var Report = function() {
if (offset > 0) $("#prev").attr("disabled", false); if (offset > 0) $("#prev").attr("disabled", false);
$("#prev").off("click").on("click", funcdec); $("#prev").off("click").on("click", funcdec);
//in case of filters, always allow next //in case of filters, always allow next
if (total == null) { if (total == null) {
$("#next").attr("disabled", false); $("#next").attr("disabled", false);
} else if (offset + limit < total) { } else if (offset + limit < total) {
$("#next").attr("disabled", false); $("#next").attr("disabled", false);
$("#exlimit").addClass("red"); $("#exlimit").addClass("red");
} else if (limit == total) { } else if (limit == total) {
//total may only be accurate to one page //total may only be accurate to one page
$("#next").attr("disabled", false); $("#next").attr("disabled", false);
$("#exlimit").addClass("red"); $("#exlimit").addClass("red");
} }
$("#next").off("click").on("click", funcinc); $("#next").off("click").on("click", funcinc);
} }
this.myReportParameters = undefined; this.myReportParameters = undefined;
this.myFilters = undefined; this.myFilters = undefined;
this.myMetadataFields = undefined; this.myMetadataFields = undefined;
this.initMetadataFields = function() { this.initMetadataFields = function() {
this.myMetadataFields = new MetadataFields(self); this.myMetadataFields = new MetadataFields(self);
this.myMetadataFields.load(); this.myMetadataFields.load();
} }
this.initBitstreamFields = function() { this.initBitstreamFields = function() {
this.myBitstreamFields = new BitstreamFields(self); this.myBitstreamFields = new BitstreamFields(self);
this.myBitstreamFields.load(); this.myBitstreamFields.load();
} }
this.baseInit = function() { this.baseInit = function() {
this.myReportParameters = new ReportParameters( this.myReportParameters = new ReportParameters(
this.getDefaultParameters(), this.getDefaultParameters(),
@@ -173,13 +173,13 @@ var Report = function() {
}); });
return itemdata; return itemdata;
} }
this.export = function(rows) { this.export = function(rows) {
var itemdata = "data:text/csv;charset=utf-8," + this.makeCsv(rows); var itemdata = "data:text/csv;charset=utf-8," + this.makeCsv(rows);
var encodedUri = encodeURI(itemdata); var encodedUri = encodeURI(itemdata);
window.open(encodedUri); window.open(encodedUri);
} }
//this is meant to be overridden for each report //this is meant to be overridden for each report
this.exportCol = function(colnum, col) { this.exportCol = function(colnum, col) {
var data = ""; var data = "";
@@ -187,7 +187,7 @@ var Report = function() {
data += self.exportCell(col); data += self.exportCell(col);
return data; return data;
} }
this.exportCell = function(col) { this.exportCell = function(col) {
data = "\""; data = "\"";
$(col).contents().each(function(i, node){ $(col).contents().each(function(i, node){
@@ -198,16 +198,16 @@ var Report = function() {
if ($(node).is("div:not(:last-child)")) { if ($(node).is("div:not(:last-child)")) {
data += "||"; data += "||";
} }
} }
}); });
data += "\""; data += "\"";
return data; return data;
} }
this.init = function() { this.init = function() {
this.baseInit(); this.baseInit();
} }
} }
var Auth = function(report) { var Auth = function(report) {
@@ -242,17 +242,17 @@ var Auth = function(report) {
self.authStat(); self.authStat();
self.callback(); self.callback();
} }
}); });
} }
this.verifyShibLogin = function() { this.verifyShibLogin = function() {
var self = this; var self = this;
$.ajax({ $.ajax({
url: "/rest/shibboleth-login", url: "/rest/shibboleth-login",
success: self.authStat success: self.authStat
}); });
} }
this.authStat = function() { this.authStat = function() {
var self = this; var self = this;
$.ajax({ $.ajax({
@@ -264,7 +264,7 @@ var Auth = function(report) {
success: function(data) { success: function(data) {
var user = ""; var user = "";
if (data.email != undefined) { if (data.email != undefined) {
user = data.email; user = data.email;
} else { } else {
user = "You are not logged in. Some items may be excluded from reports."; user = "You are not logged in. Some items may be excluded from reports.";
} }
@@ -279,10 +279,10 @@ var Auth = function(report) {
if (data.email == undefined && self.report.makeShibLink()) { if (data.email == undefined && self.report.makeShibLink()) {
self.verifyShibLogin(); self.verifyShibLogin();
} }
} }
}); });
} }
this.logout = function() { this.logout = function() {
var self = this; var self = this;
$.ajax({ $.ajax({
@@ -293,7 +293,7 @@ var Auth = function(report) {
complete: function(xhr, status) { complete: function(xhr, status) {
self.authStat(); self.authStat();
} }
}); });
} }
this.getHeaders = function() { this.getHeaders = function() {
var HEADERS = {}; var HEADERS = {};
@@ -314,14 +314,14 @@ var ReportParameters = function(defaultParams, prmstr) {
var field = tmparr[0]; var field = tmparr[0];
var val = decodeURIComponent(tmparr[1]); var val = decodeURIComponent(tmparr[1]);
var pval = this.params[field]; var pval = this.params[field];
if ($.isArray(pval)) { if ($.isArray(pval)) {
pval[pval.length] = val; pval[pval.length] = val;
} else { } else {
this.params[field] = val; this.params[field] = val;
} }
} }
$("#limit").val(this.params.limit); $("#limit").val(this.params.limit);
$("#offset").val(this.params.offset); $("#offset").val(this.params.offset);
this.limit = this.params.limit; this.limit = this.params.limit;
this.offset = this.params.offset; this.offset = this.params.offset;
@@ -350,11 +350,11 @@ var ReportParameters = function(defaultParams, prmstr) {
var lim = $("#limit").val(); var lim = $("#limit").val();
if ($.isNumeric(val) && $.isNumeric(lim)) { if ($.isNumeric(val) && $.isNumeric(lim)) {
if (increment) { if (increment) {
$("#offset").val(this.getNextOffset()); $("#offset").val(this.getNextOffset());
} else { } else {
$("#offset").val(this.getPrevOffset()); $("#offset").val(this.getPrevOffset());
} }
} }
} }
this.saveAsUrl = function(params) { this.saveAsUrl = function(params) {
@@ -381,7 +381,7 @@ var Filters = function() {
$("#filter-reload").attr("disabled", false); $("#filter-reload").attr("disabled", false);
} }
); );
$.getJSON( $.getJSON(
"/rest/filters", "/rest/filters",
function(data){ function(data){
@@ -444,13 +444,13 @@ var Filters = function() {
list = "none"; list = "none";
} }
return list; return list;
} }
} }
var MetadataFields = function(report) { var MetadataFields = function(report) {
this.metadataSchemas = undefined; this.metadataSchemas = undefined;
var self = this; var self = this;
this.load = function(){ this.load = function(){
$.ajax({ $.ajax({
url: "/rest/registries/schema", url: "/rest/registries/schema",
@@ -463,15 +463,15 @@ var MetadataFields = function(report) {
}, },
complete: function(xhr, status) { complete: function(xhr, status) {
} }
}); });
} }
this.initFields = function(data, report) { this.initFields = function(data, report) {
var params = report.myReportParameters.params; var params = report.myReportParameters.params;
self.metadataSchemas = data; self.metadataSchemas = data;
self.drawShowFields(params["show_fields[]"]); self.drawShowFields(params["show_fields[]"]);
} }
this.getShowFields = function(){ this.getShowFields = function(){
var val = $("#show-fields select").val(); var val = $("#show-fields select").val();
return val == null ? Array() : val; return val == null ? Array() : val;
@@ -497,7 +497,7 @@ var MetadataFields = function(report) {
}); });
}); });
} }
this.initQueries = function(){}; this.initQueries = function(){};
} }
@@ -508,15 +508,15 @@ var BitstreamFields = function(report) {
} }
this.map = [ this.map = [
{ {
key: "original-file-names", key: "original-file-names",
name: "Original File Names", name: "Original File Names",
ftest: self.isOriginal, ftest: self.isOriginal,
fval: function(bit) { fval: function(bit) {
return bit.name; return bit.name;
} }
}, },
{ {
key: "mime-type", key: "mime-type",
name: "Mime Type", name: "Mime Type",
ftest: self.isOriginal, ftest: self.isOriginal,
fval: function(bit) { fval: function(bit) {
@@ -524,7 +524,7 @@ var BitstreamFields = function(report) {
} }
}, },
{ {
key: "bitstream-format", key: "bitstream-format",
name: "Bitstream Format", name: "Bitstream Format",
ftest: self.isOriginal, ftest: self.isOriginal,
fval: function(bit) { fval: function(bit) {
@@ -532,7 +532,7 @@ var BitstreamFields = function(report) {
} }
}, },
{ {
key: "bitstream-description", key: "bitstream-description",
name: "Bitstream Description", name: "Bitstream Description",
ftest: self.isOriginal, ftest: self.isOriginal,
fval: function(bit) { fval: function(bit) {
@@ -540,7 +540,7 @@ var BitstreamFields = function(report) {
} }
}, },
{ {
key: "bitstream-size", key: "bitstream-size",
name: "Bitstream Size", name: "Bitstream Size",
ftest: self.isOriginal, ftest: self.isOriginal,
fval: function(bit) { fval: function(bit) {
@@ -548,18 +548,18 @@ var BitstreamFields = function(report) {
} }
}, },
{ {
key: "bitstream-checksum", key: "bitstream-checksum",
name: "MD5 Checksum", name: "MD5 Checksum",
ftest: self.isOriginal, ftest: self.isOriginal,
fval: function(bit) { fval: function(bit) {
if (bit.checkSum.checkSumAlgorithm === "MD5") { if (bit.checkSum.checkSumAlgorithm === "MD5") {
return bit.checkSum.value; return bit.checkSum.value;
} }
return ""; return "";
} }
}, },
]; ];
this.load = function(){ this.load = function(){
self.initFields(report); self.initFields(report);
} }
@@ -568,7 +568,7 @@ var BitstreamFields = function(report) {
var params = report.myReportParameters.params; var params = report.myReportParameters.params;
self.drawShowFieldsBits(params["show_fields_bits[]"]); self.drawShowFieldsBits(params["show_fields_bits[]"]);
}; };
this.hasBitstreamFields = function() { this.hasBitstreamFields = function() {
return self.getShowFieldsBits() != null; return self.getShowFieldsBits() != null;
} }
@@ -576,20 +576,20 @@ var BitstreamFields = function(report) {
var val = $("#show-fields-bits select").val(); var val = $("#show-fields-bits select").val();
return val == null ? Array() : val; return val == null ? Array() : val;
} }
this.drawShowFieldsBits = function(pfieldsBits) { this.drawShowFieldsBits = function(pfieldsBits) {
var sel = $("<select name='show_fields_bits'/>"); var sel = $("<select name='show_fields_bits'/>");
sel.attr("multiple","true").attr("size","8").appendTo("#show-fields-bits"); sel.attr("multiple","true").attr("size","8").appendTo("#show-fields-bits");
for(var i=0; i<this.map.length; i++) { for(var i=0; i<this.map.length; i++) {
var opt = report.myHtmlUtil.addOpt(sel, this.map[i].name, this.map[i].key); var opt = report.myHtmlUtil.addOpt(sel, this.map[i].name, this.map[i].key);
if (pfieldsBits != null) { if (pfieldsBits != null) {
opt.attr("selected", pfieldsBits[this.map[i].key] != undefined ? "Y" : null); opt.attr("selected", pfieldsBits[this.map[i].key] != undefined ? "Y" : null);
} }
sel.append(opt); sel.append(opt);
} }
} }
this.getKeyText = function(key, item, bitfields) { this.getKeyText = function(key, item, bitfields) {
var ret = []; var ret = [];
if (bitfields == null || item.bitstreams == null) { if (bitfields == null || item.bitstreams == null) {
@@ -608,20 +608,20 @@ var BitstreamFields = function(report) {
if (mapval == null) { if (mapval == null) {
return ret; return ret;
} }
$.each(item.bitstreams, function(colindex, bitstream) { $.each(item.bitstreams, function(colindex, bitstream) {
if (mapval.ftest(bitstream)) { if (mapval.ftest(bitstream)) {
var val = mapval.fval(bitstream); var val = mapval.fval(bitstream);
if (val != null) { if (val != null) {
if (isNaN(val) || ret.length == 0) { if (isNaN(val) || ret.length == 0) {
ret.push(val); ret.push(val);
} else { } else {
ret[0] += val; ret[0] += val;
} }
} }
} }
}); });
return ret; return ret;
} }
} }
@@ -661,6 +661,7 @@ var HtmlUtil = function() {
a.append(val); a.append(val);
a.attr("href", href); a.attr("href", href);
a.attr("target", "_blank"); a.attr("target", "_blank");
a.attr("rel", "noopener noreferrer");
return a; return a;
} }
@@ -704,7 +705,7 @@ var CommunitySelector = function(report, parent, paramCollSel) {
var collSel = $("<select/>").attr("id","collSel").attr("name","collSel").attr("multiple", true).attr("size",15); var collSel = $("<select/>").attr("id","collSel").attr("name","collSel").attr("multiple", true).attr("size",15);
parent.append(collSel); parent.append(collSel);
report.myHtmlUtil.addOpt(collSel, "Whole Repository", ""); report.myHtmlUtil.addOpt(collSel, "Whole Repository", "");
$.ajax({ $.ajax({
url: "/rest/hierarchy", url: "/rest/hierarchy",
dataType: "json", dataType: "json",
@@ -722,7 +723,7 @@ var CommunitySelector = function(report, parent, paramCollSel) {
}, },
complete: function(xhr, status) { complete: function(xhr, status) {
} }
}); });
this.addCommLabel = function(collSel, comm, indent, paramCollSel) { this.addCommLabel = function(collSel, comm, indent, paramCollSel) {
var prefix = ""; var prefix = "";
@@ -738,12 +739,12 @@ var CommunitySelector = function(report, parent, paramCollSel) {
opt.attr("selected", true); opt.attr("selected", true);
} }
}); });
}); });
} }
if (comm.community != null) { if (comm.community != null) {
$.each(comm.community, function(index, scomm) { $.each(comm.community, function(index, scomm) {
self.addCommLabel(collSel, scomm, indent + 1, paramCollSel); self.addCommLabel(collSel, scomm, indent + 1, paramCollSel);
}); });
} }
} }
} }

View File

@@ -15,7 +15,7 @@
<parent> <parent>
<groupId>org.dspace</groupId> <groupId>org.dspace</groupId>
<artifactId>dspace-parent</artifactId> <artifactId>dspace-parent</artifactId>
<version>7.0-beta3-SNAPSHOT</version> <version>7.0-beta4-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
@@ -460,6 +460,14 @@
<artifactId>solr-cell</artifactId> <artifactId>solr-cell</artifactId>
<scope>test</scope> <scope>test</scope>
<exclusions> <exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
<exclusion> <exclusion>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-continuation</artifactId> <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 // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid
// for our Access-Control-Allow-Origin header // for our Access-Control-Allow-Origin header
.allowCredentials(corsAllowCredentials).allowedOrigins(corsAllowedOrigins) .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", .allowedHeaders("Authorization", "Content-Type", "X-Requested-With", "accept", "Origin",
"Access-Control-Request-Method", "Access-Control-Request-Headers", "Access-Control-Request-Method", "Access-Control-Request-Headers",
"X-On-Behalf-Of") "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", .exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials",
"Authorization"); "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.converter.EPersonConverter;
import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.link.HalLinkService;
import org.dspace.app.rest.model.AuthenticationStatusRest; 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.AuthnRest;
import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.EPersonRest;
import org.dspace.app.rest.model.hateoas.AuthenticationStatusResource; 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.hateoas.AuthnResource;
import org.dspace.app.rest.model.wrapper.AuthenticationToken;
import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.security.RestAuthenticationService; import org.dspace.app.rest.security.RestAuthenticationService;
import org.dspace.app.rest.utils.ContextUtil; 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.hateoas.Link;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
@@ -118,6 +122,30 @@ public class AuthenticationRestController implements InitializingBean {
"valid."); "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, @RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.PUT, RequestMethod.PATCH,
RequestMethod.DELETE }) RequestMethod.DELETE })
public ResponseEntity login() { public ResponseEntity login() {

View File

@@ -34,6 +34,7 @@ import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService; import org.dspace.content.service.CommunityService;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.core.LogManager; import org.dspace.core.LogManager;
import org.dspace.core.Utils;
import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.IndexableObject; import org.dspace.discovery.IndexableObject;
@@ -103,7 +104,8 @@ public class OpenSearchController {
// do some sanity checking // do some sanity checking
if (!openSearchService.getFormats().contains(format)) { 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.setContentType("text/html");
response.setContentLength(err.length()); response.setContentLength(err.length());
response.getWriter().write(err); 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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; 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.app.rest.security.RestAuthenticationService;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.springframework.beans.TypeMismatchException; import org.springframework.beans.TypeMismatchException;
@@ -43,6 +45,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep
*/ */
@ControllerAdvice @ControllerAdvice
public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionHandler { public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionHandler {
private static final Logger log = LogManager.getLogger(DSpaceApiExceptionControllerAdvice.class);
@Autowired @Autowired
private RestAuthenticationService restAuthenticationService; private RestAuthenticationService restAuthenticationService;
@@ -51,16 +54,16 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
protected void handleAuthorizeException(HttpServletRequest request, HttpServletResponse response, Exception ex) protected void handleAuthorizeException(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws IOException { throws IOException {
if (restAuthenticationService.hasAuthenticationData(request)) { if (restAuthenticationService.hasAuthenticationData(request)) {
sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_FORBIDDEN); sendErrorResponse(request, response, ex, "Access is denied", HttpServletResponse.SC_FORBIDDEN);
} else { } 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}) @ExceptionHandler({IllegalArgumentException.class, MultipartException.class})
protected void handleWrongRequestException(HttpServletRequest request, HttpServletResponse response, protected void handleWrongRequestException(HttpServletRequest request, HttpServletResponse response,
Exception ex) throws IOException { 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) @ExceptionHandler(SQLException.class)
@@ -74,24 +77,24 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
protected void handleIOException(HttpServletRequest request, HttpServletResponse response, Exception ex) protected void handleIOException(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws IOException { throws IOException {
sendErrorResponse(request, response, ex, 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); HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} }
@ExceptionHandler(MethodNotAllowedException.class) @ExceptionHandler(MethodNotAllowedException.class)
protected void methodNotAllowedException(HttpServletRequest request, HttpServletResponse response, protected void methodNotAllowedException(HttpServletRequest request, HttpServletResponse response,
Exception ex) throws IOException { 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}) @ExceptionHandler( {UnprocessableEntityException.class})
protected void handleUnprocessableEntityException(HttpServletRequest request, HttpServletResponse response, protected void handleUnprocessableEntityException(HttpServletRequest request, HttpServletResponse response,
Exception ex) throws IOException { Exception ex) throws IOException {
//422 is not defined in HttpServletResponse. Its meaning is "Unprocessable Entity". //422 is not defined in HttpServletResponse. Its meaning is "Unprocessable Entity".
//Using the value from HttpStatus. //Using the value from HttpStatus.
sendErrorResponse(request, response, null, sendErrorResponse(request, response, null,
ex.getMessage(), "Unprocessable or invalid entity",
HttpStatus.UNPROCESSABLE_ENTITY.value()); HttpStatus.UNPROCESSABLE_ENTITY.value());
} }
@@ -100,7 +103,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
throws IOException { throws IOException {
// we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428
sendErrorResponse(request, response, null, sendErrorResponse(request, response, null,
ex.getMessage(), "A required parameter is invalid",
HttpStatus.BAD_REQUEST.value()); HttpStatus.BAD_REQUEST.value());
} }
@@ -109,7 +112,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
throws IOException { throws IOException {
// we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428
sendErrorResponse(request, response, null, sendErrorResponse(request, response, null,
ex.getMessage(), "A required parameter is missing",
HttpStatus.BAD_REQUEST.value()); HttpStatus.BAD_REQUEST.value());
} }
@@ -139,7 +142,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
} else { } else {
returnCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 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 //Make sure Spring picks up this exception
request.setAttribute(EXCEPTION_ATTRIBUTE, ex); 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 //Exception properties will be set by org.springframework.boot.web.support.ErrorPageFilter
response.sendError(statusCode, message); 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; private boolean authenticated;
public static final String NAME = "status"; public static final String NAME = "status";
public static final String CATEGORY = "authn"; public static final String CATEGORY = RestAddressableModel.AUTHENTICATION;
@Override @Override
public String getCategory() { 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 class AuthnRest extends BaseObjectRest<Integer> {
public static final String NAME = "authn"; public static final String NAME = "authn";
public static final String CATEGORY = "authn"; public static final String CATEGORY = RestAddressableModel.AUTHENTICATION;
public String getCategory() { public String getCategory() {
return CATEGORY; return CATEGORY;

View File

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

View File

@@ -41,7 +41,7 @@ public class EPersonRest extends DSpaceObjectRest {
private boolean requireCertificate = false; private boolean requireCertificate = false;
private boolean selfRegistered = false; private Boolean selfRegistered;
@JsonProperty(access = Access.WRITE_ONLY) @JsonProperty(access = Access.WRITE_ONLY)
private String password; private String password;
@@ -92,11 +92,11 @@ public class EPersonRest extends DSpaceObjectRest {
this.requireCertificate = requireCertificate; this.requireCertificate = requireCertificate;
} }
public boolean isSelfRegistered() { public Boolean isSelfRegistered() {
return selfRegistered; return selfRegistered;
} }
public void setSelfRegistered(boolean selfRegistered) { public void setSelfRegistered(Boolean selfRegistered) {
this.selfRegistered = 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 WORKFLOW = "workflow";
public static final String AUTHORIZATION = "authz"; public static final String AUTHORIZATION = "authz";
public static final String VERSIONING = "versioning"; public static final String VERSIONING = "versioning";
public static final String AUTHENTICATION = "authn";
public String getType(); 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; 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.converter.ConverterService;
import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.app.rest.utils.Utils; import org.dspace.app.rest.utils.Utils;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.core.I18nUtil;
import org.dspace.services.RequestService; import org.dspace.services.RequestService;
import org.dspace.services.model.Request; import org.dspace.services.model.Request;
import org.dspace.utils.DSpace; import org.dspace.utils.DSpace;
@@ -33,11 +38,47 @@ public abstract class AbstractDSpaceRestRepository {
protected RequestService requestService = new DSpace().getRequestService(); protected RequestService requestService = new DSpace().getRequestService();
protected Context obtainContext() { protected Context obtainContext() {
Context context = null;
Request currentRequest = requestService.getCurrentRequest(); 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() { public RequestService getRequestService() {
return requestService; 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) private EPerson getUserFromRequestParameter(Context context, UUID epersonUuid)
throws AuthorizeException, SQLException { throws AuthorizeException, SQLException {
EPerson currUser = context.getCurrentUser(); 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) { if (currUser == null) {
throw new AuthorizeException("attempt to anonymously access the authorization of the eperson " throw new AuthorizeException("attempt to anonymously access the authorization of the eperson "
+ epersonUuid); + epersonUuid);
} else {
// an user is specified in the request parameters } else if (!authorizeService.isAdmin(context) && !epersonUuid.equals(currUser.getID())) {
if (!authorizeService.isAdmin(context) && !epersonUuid.equals(currUser.getID())) { throw new AuthorizeException("attempt to access the authorization of the eperson " + epersonUuid
throw new AuthorizeException("attempt to access the authorization of the eperson " + epersonUuid + " as a non-admin; only system administrators can see the authorization of other users");
+ " only system administrators can see the authorization of other users");
}
user = epersonService.find(context, epersonUuid);
} }
} else {
// the request asks to check the permission for the anonymous user return epersonService.find(context, epersonUuid);
user = null;
} }
return user;
} }
@Override @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 com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.DiscoverableEndpointsService;
import org.dspace.app.rest.Parameter; import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod; 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.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.EPersonRest; 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.rest.model.patch.Patch;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.service.SiteService;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.eperson.EPerson; 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.EPersonService;
import org.dspace.eperson.service.RegistrationDataService;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link; import org.springframework.hateoas.Link;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -46,12 +58,26 @@ import org.springframework.stereotype.Component;
public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, EPersonRest> public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, EPersonRest>
implements InitializingBean { implements InitializingBean {
private static final Logger log = Logger.getLogger(EPersonRestRepository.class);
@Autowired @Autowired
AuthorizeService authorizeService; AuthorizeService authorizeService;
@Autowired @Autowired
DiscoverableEndpointsService discoverableEndpointsService; DiscoverableEndpointsService discoverableEndpointsService;
@Autowired
private AccountService accountService;
@Autowired
private AuthorizationFeatureService authorizationFeatureService;
@Autowired
private SiteService siteService;
@Autowired
private RegistrationDataService registrationDataService;
private final EPersonService es; private final EPersonService es;
@@ -72,7 +98,23 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
} catch (IOException e1) { } catch (IOException e1) {
throw new UnprocessableEntityException("error parsing the body... maybe this is not the right error code"); 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; EPerson eperson = null;
try { try {
eperson = es.create(context); eperson = es.create(context);
@@ -90,8 +132,81 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException(e.getMessage(), 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 @Override
@@ -175,6 +290,18 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
@PreAuthorize("hasPermission(#uuid, 'EPERSON', #patch)") @PreAuthorize("hasPermission(#uuid, 'EPERSON', #patch)")
protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid, protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid,
Patch patch) throws AuthorizeException, SQLException { 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); patchDSpaceObject(apiCategory, model, uuid, patch);
} }

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