Skip to content

fix(dar): reject duplicate active Data Access Requests per user and entity#28853

Open
yan-3005 wants to merge 11 commits into
mainfrom
restrict-duplicate-dar-requests
Open

fix(dar): reject duplicate active Data Access Requests per user and entity#28853
yan-3005 wants to merge 11 commits into
mainfrom
restrict-duplicate-dar-requests

Conversation

@yan-3005

@yan-3005 yan-3005 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Fixes https://github.com/open-metadata/openmetadata-collate/issues/4463

Problem

A user should be able to have only one active Data Access Request per entity. Today that's only guarded in the UI on the Entity Details page. The POST /api/v1/tasks endpoint has no such check, so the Data Access Request page (entity dropdown) lets the same user submit a second request for an entity they already have a pending request for, creating duplicates.

Fix

Enforce the rule in the backend (TaskRepository.prepare, create path only) so it holds for every entry point. On creation of a DataAccessRequest, the request is rejected with 400 when the same creator already has an active request for the same entity.

  • Active = every status except the terminal ones: Rejected, Revoked, Expired. Expired is a forthcoming status not yet in TaskEntityStatus, so it is kept as a string literal until the enum is extended.
  • Keyed per-entity (via aboutFqnHash) and per-creator (via createdById).
  • The rejection message references the existing task id so the UI can link to it.
  • Different users requesting the same entity, and the same user requesting a different entity, remain allowed.

Tests

DataAccessRequestValidationIT:

  • Same user, same entity, active request → rejected (message references existing task).
  • Different users, same entity → both allowed (creator scoping).
  • Same user, different entities → both allowed (entity scoping).

Known limitation

Check-then-insert is not atomic — two concurrent POSTs can both pass the guard. This matches the existing connector-capability validator's behavior. A unique DB index would be the airtight fix but is out of scope here.

…ntity

Enforce the one-active-request-per-entity rule in the backend so it holds
regardless of entry point (Entity Details page or the Data Access Request
page). On task creation, a Data Access Request is rejected when the same
creator already has an active request for the same entity.

A request is "active" in every status except the terminal ones (Rejected,
Revoked, Expired); Expired is kept as a string literal until the enum is
extended. Different users requesting the same entity, and the same user
requesting a different entity, remain allowed.
Copilot AI review requested due to automatic review settings June 9, 2026 07:08
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@yan-3005 yan-3005 self-assigned this Jun 9, 2026
@yan-3005 yan-3005 added safe to test Add this label to run secure Github workflows on PRs backend labels Jun 9, 2026

Copilot AI left a comment

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.

Pull request overview

This PR enforces a backend invariant for Data Access Requests (DAR): a single user can have at most one active DAR per target entity, preventing duplicate submissions via POST /api/v1/tasks (not just UI-guarded entry points).

Changes:

  • Added a backend validation on task creation to reject duplicate active DARs for the same (creator, entity) pair, returning a 400 with a message referencing the existing task id.
  • Added a DAO query to look up an existing active DAR by aboutFqnHash + type + createdById, excluding terminal statuses.
  • Added integration tests covering same-user duplicate rejection and allowed cases for different users / different entities.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TaskRepository.java Adds create-time validation to prevent duplicate active DARs per creator+entity.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java Adds a query to find an existing active DAR for a given about-entity and creator.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DataAccessRequestValidationIT.java Adds IT coverage for duplicate DAR rejection and allowed non-duplicate scenarios.

…kStatus

Address review feedback: the hand-written terminal-status denylist
(Rejected/Revoked/Expired) wrongly treated Completed/Cancelled/Failed as
active, which would block a user from re-requesting an entity whose prior
request had already ended in one of those states.

Derive the active (non-terminal) status set from the canonical workflow
definition CreateTask.isTerminalTaskStatus so the duplicate check can never
drift from it. Any status the workflow treats as terminal frees the user to
file a new request. Drops the Expired string literal: a future Expired
status is terminal by construction and needs no special-casing.
Add DataAccessRequestValidationIT.testDarAfterPreviousRequestTerminal_allowed:
create a Data Access Request, close it (resolves to the terminal Cancelled
status), then confirm the same user can file a fresh request for the same
entity. Locks in the derived active/terminal semantics so future changes to
CreateTask.isTerminalTaskStatus cannot silently regress the duplicate guard.
Copilot AI review requested due to automatic review settings June 9, 2026 07:42

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

…okup, fix capability tests

- Decouple from CreateTask.isTerminalTaskStatus (Copilot): replace the static
  initializer that force-loaded the Flowable listener class with a local
  ACTIVE_TASK_STATUSES constant (Open/InProgress/Pending/Approved/Granted);
  revert isTerminalTaskStatus to package-private. The list is documented to
  stay in sync with that canonical predicate and ListFilter's active group.
- Make the duplicate lookup deterministic (Copilot): ORDER BY createdAt DESC
  before LIMIT 1 so the rejection message references the newest active request.
- Fix three capability tests that legitimately created multiple DARs (different
  access types) on the same entity, which the one-active-request-per-entity
  rule now forbids; each access-type request now targets its own table/data
  product.
Copilot AI review requested due to automatic review settings June 10, 2026 04:59

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings June 10, 2026 12:09
@gitar-bot

gitar-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 2 resolved / 2 findings

Enforces Data Access Request uniqueness by rejecting duplicate active requests per user and entity at the backend level. The implementation addresses potential status list drift by deriving active statuses directly from the canonical terminal task predicate.

✅ 2 resolved
Quality: Broadened terminal-status set is untested

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TaskRepository.java:109-113 📄 openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DataAccessRequestValidationIT.java:277-291
This commit changes the duplicate-DAR guard from an explicit terminal list (Rejected, Revoked, Expired) to deriving terminal status from CreateTask.isTerminalTaskStatus. As a result, Completed, Cancelled, and Failed are now treated as terminal (non-active), whereas in the prior commit they counted as active and would have blocked a new request. This is a meaningful behavioral change — a user with a Completed/Cancelled/Failed request for an entity can now file a new one — but DataAccessRequestValidationIT only covers the active-rejection, different-user, and different-entity paths. There is no test asserting that a terminal request (Completed/Cancelled/Failed/Rejected/Revoked) permits a fresh request for the same entity. Add an IT that drives a request to a terminal status and confirms a subsequent request for the same entity/creator succeeds, so the derived semantics are locked in and protected against future drift in isTerminalTaskStatus.

Quality: Manual active-status list can silently drift from terminal predicate

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TaskRepository.java:101-115
This commit replaces the self-syncing derivation Arrays.stream(TaskEntityStatus.values()).filter(s -> !CreateTask.isTerminalTaskStatus(s)) with a hand-maintained ACTIVE_TASK_STATUSES list (Open, InProgress, Pending, Approved, Granted). It is currently correct, but it now duplicates knowledge that lives in two other places that the Javadoc itself names: CreateTask.isTerminalTaskStatus and the active status group in ListFilter. If a new non-terminal status is ever added to TaskEntityStatus, it will be omitted from this list and the duplicate-request guard will silently allow duplicates for that status — the exact regression the old derivation was immune to. Consider either keeping the derivation, or adding a small unit test asserting ACTIVE_TASK_STATUSES equals the set of TaskEntityStatus.values() for which CreateTask.isTerminalTaskStatus is false, so drift fails the build rather than slipping through silently.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants