Skip to content
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
2 changes: 1 addition & 1 deletion assets/js/components/record_details/CommitteeApproval.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class CommitteeApprovalManageSection extends Component {

{pubRn && <strong>{pubRn}</strong>}

{canViewReviewedVersion && draftRecordId && (
{canViewReviewedVersion && draftRecordId && draftRecordId !== record.id && (
<>
{" · "}
<a href={`/records/${draftRecordId}`} target="_blank" rel="noreferrer">
Expand Down
88 changes: 34 additions & 54 deletions site/cds_rdm/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
"""CDS RDM service components."""

from flask import current_app
from flask_principal import ActionNeed
from invenio_access import Permission
from invenio_access.permissions import system_user_id
from invenio_communities.proxies import current_communities
from invenio_drafts_resources.services.records.components import ServiceComponent
from invenio_i18n import gettext as _
Expand Down Expand Up @@ -130,22 +129,19 @@ def publish(self, identity, draft=None, record=None, **kwargs):
class CommitteeApprovalComponent(ServiceComponent):
"""Guard and sync committee approval identifiers.

1. Blocks non-privileged users from adding/modifying/deleting ``apprn``
scheme identifiers — these are system-managed only.
2. Blocks non-privileged users from adding a ``cdsrn`` identifier whose
value matches any configured committee approval report-number pattern
(e.g. CERN-EP-*).
3. Regenerates the ``apprn`` metadata identifier from parent committee_approval
on every save — only the public approved record carries it (detected by
``source_internal_version`` on the parent).
1. Blocks everyone except the system process from adding/modifying/deleting
``apprn`` scheme identifiers — including admins via the UI.
2. Regenerates the ``apprn`` metadata identifier from parent committee_approval
only when ``source_internal_version`` is set on the parent. This covers
two cases:
- Public approved copy in the two-record flow (views.py sets
``source_internal_version`` to the internal record's recid).
- Single-record migration case (migrate_cdsrn_to_apprn.py sets
``source_internal_version`` to the record's own recid).
The internal record in the normal flow never has ``source_internal_version``
on its parent, so it never carries the apprn identifier.
"""

def _is_privileged(self, identity):
"""Return True if the identity is system or has superuser access."""
return identity.id == "system" or Permission(
ActionNeed("superuser-access")
).allows(identity)

def _committee_approval_prefixes(self):
"""Return the set of fixed prefixes from all configured committee communities.

Expand All @@ -160,9 +156,9 @@ def _committee_approval_prefixes(self):
prefixes.add(prefix)
return prefixes

def _validate_identifier_changes(self, identity, data, record):
"""Raise ValidationError if the user is modifying protected identifiers."""
if self._is_privileged(identity):
def _validate_identifier_changes(self, identity, data, record, errors):
"""Raise ValidationError if a non-system identity modifies apprn."""
if identity.id == system_user_id:
return

incoming_identifiers = (data.get("metadata") or {}).get("identifiers", [])
Expand All @@ -177,7 +173,7 @@ def _validate_identifier_changes(self, identity, data, record):
}
if incoming_apprn != stored_apprn:
error_msg = _(
"The 'apprn' identifier is system-managed and cannot be "
"The EP approval report number is system-managed and cannot be "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

here we cannot hardcode the EP label as we would like to have this configurable. Maybe we added a label fro this in the config.

"added, modified, or removed manually."
)

Expand All @@ -201,59 +197,43 @@ def _validate_identifier_changes(self, identity, data, record):

raise ValidationErrorWithMessageAsList(errors)

# Block cdsrn values that look like committee report numbers.
ep_prefixes = self._committee_approval_prefixes()
if ep_prefixes:
errors = []
for index, ident in enumerate(incoming_identifiers):
if ident.get("scheme") == "cdsrn":
val = ident.get("identifier", "")
if any(val.startswith(p) for p in ep_prefixes):
errors.append(
{
"field": f"metadata.identifiers.{index}.identifier",
"messages": [
_(
f"The value '{val}' matches an EP approval "
"report number pattern and cannot be used as "
"a CDS report number."
)
],
}
)
if errors:
raise ValidationErrorWithMessageAsList(errors)
def _should_sync_apprn(self, record, committee_approval):
"""Return True if apprn should be synced with parent committee_approval."""
reportnumber = committee_approval.get("reportnumber")
approved_internal = committee_approval.get("approved_internal_version")
source_internal = committee_approval.get("source_internal_version")
if not reportnumber or not source_internal:
return False
# Migrated case: both flags point at the same version, so only the current version carries apprn
if approved_internal == source_internal:
return record["id"] == source_internal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If I understand correctly, that wil regenerate the apprn only for the accepted version. The truth is that all the newer versions should have the apprn number

# Public copy: source points back to a different (internal) record, always sync
return True
Comment on lines +208 to +211

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isnt this always True?


def _regenerate_apprn_identifier(self, record, data):
"""Keep apprn in metadata.identifiers in sync with parent committee_approval.

The apprn identifier is only added when ``source_internal_version`` is present
on the parent — that key is set exclusively on the public approved record's
parent by the ``publish_public_record`` view.
"""
ea = (
"""Keep apprn in metadata.identifiers in sync with parent committee_approval."""
committee_approval = (
(record.parent.get("permission_flags") if record.parent else None) or {}
).get("committee_approval") or {}
reportnumber = ea.get("reportnumber")
source_internal = ea.get("source_internal_version")
reportnumber = committee_approval.get("reportnumber")
identifiers = [
i
for i in (data.get("metadata") or {}).get("identifiers", [])
if i.get("scheme") != "apprn"
]
if reportnumber and source_internal:
if self._should_sync_apprn(record, committee_approval):
identifiers = [
{"scheme": "apprn", "identifier": reportnumber}
] + identifiers
data.setdefault("metadata", {})["identifiers"] = identifiers

def create(self, identity, data=None, record=None, errors=None, **kwargs):
"""Validate apprn identifier on draft creation."""
self._validate_identifier_changes(identity, data, record)
self._validate_identifier_changes(identity, data, record, errors)

def update_draft(self, identity, data=None, record=None, errors=None, **kwargs):
"""Validate and regenerate apprn identifier on draft update."""
self._validate_identifier_changes(identity, data, record)
self._validate_identifier_changes(identity, data, record, errors)
self._regenerate_apprn_identifier(record, data)

def publish(self, identity, draft=None, record=None, **kwargs):
Expand Down
3 changes: 2 additions & 1 deletion site/cds_rdm/requests/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def publish_public_record(pid_value):
)
if cern_scientific_community_id:
try:
current_record_communities_service.add(
_, errors = current_record_communities_service.add(
system_identity,
new_record.data["id"],
data={
Expand Down Expand Up @@ -289,6 +289,7 @@ def publish_public_record(pid_value):
pf["committee_approval"] = {
**ea,
"approved_public_version": new_record_id,
"source_public_version": src_id,
}
src_rec_obj.parent["permission_flags"] = pf
src_rec_obj.parent.commit()
Expand Down
Loading