Skip to content

fix: NPE (HTTP 500) in CanManageMappingsFeature for item with null owning collection#1348

Open
milanmajchrak wants to merge 1 commit into
dtq-devfrom
fix/can-manage-mappings-npe-null-owning-collection
Open

fix: NPE (HTTP 500) in CanManageMappingsFeature for item with null owning collection#1348
milanmajchrak wants to merge 1 commit into
dtq-devfrom
fix/can-manage-mappings-npe-null-owning-collection

Conversation

@milanmajchrak

@milanmajchrak milanmajchrak commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Problem description

CanManageMappingsFeature.isAuthorized() throws a NullPointerException (HTTP 500) for any item whose owning_collection is NULL. In production this made an item completely unmanageable from the UI: opening Edit Item as an administrator returned 500.

Observed in production (data-dspace.zcu.cz) for item 62133a8a-fbd1-474f-9b55-fa92d318aa03 (handle 20.500.14592/107). The page /items/<uuid>/edit/metadata calls GET /api/authz/authorizations/search/object, which evaluates the canManageMappings feature and crashes:

java.lang.NullPointerException: null
  at org.dspace.app.rest.authorization.impl.CanManageMappingsFeature.lambda$isAuthorized$0(CanManageMappingsFeature.java:74)
  at ...ReferencePipeline... findFirst
  at org.dspace.app.rest.authorization.impl.CanManageMappingsFeature.isAuthorized(CanManageMappingsFeature.java:82)
  at org.dspace.app.rest.repository.AuthorizationRestRepository.findByObjectAndFeature(AuthorizationRestRepository.java:277)

Analysis

Exact cause

The stream filter dereferenced the item's owning collection without a null check:

.filter(c -> !c.getID().equals(item.getOwningCollection().getID()))

When item.getOwningCollection() is null, .getID() is called on nullNullPointerException. The exception only reaches users who pass the preceding WRITE authorization check (i.e. administrators), which is why anonymous/non-write users saw the item but admins got a 500 when editing it.

This is a latent upstream bug: the same unguarded item.getOwningCollection().getID() is still present in upstream DSpace (dspace-7_x, main, dspace-8_x). It only manifests for an item that is in an inconsistent state with no owning collection.

How an item reaches that state (real incident)

The affected item was created by a normal UI submission and its workflow deposit committed successfully (handle assigned, collection2item mapping created, Solr-indexed, workspace item deleted). However the item row ended up with owning_collection = NULL and in_archive = false while the collection2item mapping remained — an inconsistent half-reverted state. With owning_collection = NULL, this feature crashes. (Repairing that specific item's data — re-setting owning_collection + in_archive and reindexing — is a separate operational fix and is not part of this PR. This PR makes the code robust so the feature never 500s regardless of how an item lost its owning collection.)

Fix

  • Resolve the owning collection once and treat null as "there is no owning collection to exclude" in the filter — mirroring the defensive pattern already used by DeleteFeature.
  • Also guard against itemService.find() returning null.
if (item == null || !authorizeService.authorizeActionBoolean(context, item, Constants.WRITE)) {
    return false;
}
Collection owningCollection = item.getOwningCollection();
...
.filter(c -> owningCollection == null || !c.getID().equals(owningCollection.getID()))

After this change, evaluating canManageMappings for an orphaned item returns a normal authorization result instead of throwing, so Edit Item no longer returns 500.

Problems

None.

Manual Testing (if applicable)

Manual repro: with an item whose owning_collection IS NULL, open Edit Item as admin → before: HTTP 500 (NPE); after: page loads, no exception.

Copilot review

  • Requested review from Copilot

Summary by CodeRabbit

  • Bug Fixes
    • Improved item authorization checks to handle items without an owning collection, preventing errors during editability checks.
    • Added safer handling when item data is unavailable, reducing the chance of unexpected authorization failures.

CanManageMappingsFeature.isAuthorized dereferenced
item.getOwningCollection().getID() with no null check, so evaluating the
canManageMappings feature for an item whose owning_collection is NULL threw
a NullPointerException (HTTP 500). This surfaced on the item edit page via
GET /api/authz/authorizations/search/object for users with WRITE (admins).

Resolve the owning collection once and treat null as "no collection to
exclude" in the filter, mirroring the defensive pattern already used by
DeleteFeature. Also guard against itemService.find returning null.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2a20bf21-c15c-4319-b570-cae8980299f7

📥 Commits

Reviewing files that changed from the base of the PR and between 867e43d and 3b6b7a7.

📒 Files selected for processing (1)
  • dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageMappingsFeature.java

📝 Walkthrough

Walkthrough

In CanManageMappingsFeature.isAuthorized, a null check for the resolved Item is added, and item.getOwningCollection() is captured into a local variable. The collection-filter predicate is updated to handle a null owning collection, preventing a NullPointerException for orphaned items.

CanManageMappingsFeature Authorization Fix

Layer / File(s) Summary
Null-safe item and owning collection check
dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageMappingsFeature.java
Returns false when item is null; stores getOwningCollection() in a local variable; filter predicate treats null owning collection as a match to avoid NPE on orphaned items.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fix: a null owning collection causing an NPE in CanManageMappingsFeature.
Description check ✅ Passed The description follows the template well, with problem, analysis, problems, manual testing, and Copilot review sections present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes a NullPointerException (HTTP 500) in the REST authorization feature canManageMappings when evaluating permissions for an Item whose owningCollection is null (or when the Item cannot be found), preventing admin UI flows like Edit Item from crashing on orphaned items.

Changes:

  • Add a null guard for itemService.find(...) returning null before running authorization checks.
  • Resolve owningCollection once and update the stream filter to safely handle null owning collections (avoid dereferencing item.getOwningCollection().getID()).

Comment on lines +67 to +71
if (item == null || !authorizeService.authorizeActionBoolean(context, item, Constants.WRITE)) {
return false;
}
// An orphaned item (e.g. one whose owning collection was lost) has no owning collection.
// Guard against it so the feature evaluation does not throw a NullPointerException.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants