Skip to content

Update consent server version#3518

Open
ThumulaPerera wants to merge 1 commit into
thunder-id:mainfrom
ThumulaPerera:update-consent-server-version
Open

Update consent server version#3518
ThumulaPerera wants to merge 1 commit into
thunder-id:mainfrom
ThumulaPerera:update-consent-server-version

Conversation

@ThumulaPerera

@ThumulaPerera ThumulaPerera commented Jun 24, 2026

Copy link
Copy Markdown
Member

Purpose

Update the consent server version to support updating consent perposes via versioning.

⚠️ Breaking Changes

🔧 Summary of Breaking Changes

The bundled consent server moves from v0.2.0 to v0.3.0. The DB schema is updated.

Renamed columns (existing tables)

Table Old New
CONSENT CLIENT_ID GROUP_ID
CONSENT VALIDITY_TIME EXPIRATION_TIME
PURPOSE CLIENT_ID GROUP_ID
PURPOSE_ELEMENT_MAPPING IS_MANDATORY MANDATORY
CONSENT_ELEMENT_APPROVAL IS_USER_APPROVED APPROVED

Restructured tables — element & purpose are now versioned

  • ELEMENT and PURPOSE each row is now one version of the entity. PK is VERSION_ID; logical identity is ID (groups versions); a new VERSION integer column orders them.
  • ELEMENT gains NAMESPACE (default 'default') and TYPE (default 'basic').
  • *_PROPERTY tables (ELEMENT_PROPERTY, PURPOSE_PROPERTY) now key on the version row, not the entity.
  • PURPOSE_ELEMENT_MAPPING maps version → version (PURPOSE_VERSION_ID, ELEMENT_VERSION_ID), not entity → entity.
  • CONSENT_ELEMENT_APPROVAL and the new PURPOSE_CONSENT_MAPPING pin each consent to the exact purpose/element versions it was issued against.

New tables

  • CONSENT_STATUS_AUDIT — append-only status history; adds ACTION_BY (required by the new revoke API).
  • CONSENT_AUTH_RESOURCE — per-consent authorizations.
  • CONSENT_ATTRIBUTE — per-consent key/value pairs.
  • PURPOSE_CONSENT_MAPPING — links a consent to its purpose version.

💥 Impact

  • Any deployment with flows which include consent will lose previous consent data.

🔄 Migration Guide

Drop the old consent DB and recreate it from the v0.3.0 scripts** bundled under consent/dbscripts/ in the new Thunder distribution.

Approach

The PR is a wire-format adapter swap — the new consent server (v0.3.0) is bolted on without touching anything outside the consent client.

Approach in five points:

  1. All changes live in one file (default_client.go). Thunder's internal interfaces, domain types, and callers are untouched.
  2. Where v0.3.0 removed things, emulate them in the client:
    • Removed validate endpoint → reimplemented as per-name GET lookups.
    • Updates are now versioned (POST /versions) → presented to callers as if still in-place edits.
    • List-purposes no longer returns elements → followed up with one GET per purpose to refill them.
  3. Where v0.3.0 can't be hidden, narrow the change and log it:
    • Multi-value purposeNames search → only the first is sent, the rest are debug-logged.
    • Stale element approvals are now dropped during merge (new behavior required by versioning).
  4. Hardcode placeholders for fields Thunder's domain doesn't model yet:
    • actionBy = "thunder" on revoke (Thunder has no actor field).
    • type = "basic" on elements (Thunder only uses one type).
    • displayName mirrors description (Thunder has one field, not two).
  5. Tests follow the server. The integration mock is rewritten to mimic v0.3.0; client unit tests gain coverage for the new mappers and versioning flows.

Related Issues

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
    • Ran Vale and fixed all errors and warnings
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

Summary

  • New Features

    • Consent recording now revalidates purposes against the latest definitions and drops stale purpose/element decisions.
    • Consent prompts now only include elements that match the requested essential/optional attributes.
  • Bug Fixes

    • Purpose/element merging now filters out deleted or no-longer-valid items and preserves safe ordering.
    • Bulk consent element creation now reports partial failures more accurately.
    • Consent listing/search/revocation updated to align with the latest request/response behavior (including updated filter and pagination handling).
  • Breaking Changes

    • Consent element update and purpose deletion operations have been removed.

@ThumulaPerera ThumulaPerera added Type/Improvement breaking change The feature/ improvement will alter the existing behaviour labels Jun 24, 2026
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates the default consent client to the v0.3.0 API contract: DTO field names, endpoint paths, headers, query parameters, and element validation behavior change. It also updates consent recording to re-read current purpose definitions before merging approvals, and adds tests for the new filtering behavior.

Changes

Default consent client v0.3.0 adaptation

Layer / File(s) Summary
Wire contracts and removed operations
backend/internal/consent/default_client.go, backend/internal/consent/interface.go, backend/internal/consent/service.go, backend/internal/consent/error_constants.go, backend/internal/consent/ConsentServiceInterface_mock_test.go, backend/internal/consent/consentClientInterface_mock_test.go, backend/internal/consent/model.go, tests/integration/application/consent_api_test.go
Updates consent DTOs, constants, interface signatures, removed consent-element/purpose operations, and the matching mock/integration fixtures to the v0.3.0 wire shapes and identifiers.
Element create and validation flow
backend/internal/consent/default_client.go, backend/internal/consent/default_client_test.go
Switches bulk element creation to per-item results, changes validation to client-side exact-match searches, and updates the related tests for bulk failures, exact matching, and deduplication.
Purpose, consent, search, and revoke wire changes
backend/internal/consent/default_client.go, backend/internal/consent/default_client_test.go
Changes purpose listing and versioning, consent request/response mappings, search parameters, revoke payloads, header keys, and the matching wire-contract tests. Tests also cover the new groupIds and purposeName parameters and the POST-based version/revoke endpoints.

Consent enforcer stale-element filtering

Layer / File(s) Summary
Current-purpose filtering in RecordConsent
backend/internal/authn/consent/service.go
RecordConsent now loads current purpose definitions, builds a purpose-to-element allowlist, and merges consent decisions with definition-aware filtering.
Consent enforcer tests
backend/internal/authn/consent/service_test.go
Updates record-consent tests to mock purpose listing, revises prompt and merge tests for the new allowlist parameter, and adds stale-element and deleted-purpose coverage.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • thunder-id/thunderid#1697: It touches the same consent enforcer service flow and related consent-purpose handling in backend/internal/authn/consent/service.go.
  • thunder-id/thunderid#2823: It also changes consent-purpose and element decision filtering logic in the consent enforcer path.

Suggested reviewers

  • ThaminduDilshan
  • thiva-k
  • senthalan
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: upgrading the consent server version.
Description check ✅ Passed The description follows the template and includes purpose, breaking changes, approach, related issues, checklist, and security checks.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 unit tests (beta)
  • Create PR with unit tests

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.

@ThumulaPerera ThumulaPerera marked this pull request as draft June 24, 2026 10:28
@ThumulaPerera

ThumulaPerera commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

Kept as a draft as this requires a new release in consent server and a corresponding consent server version bump at ThunderID

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Bundle Report

Bundle size has no change ✅

Affected Assets, Files, and Routes:

view changes for bundle: console-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/dist-*.js -136.43kB 130 bytes -99.9%
assets/dist-*.js 9 bytes 154 bytes 6.21% ⚠️
assets/dist-*.js -3 bytes 145 bytes -2.03%
assets/dist-*.js 136.41kB 136.56kB 88577.27% ⚠️
assets/dist-*.js 8 bytes 148 bytes 5.71% ⚠️
assets/dist-*.js 10 bytes 140 bytes 7.69% ⚠️

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/internal/authn/consent/service_test.go (1)

782-786: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a direct failure-path test for ListConsentPurposes.

The new branch that maps purpose-list failures to ErrorConsentPurposeFetchFailed / InternalServerError is still uncovered here. A small client-error + server-error pair would lock down the new error handling.

Also applies to: 820-822, 855-857

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/authn/consent/service_test.go` around lines 782 - 786, Add
direct failure-path coverage for ListConsentPurposes in the consent service
tests: the new branch that maps purpose-list errors to
ErrorConsentPurposeFetchFailed and InternalServerError is still untested. Extend
the relevant test cases around mockConsentSvc.On("ListConsentPurposes", ...) to
include one client-error and one server-error scenario, then assert the returned
error mapping from the consent service method that calls ListConsentPurposes.
backend/internal/consent/default_client.go (1)

51-51: 🔒 Security & Privacy | 🔵 Trivial | 🏗️ Heavy lift

Verify the hardcoded revoke actor.

Is this hardcoded brand name intentional? actionBy feeds the v0.3.0 revoke audit payload, so a fixed "thunder" value makes all revocations indistinguishable. Prefer sourcing this from the caller, runtime config, or a configured service-principal identity.

As per path instructions, hardcoded brand-name literals in Go files should be flagged and replaced with a named/runtime value when possible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/consent/default_client.go` at line 51, The hardcoded revoke
actor value in default_client.go is making every revoke audit entry look the
same. Update the revoke path around revokeActionBy and the revoke audit payload
wiring in default_client.go so actionBy comes from the caller, runtime
configuration, or a configured service-principal identity instead of a fixed
brand-name literal, and keep the constant only if it is truly intended to be a
fallback default.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/internal/authn/consent/service.go`:
- Around line 247-265: The merge only filters stale purpose elements when an
existing consent record is found, so first-time consent submissions can still
persist outdated purpose/element decisions from newPurposeItems and misfire
hasEssentialDenial. Update the create path in service.go around the existing
consent handling so createNewConsent uses the same current purpose definitions
fetched via ListConsentPurposes and applies
mergeConsentPurposes/buildPurposeElementSet before saving, reusing the existing
merge/filter logic to sanitize newPurposeItems as well.

In `@backend/internal/consent/default_client_test.go`:
- Around line 481-485: The validateConsentElements test currently uses a broad
HTTP mock that can pass even if the request is made with the empty string
instead of the deduped email target. Tighten the expectation around
validateConsentElements by matching the outgoing request’s query parameter name
to email on the httpMock.EXPECT().Do call, so the test verifies deduping and
empty-skip behavior rather than just any request shape.

In `@backend/internal/consent/default_client.go`:
- Around line 984-993: The Search filter handling in default_client.go is
silently ignoring extra values in filter.PurposeNames by only setting the first
purposeName on the request. Update the consent search path in the relevant
search/filter builder to either reject multi-value PurposeNames explicitly with
an error, or apply the remaining purpose-name constraints client-side after the
service call. Make sure the behavior is implemented where
params.Set("purposeName", ...) is populated so callers never receive results
that violate the requested filter.
- Around line 525-530: The elementNameExists lookup only checks the first page
of search results, so it can incorrectly return false when the match is on a
later page. Update elementNameExists in default_client.go to keep paging through
the consent-element search results using result.Metadata until all pages are
exhausted, returning true as soon as dto.Name matches name and only returning
false after every page has been inspected.

---

Nitpick comments:
In `@backend/internal/authn/consent/service_test.go`:
- Around line 782-786: Add direct failure-path coverage for ListConsentPurposes
in the consent service tests: the new branch that maps purpose-list errors to
ErrorConsentPurposeFetchFailed and InternalServerError is still untested. Extend
the relevant test cases around mockConsentSvc.On("ListConsentPurposes", ...) to
include one client-error and one server-error scenario, then assert the returned
error mapping from the consent service method that calls ListConsentPurposes.

In `@backend/internal/consent/default_client.go`:
- Line 51: The hardcoded revoke actor value in default_client.go is making every
revoke audit entry look the same. Update the revoke path around revokeActionBy
and the revoke audit payload wiring in default_client.go so actionBy comes from
the caller, runtime configuration, or a configured service-principal identity
instead of a fixed brand-name literal, and keep the constant only if it is truly
intended to be a fallback default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 246d88d4-837f-4311-bdf4-8bfb81ab9630

📥 Commits

Reviewing files that changed from the base of the PR and between 2661170 and ecbc3be.

⛔ Files ignored due to path filters (1)
  • tests/integration/testutils/mock_consent_server.go is excluded by !**/mock_*.go
📒 Files selected for processing (5)
  • backend/internal/authn/consent/service.go
  • backend/internal/authn/consent/service_test.go
  • backend/internal/consent/default_client.go
  • backend/internal/consent/default_client_test.go
  • tests/integration/application/consent_api_test.go

Comment thread backend/internal/authn/consent/service.go Outdated
Comment thread backend/internal/consent/default_client_test.go Outdated
Comment thread backend/internal/consent/default_client.go
Comment thread backend/internal/consent/default_client.go Outdated
@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.62874% with 24 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
backend/internal/authn/consent/service.go 78.94% 8 Missing and 4 partials ⚠️
backend/internal/consent/default_client.go 89.09% 7 Missing and 5 partials ⚠️

📢 Thoughts on this report? Let us know!

Comment thread tests/integration/testutils/mock_consent_server.go Outdated
Comment thread tests/integration/testutils/mock_consent_server.go
// revokeActionBy is the value sent in the revoke payload's required actionBy field. The
// Thunder-internal ConsentRevokeRequest does not carry actor identity yet; once the
// interface gains an actor field, source it from there instead.
const revokeActionBy = "thunder"

@ThaminduDilshan ThaminduDilshan Jun 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use something else other than thunder? Maybe admin/ user. Btw what happens if we sent this empty as before? Are they rejecting it?

On a separate note we should use thunderid instead of thunder in all places

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

what happens if we sent this empty as before? Are they rejecting it? -> this is a required field. Request will get rejected

But currently we do not have any usages of revoke method. We can remove the method from the interface itself. We can add back in the future if needed. WDYT?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

As discussed offline, will keep the revoke method as we have a planned future use

Comment thread backend/internal/consent/default_client.go Outdated
Comment thread backend/internal/consent/default_client.go Outdated
// element map to the conflict error; everything else falls back to the generic
// invalid-element-request error.
func bulkResultErrorToServiceError(msg string) *serviceerror.ServiceError {
if strings.Contains(msg, "already exists") {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't it possible to detect this with a status code? I guess this is the way openfgc returns the error.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

not possible. in bulk element create, response will have a code of 200 and a seperate status for each element. some elements may be created while some may not

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is not a scalable approach to detect errors right? Shall we create an issue on openfgc side and keep it tracked?

Comment thread backend/internal/consent/default_client.go Outdated

// v0.3.0 list omits per-purpose elements; fetch each by ID to populate them. Callers
// (consent prompt building, attribute-purpose sync) compare against Elements, so an
// empty slice would silently break those flows.

@ThaminduDilshan ThaminduDilshan Jun 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Our implementation is done according to the previous openFGC version, but most of these endpoints doesn't have a valid usage on our end.
If this is something not required by us, we can modify the existing data structures to align with the openfgc v0.3.0 models. We don't have to fetch each purpose to align this with the existing model unless there's a valid usage

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This has a valid usage on our end. In all current usages of consent purpose listing, we have a requirement to iterate through elements of those purposes too

// multiple, send the first and drop the rest — log so the omission is visible.
if len(filter.PurposeNames) > 0 {
params.Set("purposeNames", strings.Join(filter.PurposeNames, ","))
params.Set("purposeName", filter.PurposeNames[0])

@ThaminduDilshan ThaminduDilshan Jun 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as previous comment. If this is something openfgc no longer supports, we can adjust our usages and models to follow their new format. Dropping it silently will not solve the actual problem right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Currently we don't have any usages for this field in the filter. Updated the model to match openfgc shape

@ThumulaPerera ThumulaPerera force-pushed the update-consent-server-version branch 5 times, most recently from b5edce5 to 25a186f Compare June 26, 2026 06:33
@ThumulaPerera ThumulaPerera force-pushed the update-consent-server-version branch from 25a186f to 94dbf89 Compare June 26, 2026 07:52
@ThumulaPerera ThumulaPerera marked this pull request as ready for review June 26, 2026 07:53

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/internal/authn/consent/service.go (1)

180-182: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Filter essential-denial checks through the live purpose allowlist.

hasEssentialDenial is computed from the session token before validElements is built. If an essential element is removed/re-versioned after prompting, mergeConsentPurposes drops it from persistence, but RecordConsent can still return ErrorEssentialConsentDenied.

Compute essential denials after buildPurposeElementSet(currentPurposes) and ignore purposes/elements absent from validElements.

Also applies to: 209-222

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/authn/consent/service.go` around lines 180 - 182,
`RecordConsent` is checking essential denials too early from `sessionData`,
which can still trigger `ErrorEssentialConsentDenied` for purposes/elements that
were removed or re-versioned. Move the essential-denial check to after
`buildPurposeElementSet(currentPurposes)` in `service.go`, and filter
`hasEssentialDenials` using `validElements` so only currently allowed
purposes/elements can block consent. Also ensure the same live-allowlist
filtering is applied in the `mergeConsentPurposes`/persistence flow referenced
by the later consent handling block.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/internal/authn/consent/service.go`:
- Around line 180-182: `RecordConsent` is checking essential denials too early
from `sessionData`, which can still trigger `ErrorEssentialConsentDenied` for
purposes/elements that were removed or re-versioned. Move the essential-denial
check to after `buildPurposeElementSet(currentPurposes)` in `service.go`, and
filter `hasEssentialDenials` using `validElements` so only currently allowed
purposes/elements can block consent. Also ensure the same live-allowlist
filtering is applied in the `mergeConsentPurposes`/persistence flow referenced
by the later consent handling block.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c6c5d7ea-d0e6-486e-b10e-6209d9b62935

📥 Commits

Reviewing files that changed from the base of the PR and between ecbc3be and 94dbf89.

📒 Files selected for processing (11)
  • backend/internal/authn/consent/service.go
  • backend/internal/authn/consent/service_test.go
  • backend/internal/consent/ConsentServiceInterface_mock_test.go
  • backend/internal/consent/consentClientInterface_mock_test.go
  • backend/internal/consent/default_client.go
  • backend/internal/consent/default_client_test.go
  • backend/internal/consent/error_constants.go
  • backend/internal/consent/interface.go
  • backend/internal/consent/model.go
  • backend/internal/consent/service.go
  • backend/internal/consent/service_test.go
💤 Files with no reviewable changes (9)
  • backend/internal/consent/model.go
  • backend/internal/consent/error_constants.go
  • backend/internal/consent/service.go
  • backend/internal/consent/consentClientInterface_mock_test.go
  • backend/internal/consent/service_test.go
  • backend/internal/consent/interface.go
  • backend/internal/consent/ConsentServiceInterface_mock_test.go
  • backend/internal/consent/default_client.go
  • backend/internal/consent/default_client_test.go

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

Labels

breaking change The feature/ improvement will alter the existing behaviour Type/Improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants