Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOI / Update DOI widget in editor #8468

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.servlet.http.HttpServletRequest;
import org.fao.geonet.api.API;
import org.fao.geonet.api.ApiParams;
import static org.fao.geonet.api.ApiParams.API_PARAM_RECORD_UUID;
import org.fao.geonet.api.ApiUtils;
import org.fao.geonet.api.doiservers.model.AnonymousDoiServer;
import org.fao.geonet.api.doiservers.model.DoiServerDto;
import org.fao.geonet.api.exception.NotAllowedException;
import org.fao.geonet.api.exception.ResourceNotFoundException;
import org.fao.geonet.domain.AbstractMetadata;
import org.fao.geonet.domain.DoiServer;
import org.fao.geonet.kernel.datamanager.IMetadataUtils;
import org.fao.geonet.repository.DoiServerRepository;
import org.fao.geonet.utils.Log;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -93,27 +99,38 @@ List<AnonymousDoiServer> getDoiServers() {
@io.swagger.v3.oas.annotations.Operation(
summary = "Get DOI servers that can be used with a metadata"
)
@GetMapping(value = "metadata/{metadataId}",
@GetMapping(value = "metadata/{metadataUuid}",
produces = {
MediaType.APPLICATION_JSON_VALUE
})
public
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasAuthority('Administrator')")
@PreAuthorize("hasAuthority('Editor')")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "List of all DOI servers where a metadata can be published."),
@ApiResponse(responseCode = "403", description = ApiParams.API_RESPONSE_NOT_ALLOWED_ONLY_ADMIN)
})
List<AnonymousDoiServer> getDoiServers(
@Parameter(description = "Metadata UUID",
required = true,
example = "")
@PathVariable Integer metadataId) {
@Parameter(
description = API_PARAM_RECORD_UUID,
required = true)
@PathVariable
String metadataUuid,
HttpServletRequest request) throws ResourceNotFoundException {

List<DoiServer> doiServers = doiServerRepository.findAll();
List<AnonymousDoiServer> list = new ArrayList<>(doiServers.size());

AbstractMetadata metadata = metadataUtils.findOne(metadataId);
AbstractMetadata metadata;
try {
metadata = ApiUtils.canViewRecord(metadataUuid, true, request);
} catch (ResourceNotFoundException e) {
Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e);
throw e;
} catch (Exception e) {
Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e);
throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW);
}
Integer groupOwner = metadata.getSourceInfo().getGroupOwner();

// Find servers related to the metadata groups owner
Expand Down
16 changes: 13 additions & 3 deletions web-ui/src/main/resources/catalog/components/doi/DoiDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,24 @@
scope.gnDoiService = gnDoiService;
scope.response = {};
scope.isUpdate = angular.isDefined(scope.doiUrl);

scope.doiServers = [];
scope.selectedDoiServer = null;

gnDoiService.getDoiServersForMetadata(scope.uuid).then(function (response) {
scope.doiServers = response.data;
if (scope.doiServers.length > 0) {
scope.selectedDoiServer = scope.doiServers[0].id;

if (scope.isUpdate) {
gnDoiService
.getDoiServerForMetadataAndDoi(scope.uuid, scope.doiUrl)
.then(function (server) {
if (server) {
scope.selectedDoiServer = server.id;
}
});
} else {
if (scope.doiServers.length > 0) {
scope.selectedDoiServer = scope.doiServers[0].id;
}
}
});

Expand Down
66 changes: 60 additions & 6 deletions web-ui/src/main/resources/catalog/components/doi/DoiService.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@
*/
module.service("gnDoiService", [
"$http",
"$q",
"gnConfig",
function ($http, gnConfig) {
function ($http, $q, gnConfig) {
/**
* Returns a promise to validate a metadata to be published on a DOI server.
*
Expand Down Expand Up @@ -65,7 +66,29 @@
* @returns {*}
*/
function getDoiServersForMetadata(metadataId) {
return $http.get("../api/doiservers/metadata/" + metadataId);
return $http.get("../api/doiservers/metadata/" + metadataId, { cache: true });
}

/**
* Return the DOI server with a prefix matching the DOI url.
*/
function getDoiServerForMetadataAndDoi(metadataId, doiUrl) {
var deferred = $q.defer();
getDoiServersForMetadata(metadataId).then(
function (response) {
for (var i = 0; i < response.data.length; i++) {
if (doiUrl.match("doi.org/" + response.data[i].prefix)) {
deferred.resolve(response.data[i]);
return;
}
}
deferred.reject(null);
},
function () {
deferred.reject(null);
}
);
return deferred.promise;
}

function isDoiApplicableForMetadata(md) {
Expand All @@ -85,24 +108,55 @@
* the metadata is published.
*
*/
function canPublishDoiForResource(md, resource) {
function canPublishDoiForResource(md, doiUrl) {
if (doiUrl == null || doiUrl.indexOf("doi.org/") === -1) {

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
doi.org/
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix AI 3 months ago

To fix the problem, we need to ensure that the URL is parsed and the host is explicitly checked against a whitelist of allowed hosts. This will prevent attackers from embedding "doi.org/" in unexpected parts of the URL. We will use the URL constructor available in modern JavaScript to parse the URL and then check the host.

  1. Parse the doiUrl using the URL constructor.
  2. Extract the host from the parsed URL.
  3. Check if the host matches the expected "doi.org" or any allowed subdomains.
Suggested changeset 1
web-ui/src/main/resources/catalog/components/doi/DoiService.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web-ui/src/main/resources/catalog/components/doi/DoiService.js b/web-ui/src/main/resources/catalog/components/doi/DoiService.js
--- a/web-ui/src/main/resources/catalog/components/doi/DoiService.js
+++ b/web-ui/src/main/resources/catalog/components/doi/DoiService.js
@@ -111,3 +111,12 @@
       function canPublishDoiForResource(md, doiUrl) {
-        if (doiUrl == null || doiUrl.indexOf("doi.org/") === -1) {
+        if (doiUrl == null) {
+          return false;
+        }
+        try {
+          var parsedUrl = new URL(doiUrl);
+          var host = parsedUrl.host;
+          if (host !== "doi.org" && !host.endsWith(".doi.org")) {
+            return false;
+          }
+        } catch (e) {
           return false;
EOF
@@ -111,3 +111,12 @@
function canPublishDoiForResource(md, doiUrl) {
if (doiUrl == null || doiUrl.indexOf("doi.org/") === -1) {
if (doiUrl == null) {
return false;
}
try {
var parsedUrl = new URL(doiUrl);
var host = parsedUrl.host;
if (host !== "doi.org" && !host.endsWith(".doi.org")) {
return false;
}
} catch (e) {
return false;
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
return false;
}

var doiKey = gnConfig["system.publication.doi.doikey"];
var isMdWorkflowEnableForMetadata =
gnConfig["metadata.workflow.enable"] && md.draft === "y";
return (
isDoiApplicableForMetadata(md) &&
resource.lUrl !== null &&
resource.lUrl.match("doi.org/" + doiKey) !== null &&
doiUrl.match("doi.org/" + doiKey) !== null &&
!isMdWorkflowEnableForMetadata
);
}

function checkDoiManagementForResource(md, resource) {
if (resource.locUrl == null || resource.locUrl.indexOf("doi.org/") === -1) {

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
doi.org/
' can be anywhere in the URL, and arbitrary hosts may come before or after it.
return;
}
var doiKey = null;
getDoiServersForMetadata(md.id).then(function (response) {
var isMdWorkflowEnableForMetadata =
gnConfig["metadata.workflow.enable"] && md.draft === "y";
for (var i = 0; i < response.data.length; i++) {
if (resource.locUrl.match("doi.org/" + response.data[i].prefix)) {
doiKey = response.data[i].prefix;
break;
}
}
if (doiKey !== null) {
if (
isDoiApplicableForMetadata(md) &&
resource.locUrl.match("doi.org/" + doiKey) !== null &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!isMdWorkflowEnableForMetadata
) {
resource.canManageDoi = true;
}
}
});
}

return {
check: check,
create: create,
isDoiApplicableForMetadata: isDoiApplicableForMetadata,
canPublishDoiForResource: canPublishDoiForResource,
getDoiServersForMetadata: getDoiServersForMetadata
getDoiServersForMetadata: getDoiServersForMetadata,
checkDoiManagementForResource: checkDoiManagementForResource,
getDoiServerForMetadataAndDoi: getDoiServerForMetadataAndDoi
};
}
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ <h2 data-translate="" data-ng-hide="xsMode">createDoiForRecord</h2>
class="form-control"
data-ng-change="updateDoiServer()"
data-ng-show="doiServers.length > 1"
data-ng-disabled="isUpdate"
data-ng-model="selectedDoiServer"
data-ng-options="s.id as s.name for s in doiServers"
>
>
</select>
></select>

<button
class="btn btn-default"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,8 @@
scope.isDoiApplicableForMetadata = gnDoiService.isDoiApplicableForMetadata(
scope.gnCurrentEdit.metadata
);
scope.canPublishDoiForResource = gnDoiService.canPublishDoiForResource;
scope.checkDoiManagementForResource =
gnDoiService.checkDoiManagementForResource;

/**
* Calls service 'relations.get' to load
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,14 @@
class="list-group-item"
href=""
data-ng-repeat="resource in relations.onlines | filter:sortLinksFilter | orderBy:sortLinks:sortLinksReverse"
data-ng-init="gnRelatedResources.getType(resource, 'onlines')"
data-ng-init="gnRelatedResources.getType(resource, 'onlines');
checkDoiManagementForResource(gnCurrentEdit.metadata, resource)"
>
<div class="row">
<div class="col-md-11">
<span
data-gn-link-icon="resource"
titel="{{resource.protocol || ''}}"
title="{{resource.protocol || ''}}"
data-mode="gn-icon-inline"
></span>
<strong>{{resource.locTitle}}</strong>
Expand All @@ -183,9 +184,9 @@
<br />

<div
data-ng-if="canPublishDoiForResource(gnCurrentEdit.metadata, resource)"
data-ng-if="resource.canManageDoi"
data-gn-doi-wizard="gnCurrentEdit.uuid"
data-gn-doi-url="resource.lUrl"
data-doi-url="resource.lUrl"
data-xs-mode="true"
></div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,9 @@
editorConfig: "="
},
link: function (scope, element, attrs) {
scope.canPublishDoiForResource = gnDoiService.canPublishDoiForResource;
scope.checkDoiManagementForResource =
gnDoiService.checkDoiManagementForResource;

scope.editable = angular.isDefined(scope.editorConfig);
scope.lang = scope.lang || scope.$parent.lang;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ <h3>
data-ng-init="mainType = config.getType(r, 'onlines');
badge = config.getBadgeLabel(mainType, r);
icon = config.getClassIcon(mainType);
linkForEdit = convertLinkToEdit(r);"
linkForEdit = convertLinkToEdit(r);
checkDoiManagementForResource(md, r)"
class="row list-group-item gn-related-item gn-related-onlines gn-relation-type-{{mainType}}"
data-ng-repeat="r in distributions | orderBy:getOrderBy"
data-ng-if="editable"
Expand Down Expand Up @@ -204,9 +205,9 @@ <h3 class="flex-grow">
</p>

<div
data-ng-if="canPublishDoiForResource(md, linkForEdit, doiServers)"
data-ng-if="r.canManageDoi"
data-gn-doi-wizard="md.uuid"
data-gn-doi-url="r.locUrl"
data-doi-url="r.locUrl"
data-xs-mode="true"
></div>

Expand Down