Skip to content

Commit

Permalink
UUID with URL special character support.
Browse files Browse the repository at this point in the history
eg. info:doi:10.24396/ORDAR-56 or http://dada.moo/ORDAR-56

In order to support UUID with character like / or ; in it, you need
to disable default Spring HTTP Firewall behaviour which consider those characters unsecure.
Error would look like "URL contained a potentially malicious String "%2F""
For this uncomment the firewall configuration in config-security-core.xml and adjust StrictHttpFirewall configuration.
Also uncomment the firewall property of filterChainProxy.

Client side already URL encode UUIDs and with this, spring will not
decode path before matching URL (which would cause issue with request mapping)

By default, this is not active.
  • Loading branch information
fxprunayre committed Jun 9, 2021
1 parent b4f5fc4 commit a561bd7
Show file tree
Hide file tree
Showing 37 changed files with 114 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public FilesystemStoreResource(String metadataUuid,

@Override
public String getId() {
return UrlEscapers.urlFragmentEscaper().escape(metadataUuid) +
return UrlEscapers.urlPathSegmentEscaper().escape(metadataUuid) +
"/attachments/" +
UrlEscapers.urlFragmentEscaper().escape(filename);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ public void deleteMapserver(
// @Authorization(value = "basicAuth")
// }
)
@RequestMapping(value = "/{mapserverId}/records/{metadataUuid}",
@RequestMapping(value = "/{mapserverId}/records/{metadataUuid:.+}",
method = RequestMethod.GET,
produces = {
MediaType.TEXT_PLAIN_VALUE
Expand Down Expand Up @@ -405,7 +405,7 @@ public String getMapserverResource(
// @Authorization(value = "basicAuth")
// }
)
@RequestMapping(value = "/{mapserverId}/records/{metadataUuid}",
@RequestMapping(value = "/{mapserverId}/records/{metadataUuid:.+}",
method = RequestMethod.PUT,
produces = {
MediaType.TEXT_PLAIN_VALUE
Expand Down Expand Up @@ -460,7 +460,7 @@ public String publishMapserverResource(
// })
)
@RequestMapping(
value = "/{mapserverId}/records/{metadataUuid}",
value = "/{mapserverId}/records/{metadataUuid:.+}",
method = RequestMethod.DELETE,
produces = {
MediaType.TEXT_PLAIN_VALUE
Expand Down
6 changes: 3 additions & 3 deletions services/src/main/java/org/fao/geonet/api/records/DoiApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public class DoiApi {
@io.swagger.v3.oas.annotations.Operation(
summary = "Check that a record can be submitted to DataCite for DOI creation. " +
"DataCite requires some fields to be populated.")
@RequestMapping(value = "/{metadataUuid}/doi/checkPreConditions",
@RequestMapping(value = "/{metadataUuid:.+}/doi/checkPreConditions",
method = RequestMethod.GET,
produces = {
MediaType.APPLICATION_JSON_VALUE
Expand Down Expand Up @@ -104,7 +104,7 @@ ResponseEntity<Map<String, Boolean>> checkDoiStatus(

@io.swagger.v3.oas.annotations.Operation(
summary = "Submit a record to the Datacite metadata store in order to create a DOI.")
@RequestMapping(value = "/{metadataUuid}/doi",
@RequestMapping(value = "/{metadataUuid:.+}/doi",
method = RequestMethod.PUT,
produces = {
MediaType.APPLICATION_JSON_VALUE
Expand Down Expand Up @@ -141,7 +141,7 @@ ResponseEntity<Map<String, String>> createDoi(

@io.swagger.v3.oas.annotations.Operation(
summary = "Remove a DOI (this is not recommended, DOI are supposed to be persistent once created. This is mainly here for testing).")
@RequestMapping(value = "/{metadataUuid}/doi",
@RequestMapping(value = "/{metadataUuid:.+}/doi",
method = RequestMethod.DELETE,
produces = {
MediaType.APPLICATION_JSON_VALUE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public class InspireValidationApi {
@io.swagger.v3.oas.annotations.Operation(
summary = "Get test suites available.",
description = "TG13, TG2, ...")
@RequestMapping(value = "/{metadataUuid}/validate/inspire/testsuites",
@RequestMapping(value = "/{metadataUuid:.+}/validate/inspire/testsuites",
method = RequestMethod.GET,
produces = {
MediaType.APPLICATION_JSON_VALUE
Expand Down Expand Up @@ -153,7 +153,7 @@ Map<String, String[]> getTestSuites(
+ "An INSPIRE endpoint must be configured in Settings. "
+ "This activates an asyncronous process, this method does not return any report. "
+ "This method returns an id to be used to get the report.")
@RequestMapping(value = "/{metadataUuid}/validate/inspire",
@RequestMapping(value = "/{metadataUuid:.+}/validate/inspire",
method = RequestMethod.PUT,
produces = {
MediaType.TEXT_PLAIN_VALUE
Expand Down
20 changes: 11 additions & 9 deletions services/src/main/java/org/fao/geonet/api/records/MetadataApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import jeeves.services.ReadWriteController;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.fao.geonet.Constants;
import org.fao.geonet.api.API;
import org.fao.geonet.api.ApiParams;
import org.fao.geonet.api.ApiUtils;
Expand Down Expand Up @@ -65,6 +66,7 @@

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
Expand Down Expand Up @@ -160,22 +162,22 @@ public String getRecord(
List<String> accept = Arrays.asList(acceptHeader.split(","));

String defaultFormatter = "xsl-view";
String encodedUuid = URLEncoder.encode(metadataUuid, Constants.ENCODING);
if (accept.contains(MediaType.TEXT_HTML_VALUE)
|| accept.contains(MediaType.APPLICATION_XHTML_XML_VALUE)
|| accept.contains("application/pdf")) {
return "forward:" + (metadataUuid + "/formatters/" + defaultFormatter);
return "redirect:" + (encodedUuid + "/formatters/" + defaultFormatter);
} else if (accept.contains(MediaType.APPLICATION_XML_VALUE)
|| accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
return "forward:" + (metadataUuid + "/formatters/xml");
return "redirect:" + (encodedUuid + "/formatters/xml");
} else if (accept.contains("application/zip")
|| accept.contains(MEF_V1_ACCEPT_TYPE)
|| accept.contains(MEF_V2_ACCEPT_TYPE)) {
return "forward:" + (metadataUuid + "/formatters/zip");
return "redirect:" + (encodedUuid + "/formatters/zip");
} else {
// FIXME this else is never reached because any of the accepted medias match one of the previous if conditions.
response.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_XHTML_XML_VALUE);
//response.sendRedirect(metadataUuid + "/formatters/" + defaultFormatter);
return "forward:" + (metadataUuid + "/formatters/" + defaultFormatter);
return "redirect:" + (encodedUuid + "/formatters/" + defaultFormatter);
}
}

Expand All @@ -184,8 +186,8 @@ public String getRecord(
description = "")
@RequestMapping(value =
{
"/{metadataUuid}/formatters/xml",
"/{metadataUuid}/formatters/json"
"/{metadataUuid:.+}/formatters/xml",
"/{metadataUuid:.+}/formatters/json"
},
method = RequestMethod.GET,
produces = {
Expand Down Expand Up @@ -310,7 +312,7 @@ Object getRecordAs(
description = "Metadata Exchange Format (MEF) is returned. MEF is a ZIP file containing " +
"the metadata as XML and some others files depending on the version requested. " +
"See http://geonetwork-opensource.org/manuals/trunk/eng/users/annexes/mef-format.html.")
@RequestMapping(value = "/{metadataUuid}/formatters/zip",
@RequestMapping(value = "/{metadataUuid:.+}/formatters/zip",
method = RequestMethod.GET,
consumes = {
MediaType.ALL_VALUE
Expand Down Expand Up @@ -549,7 +551,7 @@ public RelatedResponse getAssociatedResources(
description = "Retrieve related services, datasets, onlines, thumbnails, sources, ... " +
"to this records.<br/>" +
"<a href='http://geonetwork-opensource.org/manuals/trunk/eng/users/user-guide/associating-resources/index.html'>More info</a>")
@RequestMapping(value = "/{metadataUuid}/featureCatalog",
@RequestMapping(value = "/{metadataUuid:.+}/featureCatalog",
method = RequestMethod.GET,
produces = {
MediaType.APPLICATION_XML_VALUE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public class MetadataInsertDeleteApi {
+ "By default, a backup is made in ZIP format. After that, "
+ "the record attachments are removed, the document removed "
+ "from the index and then from the database.")
@RequestMapping(value = "/{metadataUuid}", method = RequestMethod.DELETE)
@RequestMapping(value = "/{metadataUuid:.+}", method = RequestMethod.DELETE)
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Record deleted."),
@ApiResponse(responseCode = "403", description = ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_EDIT)})
@ResponseStatus(HttpStatus.NO_CONTENT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public class MetadataProcessApi {

@io.swagger.v3.oas.annotations.Operation(summary = "Get suggestions", description ="Analyze the record an suggest processes to improve the quality of the record.<br/>"
+ "<a href='http://geonetwork-opensource.org/manuals/trunk/eng/users/user-guide/workflow/batchupdate-xsl.html'>More info</a>")
@RequestMapping(value = "/{metadataUuid}/processes", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{metadataUuid:.+}/processes", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('Editor')")
@ResponseStatus(HttpStatus.OK)
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Record suggestions."),
Expand Down Expand Up @@ -133,7 +133,7 @@ List<SuggestionType> getSuggestions(
}

@io.swagger.v3.oas.annotations.Operation(summary = "Preview process result", description =API_OP_NOTE_PROCESS)
@RequestMapping(value = "/{metadataUuid}/processes/{process:.+}", method = {
@RequestMapping(value = "/{metadataUuid:.+}/processes/{process:.+}", method = {
RequestMethod.GET}, produces = MediaType.APPLICATION_XML_VALUE)
@PreAuthorize("hasAuthority('Editor')")
@ResponseStatus(HttpStatus.OK)
Expand All @@ -158,7 +158,7 @@ ResponseEntity<Element> processRecordPreview(
}

@io.swagger.v3.oas.annotations.Operation(summary = "Apply a process", description =API_OP_NOTE_PROCESS)
@RequestMapping(value = "/{metadataUuid}/processes/{process:.+}", method = {
@RequestMapping(value = "/{metadataUuid:.+}/processes/{process:.+}", method = {
RequestMethod.POST,}, produces = MediaType.APPLICATION_XML_VALUE)
@PreAuthorize("hasAuthority('Editor')")
@ResponseStatus(HttpStatus.OK)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
*/
@Service
@RequestMapping(value = {
"/{portal}/api/records/{metadataUuid}"
"/{portal}/api/records/{metadataUuid:.+}"
})
@Tag(name = API_CLASS_RECORD_TAG,
description = API_CLASS_RECORD_OPS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public static Vector<OperationAllowedId> retrievePrivileges(ServiceContext conte
@io.swagger.v3.oas.annotations.Operation(
summary = "Set privileges for ALL group to publish the metadata for all users.")
@RequestMapping(
value = "/{metadataUuid}/publish",
value = "/{metadataUuid:.+}/publish",
method = RequestMethod.PUT
)
@ApiResponses(value = {
Expand All @@ -187,7 +187,7 @@ public void publish(
@io.swagger.v3.oas.annotations.Operation(
summary = "Unsets privileges for ALL group to publish the metadata for all users.")
@RequestMapping(
value = "/{metadataUuid}/unpublish",
value = "/{metadataUuid:.+}/unpublish",
method = RequestMethod.PUT
)
@ApiResponses(value = {
Expand Down Expand Up @@ -223,7 +223,7 @@ public void unpublish(
"administrator, a reviewer or the owner of the record.<br/>" +
"<a href='http://geonetwork-opensource.org/manuals/trunk/eng/users/user-guide/publishing/managing-privileges.html'>More info</a>")
@RequestMapping(
value = "/{metadataUuid}/sharing",
value = "/{metadataUuid:.+}/sharing",
method = RequestMethod.PUT
)
@ApiResponses(value = {
Expand Down Expand Up @@ -453,7 +453,7 @@ private void setOperations(
summary = "Get record sharing settings",
description = "Return current sharing options for a record.")
@RequestMapping(
value = "/{metadataUuid}/sharing",
value = "/{metadataUuid:.+}/sharing",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE
)
Expand Down Expand Up @@ -549,7 +549,7 @@ public SharingResponse getRecordSharingSettings(
summary = "Set record group",
description = "A record is related to one group.")
@RequestMapping(
value = "/{metadataUuid}/group",
value = "/{metadataUuid:.+}/group",
method = RequestMethod.PUT
)
@ApiResponses(value = {
Expand Down Expand Up @@ -738,7 +738,7 @@ MetadataProcessingReport setGroupAndOwner(
summary = "Set record group and owner",
description = "")
@RequestMapping(
value = "/{metadataUuid}/ownership",
value = "/{metadataUuid:.+}/ownership",
method = RequestMethod.PUT
)
@ResponseStatus(HttpStatus.CREATED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public class MetadataSocialApi {
"When a remote rating is applied, the local rating is not updated. It will be updated on the next " +
"harvest run (FIXME ?).")
@RequestMapping(
value = "/{metadataUuid}/rate",
value = "/{metadataUuid:.+}/rate",
method = RequestMethod.PUT
)
@ResponseStatus(HttpStatus.CREATED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public class MetadataTagApi {
description = "Tags are used to classify information.<br/>" +
"<a href='http://geonetwork-opensource.org/manuals/trunk/eng/users/user-guide/tag-information/tagging-with-categories.html'>More info</a>")
@RequestMapping(
value = "/{metadataUuid}/tags",
value = "/{metadataUuid:.+}/tags",
produces = {
MediaType.APPLICATION_JSON_VALUE
},
Expand Down Expand Up @@ -114,7 +114,7 @@ public Set<MetadataCategory> getRecordTags(
summary = "Add tags to a record",
description = "")
@RequestMapping(
value = "/{metadataUuid}/tags",
value = "/{metadataUuid:.+}/tags",
method = RequestMethod.PUT)
@ResponseStatus(value = HttpStatus.CREATED)
@ApiResponses(value = {
Expand Down Expand Up @@ -181,7 +181,7 @@ public void tagRecord(
summary = "Delete tags of a record",
description = "")
@RequestMapping(
value = "/{metadataUuid}/tags",
value = "/{metadataUuid:.+}/tags",
method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@ApiResponses(value = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public static void restructureReportToHavePatternRuleHierarchy(Element errorRepo

@io.swagger.v3.oas.annotations.Operation(summary = "Validate a record", description = "User MUST be able to edit the record to validate it. "
+ "FIXME : id MUST be the id of the current metadata record in session ?")
@RequestMapping(value = "/{metadataUuid}/validate/internal", method = RequestMethod.PUT, produces = {
@RequestMapping(value = "/{metadataUuid:.+}/validate/internal", method = RequestMethod.PUT, produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
@ResponseStatus(HttpStatus.CREATED)
@PreAuthorize("hasAuthority('Editor')")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public class MetadataVersionningApi {
summary = "(Experimental) Enable version control",
description = "")
@RequestMapping(
value = "/{metadataUuid}/versions",
value = "/{metadataUuid:.+}/versions",
method = RequestMethod.PUT)
@ResponseStatus(value = HttpStatus.OK)
@PreAuthorize("hasAuthority('Editor')")
Expand Down
Loading

0 comments on commit a561bd7

Please sign in to comment.