Skip to content

[OB4] Adding consent manage PUT to update basic consent#914

Closed
Ashi1993 wants to merge 4 commits intowso2:mainfrom
Ashi1993:consent-put
Closed

[OB4] Adding consent manage PUT to update basic consent#914
Ashi1993 wants to merge 4 commits intowso2:mainfrom
Ashi1993:consent-put

Conversation

@Ashi1993
Copy link
Contributor

@Ashi1993 Ashi1993 commented Mar 11, 2026

Adding consent manage PUT to update basic consent

Explain in a few lines the purpose of this pull request

Issue link: required

Doc Issue: Optional, link issue from documentation repository

Applicable Labels: Spec, product, version, type (specify requested labels)


Development Checklist

  1. Build complete solution with pull request in place.
  2. Ran checkstyle plugin with pull request in place.
  3. Ran Findbugs plugin with pull request in place.
  4. Ran FindSecurityBugs plugin and verified report.
  5. Formatted code according to WSO2 code style.
  6. Have you verified the PR doesn't commit any keys, passwords, tokens, usernames, or other secrets?
  7. Migration scripts written (if applicable).
  8. Have you followed secure coding standards in WSO2 Secure Engineering Guidelines?

Testing Checklist

  1. Written unit tests.
  2. Verified tests in multiple database environments (if applicable).
  3. Tested with BI enabled (if applicable).

Summary by CodeRabbit

  • New Features

    • Added comprehensive OpenAPI contract for consent management extensibility with new service endpoints for client, application, consent, token, and authorization workflows.
    • Enabled external service integration for pre- and post-processing consent updates with configurable feature flags.
  • Bug Fixes

    • Corrected SQL join operation for improved consent attribute retrieval.

@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

Walkthrough

This pull request introduces external API consent update extensibility to a financial services accelerator. It adds new OpenAPI contract definitions, deployment configurations, enum constants, data transfer objects, utility methods, core service updates, and handler logic to support pre-processing and post-processing consent updates via external services, along with comprehensive test coverage.

Changes

Cohort / File(s) Summary
OpenAPI API Contract
financial-services-accelerator/accelerators/fs-is/repository/resources/apis/accelerator-extensions-v1.0.4.yaml
New comprehensive OpenAPI 3.0.1 specification defining REST endpoints for accelerator extensibility, including /map-accelerator-error-response, /pre-process-*, /enrich-*, /validate-*, /issue-refresh-token, /validate-authorization-request, /event-*, and /consent-* paths with request/response schemas, security schemes, and error handling.
Deployment Configuration
financial-services-accelerator/accelerators/fs-is/repository/resources/wso2is-7.0.0-deployment.toml, wso2is-7.1.0-deployment.toml, wso2is-7.2.0-deployment.toml
Added pre_process_consent_update and enrich_consent_update_response to the allowed_extensions list in each configuration file to enable new consent update extension points.
Extension Type Enum
financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/extension/model/ServiceExtensionTypeEnum.java
Added two new enum constants: PRE_PROCESS_CONSENT_UPDATE and ENRICH_CONSENT_UPDATE_RESPONSE, with updated delimiter syntax to accommodate the new values.
Database Query
financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.dao/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/dao/queries/ConsentMgtCommonDBQueries.java
Changed SQL join type from RIGHT JOIN to LEFT JOIN in the getGetConsentWithConsentAttributesPreparedStatement method for FS_CONSENT and FS_CONSENT_ATTRIBUTE tables.
External API DTOs
financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/model/ExternalAPIBasicConsentResourceRequestDTO.java, ExternalAPIBasicConsentResourceResponseDTO.java, manage/model/ExternalAPIPreConsentUpdateRequestDTO.java, ExternalAPIPreConsentUpdateResponseDTO.java, ExternalAPIPostConsentUpdateRequestDTO.java
Five new data transfer objects encapsulating consent resource data for external API pre-update and post-update flows, with constructors, getters, and setters.
Consent Extension Utilities
financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java, common/ExternalAPIUtil.java
Added overload for getInitiationResponse accepting ConsentResource and new buildConsentResource method to construct consent resources from external API response DTOs.
Consent Manage Handler
financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/impl/DefaultConsentManageHandler.java
Extended handler with feature toggles for external pre/post consent update flows, new private helper methods for updating consent resources, updated error messages to standardized constants, and additional response/header propagation logic for external service interactions.
Consent Manage Constants & Utils
financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageConstants.java, ConsentManageUtils.java, ExternalAPIConsentManageUtils.java
Added constants INTERNAL_API_REQUEST_HEADER and CLIENT_ID_MISMATCH_ERROR; new getStatus() method in ConsentManageUtils; and two new overloaded callExternalService methods in ExternalAPIConsentManageUtils for consent update flows.
Core Consent Service
financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/service/ConsentCoreService.java, impl/ConsentCoreServiceImpl.java, constants/ConsentCoreServiceConstants.java
Added updateConsent() method to interface and implementation with validation, DAO updates, audit record creation, and transaction handling; introduced BASIC_CONSENT_UPDATE_REASON constant.
Test Utilities & Data Providers
financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/util/DataProviders.java, TestConstants.java, TestUtil.java
Added constants for account consent update path and authorization update payload; new helper methods for generating sample external API DTOs; updated data provider to include validation messages.
Handler & Service Tests
financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/DefaultConsentManageHandlerTest.java, financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/service/impl/ConsentMgtCoreServiceTests.java
Refactored handler tests with local mocks and assertion-based exception handling, expanded coverage for external consent update flows; added three new tests for updateConsent method covering positive path, validation errors, and update failures.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A hop through consent updates bright,
External flows extending right,
Pre-process, enrich, and validate true,
New schemas bloom in OpenAPI's view,
Audit records hop along with glee!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description follows the template structure with multiple checklist sections (Development, Testing). However, it lacks the required issue link, missing explanation of purpose, applicable labels specification, and incomplete testing checklist details. Add the required issue link, provide explanation of PR purpose in 2-3 lines, specify applicable labels (Spec, product, version, type), and complete or document any incomplete testing checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title '[OB4] Adding consent manage PUT to update basic consent' directly and clearly describes the main feature: adding a PUT endpoint to update basic consent. It is concise and reflects the core objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Comment on lines +130 to +132
public static JSONObject getInitiationResponse(Object responseObj, ConsentResource createdConsent) {

JSONObject response = (JSONObject) responseObj;
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 1

Suggested change
public static JSONObject getInitiationResponse(Object responseObj, ConsentResource createdConsent) {
JSONObject response = (JSONObject) responseObj;
public static JSONObject getInitiationResponse(Object responseObj, ConsentResource createdConsent) {
log.info("Constructing initiation response for consent ID: " + createdConsent.getConsentID());
JSONObject response = (JSONObject) responseObj;

Comment on lines +452 to +454
public static ConsentResource buildConsentResource(ExternalAPIBasicConsentResourceResponseDTO basicConsentResource,
String consentID, String clientID, long createTime) {

Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 2

Suggested change
public static ConsentResource buildConsentResource(ExternalAPIBasicConsentResourceResponseDTO basicConsentResource,
String consentID, String clientID, long createTime) {
public static ConsentResource buildConsentResource(ExternalAPIBasicConsentResourceResponseDTO basicConsentResource,
String consentID, String clientID, long createTime) {
log.debug("Building consent resource for consentID: {}, clientID: {}", consentID, clientID);

Comment on lines +459 to +460
basicConsentResource.getRecurringIndicator(), basicConsentResource.getStatus(),
createTime, 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 3

Suggested change
basicConsentResource.getRecurringIndicator(), basicConsentResource.getStatus(),
createTime, 0);
basicConsentResource.getRecurringIndicator(), basicConsentResource.getStatus(),
createTime, 0);
log.info("Successfully built consent resource for consentID: {}", consentID);
return new ConsentResource(consentID, clientID, receipt, basicConsentResource.getType(),

Comment on lines +42 to +45

public ExternalAPIBasicConsentResourceRequestDTO(ConsentResource consentResource) {

this.id = consentResource.getConsentID();
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 4

Suggested change
public ExternalAPIBasicConsentResourceRequestDTO(ConsentResource consentResource) {
this.id = consentResource.getConsentID();
public ExternalAPIBasicConsentResourceRequestDTO(ConsentResource consentResource) {
log.debug("Creating ExternalAPIBasicConsentResourceRequestDTO for consent ID: " + consentResource.getConsentID());
this.id = consentResource.getConsentID();

Comment on lines +54 to +59

if (consentResource.getReceipt() != null && !consentResource.getReceipt().isEmpty()) {
JSONObject receiptJson = new JSONObject(consentResource.getReceipt());
this.receipt = receiptJson.toMap();
} else {
this.receipt = Collections.emptyMap();
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 5

Suggested change
if (consentResource.getReceipt() != null && !consentResource.getReceipt().isEmpty()) {
JSONObject receiptJson = new JSONObject(consentResource.getReceipt());
this.receipt = receiptJson.toMap();
} else {
this.receipt = Collections.emptyMap();
if (consentResource.getReceipt() != null && !consentResource.getReceipt().isEmpty()) {
JSONObject receiptJson = new JSONObject(consentResource.getReceipt());
this.receipt = receiptJson.toMap();
log.debug("Successfully parsed consent receipt for consent ID: " + this.id);
} else {
this.receipt = Collections.emptyMap();
log.debug("No receipt found for consent ID: " + this.id);
}

Comment on lines 162 to 164
try {
ConsentResource consent = consentCoreService.getConsent(consentId, false);
if (consent == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 6

Suggested change
try {
ConsentResource consent = consentCoreService.getConsent(consentId, false);
if (consent == null) {
try {
ConsentResource consent = consentCoreService.getConsent(consentId, false);
log.info("Retrieved consent for ID: {}", consentId.replaceAll("[\r\n]+", " "));
if (consent == null) {

Comment on lines +529 to +531

updatedConsent = updateConsent(consentManageData, consentId, storedConsentResource, consentType);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 7

Suggested change
updatedConsent = updateConsent(consentManageData, consentId, storedConsentResource, consentType);
}
updatedConsent = updateConsent(consentManageData, consentId, storedConsentResource, consentType);
}
log.info("Successfully updated consent with ID: {}", consentId.replaceAll("[\r\n]+", " "));
if (isExtensionsEnabled && isExternalPostConsentUpdateEnabled) {

Comment on lines +33 to +40

public ExternalAPIPostConsentUpdateRequestDTO(ExternalAPIBasicConsentResourceRequestDTO consentResource,
String resourcePath) {

this.consentId = consentResource.getId();
this.consentResource = consentResource;
this.consentResourcePath = resourcePath;

Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 8

Suggested change
public ExternalAPIPostConsentUpdateRequestDTO(ExternalAPIBasicConsentResourceRequestDTO consentResource,
String resourcePath) {
this.consentId = consentResource.getId();
this.consentResource = consentResource;
this.consentResourcePath = resourcePath;
public ExternalAPIPostConsentUpdateRequestDTO(ExternalAPIBasicConsentResourceRequestDTO consentResource,
String resourcePath) {
log.debug("Creating ExternalAPIPostConsentUpdateRequestDTO with consentId: {} and resourcePath: {}",
consentResource != null ? consentResource.getId() : null, resourcePath);
this.consentId = consentResource.getId();
this.consentResource = consentResource;
this.consentResourcePath = resourcePath;
}

Comment on lines +71 to +77
*/
public JSONObject toJson() {

JSONObject dtoJson = new JSONObject(this);
JSONObject consentResourceJson = new JSONObject(consentResource);
dtoJson.put("consentResource", consentResourceJson);
return dtoJson;
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 9

Suggested change
*/
public JSONObject toJson() {
JSONObject dtoJson = new JSONObject(this);
JSONObject consentResourceJson = new JSONObject(consentResource);
dtoJson.put("consentResource", consentResourceJson);
return dtoJson;
public JSONObject toJson() {
if (log.isDebugEnabled()) {
log.debug("Converting ExternalAPIPostConsentUpdateRequestDTO to JSON for consentId: {}", consentId);
}
JSONObject dtoJson = new JSONObject(this);
JSONObject consentResourceJson = new JSONObject(consentResource);
dtoJson.put("consentResource", consentResourceJson);
return dtoJson;
}

Comment on lines +35 to +38

public ExternalAPIPreConsentUpdateRequestDTO(ConsentManageData consentManageData,
ExternalAPIBasicConsentResourceRequestDTO storedConsentResource) {

Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 10

Suggested change
public ExternalAPIPreConsentUpdateRequestDTO(ConsentManageData consentManageData,
ExternalAPIBasicConsentResourceRequestDTO storedConsentResource) {
public ExternalAPIPreConsentUpdateRequestDTO(ConsentManageData consentManageData,
ExternalAPIBasicConsentResourceRequestDTO storedConsentResource) {
if (log.isDebugEnabled()) {
log.debug("Creating ExternalAPIPreConsentUpdateRequestDTO with consent resource path: " +
consentManageData.getRequestPath());
}

* @param payload request payload
* @return status value if present in the request, null otherwise
*/
public static String getStatus(Object payload) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 11

Suggested change
public static String getStatus(Object payload) {
public static String getStatus(Object payload) {
log.debug("Extracting status from consent request payload");

Comment on lines +298 to +300
if (data.has(ConsentExtensionConstants.STATUS)) {
return data.getString(ConsentExtensionConstants.STATUS);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 12

Suggested change
if (data.has(ConsentExtensionConstants.STATUS)) {
return data.getString(ConsentExtensionConstants.STATUS);
}
if (data.has(ConsentExtensionConstants.STATUS)) {
String status = data.getString(ConsentExtensionConstants.STATUS);
log.debug("Status value found in request payload");
return status;
}
log.debug("Status value not present in request payload");

Comment on lines +181 to +185
public static ExternalAPIPreConsentUpdateResponseDTO callExternalService(
ExternalAPIPreConsentUpdateRequestDTO requestDTO) throws FinancialServicesException {

log.debug("Calling external service for pre consent update");
JSONObject requestJson = new JSONObject(requestDTO);
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 13

Suggested change
public static ExternalAPIPreConsentUpdateResponseDTO callExternalService(
ExternalAPIPreConsentUpdateRequestDTO requestDTO) throws FinancialServicesException {
log.debug("Calling external service for pre consent update");
JSONObject requestJson = new JSONObject(requestDTO);
public static ExternalAPIPreConsentUpdateResponseDTO callExternalService(
ExternalAPIPreConsentUpdateRequestDTO requestDTO) throws FinancialServicesException {
log.debug("Calling external service for pre consent update");
log.info("Initiating pre consent update external service call");

Comment on lines +197 to +201
public static ExternalAPIModifiedResponseDTO callExternalService(
ExternalAPIPostConsentUpdateRequestDTO requestDTO) throws FinancialServicesException {

log.debug("Calling external service for post consent update");
JSONObject requestJson = requestDTO.toJson();
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 14

Suggested change
public static ExternalAPIModifiedResponseDTO callExternalService(
ExternalAPIPostConsentUpdateRequestDTO requestDTO) throws FinancialServicesException {
log.debug("Calling external service for post consent update");
JSONObject requestJson = requestDTO.toJson();
public static ExternalAPIModifiedResponseDTO callExternalService(
ExternalAPIPostConsentUpdateRequestDTO requestDTO) throws FinancialServicesException {
log.debug("Calling external service for post consent update");
log.info("Initiating post consent update external service call");

Comment on lines 161 to +171

setConsentManageBuilder();
JSONObject payload = new JSONObject(TestConstants.VALID_INITIATION);
doReturn(payload).when(consentManageDataMock).getPayload();

defaultConsentManageHandler.handlePost(consentManageDataMock);
try {
setConsentManageBuilder();
ConsentManageData consentManageDataMock = mock(ConsentManageData.class);
doReturn(null).when(consentManageDataMock).getClientId();

defaultConsentManageHandler.handlePost(consentManageDataMock);
} catch (ConsentException e) {
Assert.assertEquals(e.getPayload().getJSONObject("error").getString("description"),
"Client ID missing in the request.");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 15

Suggested change
setConsentManageBuilder();
JSONObject payload = new JSONObject(TestConstants.VALID_INITIATION);
doReturn(payload).when(consentManageDataMock).getPayload();
defaultConsentManageHandler.handlePost(consentManageDataMock);
try {
setConsentManageBuilder();
ConsentManageData consentManageDataMock = mock(ConsentManageData.class);
doReturn(null).when(consentManageDataMock).getClientId();
defaultConsentManageHandler.handlePost(consentManageDataMock);
} catch (ConsentException e) {
Assert.assertEquals(e.getPayload().getJSONObject("error").getString("description"),
"Client ID missing in the request.");
}
try {
log.info("Handling POST request for consent management. Client ID: {}", consentManageDataMock.getClientId());
setConsentManageBuilder();
ConsentManageData consentManageDataMock = mock(ConsentManageData.class);
doReturn(null).when(consentManageDataMock).getClientId();
defaultConsentManageHandler.handlePost(consentManageDataMock);
} catch (ConsentException e) {
log.error("Failed to handle POST request due to missing client ID: {}", e.getMessage());
Assert.assertEquals(e.getPayload().getJSONObject("error").getString("description"),
"Client ID missing in the request.");
}

Comment on lines 611 to +623
public void testHandleFileGetWithoutRequestPath() {

setConsentManageBuilder();
doReturn(TestConstants.SAMPLE_CLIENT_ID).when(consentManageDataMock).getClientId();
doReturn(null).when(consentManageDataMock).getRequestPath();

defaultConsentManageHandler.handleFileGet(consentManageDataMock);
try {
setConsentManageBuilder();
ConsentManageData consentManageDataMock = mock(ConsentManageData.class);
doReturn(TestConstants.SAMPLE_CLIENT_ID).when(consentManageDataMock).getClientId();
doReturn(null).when(consentManageDataMock).getRequestPath();

defaultConsentManageHandler.handleFileGet(consentManageDataMock);
} catch (ConsentException e) {
Assert.assertEquals(e.getPayload().getJSONObject("error").getString("description"),
"Resource path not found in the request");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 16

Suggested change
public void testHandleFileGetWithoutRequestPath() {
setConsentManageBuilder();
doReturn(TestConstants.SAMPLE_CLIENT_ID).when(consentManageDataMock).getClientId();
doReturn(null).when(consentManageDataMock).getRequestPath();
defaultConsentManageHandler.handleFileGet(consentManageDataMock);
try {
setConsentManageBuilder();
ConsentManageData consentManageDataMock = mock(ConsentManageData.class);
doReturn(TestConstants.SAMPLE_CLIENT_ID).when(consentManageDataMock).getClientId();
doReturn(null).when(consentManageDataMock).getRequestPath();
defaultConsentManageHandler.handleFileGet(consentManageDataMock);
} catch (ConsentException e) {
Assert.assertEquals(e.getPayload().getJSONObject("error").getString("description"),
"Resource path not found in the request");
}
try {
log.info("Handling GET request for file consent. Client ID: {}, Request Path: {}",
consentManageDataMock.getClientId(), consentManageDataMock.getRequestPath());
setConsentManageBuilder();
ConsentManageData consentManageDataMock = mock(ConsentManageData.class);
doReturn(TestConstants.SAMPLE_CLIENT_ID).when(consentManageDataMock).getClientId();
doReturn(null).when(consentManageDataMock).getRequestPath();
defaultConsentManageHandler.handleFileGet(consentManageDataMock);
} catch (ConsentException e) {
log.error("Failed to handle file GET request due to missing request path: {}", e.getMessage());
Assert.assertEquals(e.getPayload().getJSONObject("error").getString("description"),
"Resource path not found in the request");
}

Comment on lines +265 to +268
public static ExternalAPIPreConsentUpdateResponseDTO getSampleExternalAPIPreConsentUpdateResponseDTO() {

ExternalAPIPreConsentUpdateResponseDTO responseDTO = new ExternalAPIPreConsentUpdateResponseDTO();

Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 17

Suggested change
public static ExternalAPIPreConsentUpdateResponseDTO getSampleExternalAPIPreConsentUpdateResponseDTO() {
ExternalAPIPreConsentUpdateResponseDTO responseDTO = new ExternalAPIPreConsentUpdateResponseDTO();
public static ExternalAPIPreConsentUpdateResponseDTO getSampleExternalAPIPreConsentUpdateResponseDTO() {
ExternalAPIPreConsentUpdateResponseDTO responseDTO = new ExternalAPIPreConsentUpdateResponseDTO();
log.debug("Creating sample ExternalAPIPreConsentUpdateResponseDTO for testing");

Comment on lines +290 to +292
public static ExternalAPIModifiedResponseDTO getSampleExternalAPIModifiedResponseDTO() {

ExternalAPIModifiedResponseDTO responseDTO = new ExternalAPIModifiedResponseDTO();
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 18

Suggested change
public static ExternalAPIModifiedResponseDTO getSampleExternalAPIModifiedResponseDTO() {
ExternalAPIModifiedResponseDTO responseDTO = new ExternalAPIModifiedResponseDTO();
public static ExternalAPIModifiedResponseDTO getSampleExternalAPIModifiedResponseDTO() {
ExternalAPIModifiedResponseDTO responseDTO = new ExternalAPIModifiedResponseDTO();
log.debug("Creating sample ExternalAPIModifiedResponseDTO for testing");

Comment on lines +283 to +289
@Override
public ConsentResource updateConsent(ConsentResource consentResource) throws ConsentManagementException {

if (consentResource == null || StringUtils.isBlank(consentResource.getConsentID())) {
log.error("Consent or consentId is missing");
throw new ConsentManagementException("Consent or consentId is missing");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 19

Suggested change
@Override
public ConsentResource updateConsent(ConsentResource consentResource) throws ConsentManagementException {
if (consentResource == null || StringUtils.isBlank(consentResource.getConsentID())) {
log.error("Consent or consentId is missing");
throw new ConsentManagementException("Consent or consentId is missing");
}
public ConsentResource updateConsent(ConsentResource consentResource) throws ConsentManagementException {
if (consentResource == null || StringUtils.isBlank(consentResource.getConsentID())) {
log.error("Consent or consentId is missing");
throw new ConsentManagementException("Consent or consentId is missing");
}
log.info("Updating consent with ID: " + consentResource.getConsentID());

Comment on lines +311 to +316
DatabaseUtils.commitTransaction(connection);
log.debug("Updated consent successfully.");
return consentResource;

} catch (ConsentDataInsertionException | ConsentDataUpdationException e) {
log.error("Error during updating consent, rolling back", e);
Copy link
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 20

Suggested change
DatabaseUtils.commitTransaction(connection);
log.debug("Updated consent successfully.");
return consentResource;
} catch (ConsentDataInsertionException | ConsentDataUpdationException e) {
log.error("Error during updating consent, rolling back", e);
DatabaseUtils.commitTransaction(connection);
log.debug("Updated consent successfully.");
return consentResource;
} catch (ConsentDataInsertionException | ConsentDataUpdationException e) {
log.error("Error during updating consent, rolling back: " + e.getMessage());

Copy link
Contributor

@wso2-engineering wso2-engineering bot left a comment

Choose a reason for hiding this comment

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

AI Agent Log Improvement Checklist

⚠️ Warning: AI-Generated Review Comments

  • The log-related comments and suggestions in this review were generated by an AI tool to assist with identifying potential improvements. Purpose of reviewing the code for log improvements is to improve the troubleshooting capabilities of our products.
  • Please make sure to manually review and validate all suggestions before applying any changes. Not every code suggestion would make sense or add value to our purpose. Therefore, you have the freedom to decide which of the suggestions are helpful.

✅ Before merging this pull request:

  • Review all AI-generated comments for accuracy and relevance.
  • Complete and verify the table below. We need your feedback to measure the accuracy of these suggestions and the value they add. If you are rejecting a certain code suggestion, please mention the reason briefly in the suggestion for us to capture it.
Comment Accepted (Y/N) Reason
#### Log Improvement Suggestion No: 1
#### Log Improvement Suggestion No: 2
#### Log Improvement Suggestion No: 3
#### Log Improvement Suggestion No: 4
#### Log Improvement Suggestion No: 5
#### Log Improvement Suggestion No: 6
#### Log Improvement Suggestion No: 7
#### Log Improvement Suggestion No: 8
#### Log Improvement Suggestion No: 9
#### Log Improvement Suggestion No: 10
#### Log Improvement Suggestion No: 11
#### Log Improvement Suggestion No: 12
#### Log Improvement Suggestion No: 13
#### Log Improvement Suggestion No: 14
#### Log Improvement Suggestion No: 15
#### Log Improvement Suggestion No: 16
#### Log Improvement Suggestion No: 17
#### Log Improvement Suggestion No: 18
#### Log Improvement Suggestion No: 19
#### Log Improvement Suggestion No: 20

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (2)
financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java (1)

293-302: Javadoc promises null return, but method throws exception when "Data" key is missing.

The Javadoc states "If not present, return null" but getJSONObject(DATA) at line 296 throws JSONException if the "Data" key is absent. The null-return guarantee only applies to the nested "Status" field. This is consistent with the existing getValidityTime method pattern, but the documentation could mislead callers.

Consider either:

  1. Updating the Javadoc to clarify that the payload must contain a "Data" object, or
  2. Adding defensive checks for consistency with the documented contract.
Option 1: Clarify Javadoc
     /**
-     * Get status value from the request payload. If not present, return null.
+     * Get status value from the request payload. If Status is not present within the Data object, return null.
+     * The payload must contain a "Data" JSON object; otherwise a JSONException is thrown.
      *
      * `@param` payload      request payload
      * `@return`  status value if present in the request, null otherwise
+     * `@throws` org.json.JSONException if payload is missing "Data" key
      */
Option 2: Add defensive check (aligns with Javadoc promise)
     public static String getStatus(Object payload) {
 
         JSONObject requestPayload = (JSONObject) payload;
-        JSONObject data = requestPayload.getJSONObject(ConsentExtensionConstants.DATA);
+        if (!requestPayload.has(ConsentExtensionConstants.DATA)) {
+            return null;
+        }
+        JSONObject data = requestPayload.getJSONObject(ConsentExtensionConstants.DATA);
 
         if (data.has(ConsentExtensionConstants.STATUS)) {
             return data.getString(ConsentExtensionConstants.STATUS);
         }
         return null;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java`
around lines 293 - 302, The getStatus method currently calls
requestPayload.getJSONObject(ConsentExtensionConstants.DATA) which throws if the
"Data" key is missing, contradicting the Javadoc that promises a null return;
update getStatus to be defensive: verify payload is a JSONObject and that
requestPayload.has(ConsentExtensionConstants.DATA) (and that the DATA value is a
JSONObject) before calling getJSONObject, then check for
ConsentExtensionConstants.STATUS and return its string or null; mirror the
defensive pattern used in getValidityTime so callers get null instead of an
exception when "Data" or "Status" is absent.
financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java (1)

130-148: Extract the shared initiation-response mutation into one helper.

Both overloads now carry the same DATA mutation logic, so the next field added here can easily land in only one path. Prefer delegating both overloads to a single private helper and keep the overload-specific behavior limited to logging.

♻️ Suggested refactor
 public static JSONObject getInitiationResponse(Object responseObj, DetailedConsentResource createdConsent) {
-    JSONObject response = (JSONObject) responseObj;
-    JSONObject dataObject = response.getJSONObject(ConsentExtensionConstants.DATA);
-    dataObject.put(ConsentExtensionConstants.CONSENT_ID, createdConsent.getConsentID());
-    dataObject.put(ConsentExtensionConstants.CREATION_DATE_TIME, convertToISO8601(createdConsent.getCreatedTime()));
-    dataObject.put(ConsentExtensionConstants.STATUS_UPDATE_DATE_TIME,
-            convertToISO8601(createdConsent.getUpdatedTime()));
-    dataObject.put(ConsentExtensionConstants.STATUS, createdConsent.getCurrentStatus());
-
-    response.remove(ConsentExtensionConstants.DATA);
-    response.put(ConsentExtensionConstants.DATA, dataObject);
-
-    return response;
+    return populateInitiationResponse((JSONObject) responseObj, createdConsent.getConsentID(),
+            createdConsent.getCreatedTime(), createdConsent.getUpdatedTime(), createdConsent.getCurrentStatus());
 }
 
 public static JSONObject getInitiationResponse(Object responseObj, ConsentResource createdConsent) {
-
-    JSONObject response = (JSONObject) responseObj;
-    JSONObject dataObject = response.getJSONObject(ConsentExtensionConstants.DATA);
-    dataObject.put(ConsentExtensionConstants.CONSENT_ID, createdConsent.getConsentID());
-    dataObject.put(ConsentExtensionConstants.CREATION_DATE_TIME, convertToISO8601(createdConsent.getCreatedTime()));
-    dataObject.put(ConsentExtensionConstants.STATUS_UPDATE_DATE_TIME,
-            convertToISO8601(createdConsent.getUpdatedTime()));
-    dataObject.put(ConsentExtensionConstants.STATUS, createdConsent.getCurrentStatus());
-
-    response.remove(ConsentExtensionConstants.DATA);
-    response.put(ConsentExtensionConstants.DATA, dataObject);
+    JSONObject response = populateInitiationResponse((JSONObject) responseObj, createdConsent.getConsentID(),
+            createdConsent.getCreatedTime(), createdConsent.getUpdatedTime(), createdConsent.getCurrentStatus());
 
     if (log.isDebugEnabled()) {
         log.debug(String.format("Initiation response constructed for consent ID: %s with status: %s",
                 createdConsent.getConsentID().replaceAll("[\r\n]", ""),
                 createdConsent.getCurrentStatus().replaceAll("[\r\n]", "")));
     }
     return response;
 }
+
+private static JSONObject populateInitiationResponse(JSONObject response, String consentId, long createdTime,
+                                                     long updatedTime, String status) {
+    JSONObject dataObject = response.getJSONObject(ConsentExtensionConstants.DATA);
+    dataObject.put(ConsentExtensionConstants.CONSENT_ID, consentId);
+    dataObject.put(ConsentExtensionConstants.CREATION_DATE_TIME, convertToISO8601(createdTime));
+    dataObject.put(ConsentExtensionConstants.STATUS_UPDATE_DATE_TIME, convertToISO8601(updatedTime));
+    dataObject.put(ConsentExtensionConstants.STATUS, status);
+    response.put(ConsentExtensionConstants.DATA, dataObject);
+    return response;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java`
around lines 130 - 148, The DATA mutation logic in getInitiationResponse is
duplicated across overloads; create a single private helper (e.g.,
populateInitiationData or buildInitiationData) that accepts the JSONObject
response and ConsentResource createdConsent, performs the dataObject retrieval
and updates (CONSENT_ID, CREATION_DATE_TIME via convertToISO8601,
STATUS_UPDATE_DATE_TIME, STATUS) and replaces the DATA entry, then return the
mutated JSONObject. Change both getInitiationResponse overloads to delegate to
that helper and keep only the overload-specific logging (with the same
.replaceAll sanitization) in each overload.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@financial-services-accelerator/accelerators/fs-is/repository/resources/apis/accelerator-extensions-v1.0.4.yaml`:
- Around line 1-11: Update the OpenAPI info.version field from "v1.0.3" to
"v1.0.4" so the declared contract version matches the bumped spec; locate and
edit the info.version entry in the OpenAPI YAML (the line currently set to
v1.0.3) to v1.0.4 and verify there are no other stale version strings in the
file such as in documentation metadata or examples.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.dao/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/dao/queries/ConsentMgtCommonDBQueries.java`:
- Around line 44-45: The LEFT JOIN change returns rows where
FS_CONSENT_ATTRIBUTE columns can be null, so update the mapper
ConsentManagementDAOUtil.setDataToConsentResourceWithAttributes(...) to guard
against null attribute rows: when ATT_KEY and/or ATT_VALUE are null, do not
insert an entry into the attributes map and ensure the map remains empty (or
absent) for attribute-less consents; locate where ATT_KEY/ATT_VALUE are read and
add a null-check (or Optional check) before calling map.put(...) so the query
change does not produce a {null=null} entry.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ExternalAPIUtil.java`:
- Around line 452-460: The buildConsentResource method currently reconstructs a
ConsentResource solely from ExternalAPIBasicConsentResourceResponseDTO
(basicConsentResource) and defaults a missing receipt to "{}", which overwrites
existing stored consent data on partial updates; change buildConsentResource (or
add an overload) to accept the existing ConsentResource (or load it by
consentID) and merge values: for each field on basicConsentResource only
overwrite when the DTO field is non-null/explicitly provided, preserve existing
ConsentResource values otherwise, and for receipt parse and merge JSON objects
rather than replacing with "{}" when receipt is missing; ensure the returned
ConsentResource keeps unchanged fields from the stored consent and only updates
fields present in basicConsentResource.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/impl/DefaultConsentManageHandler.java`:
- Around line 468-488: The PUT/update path in DefaultConsentManageHandler is
missing the standard header validation call; ensure you invoke the existing
validateRequestHeaders(...) method (the same one used by the GET/POST/DELETE
handlers) early in the PUT flow—e.g., right after entering the handler or before
any clientId/resource-path checks using consentManageData—to run the same header
checks and abort with the appropriate error if headers are invalid.
- Around line 826-839: The updateConsent method builds a new ConsentResource but
fails to preserve existing basic-consent fields (notably consentFrequency and
recurringIndicator) from storedConsentResource, causing local PUTs to overwrite
them; modify updateConsent (the code that constructs ConsentResource before
calling consentCoreService.updateConsent) to copy consentFrequency and
recurringIndicator (and any other non-requested basic fields) from
storedConsentResource into the new ConsentResource instance so those values are
preserved when persisted; ensure the fields are set on the consentResource prior
to calling consentCoreService.updateConsent.
- Around line 493-530: In DefaultConsentManageHandler, before validating and
updating, compare the consent type derived from the request path/payload with
the stored consent's type (storedConsentResource.getConsentType()) and reject
the update if they differ; specifically, after retrieving storedConsentResource
and before calling
ConsentManageUtils.getConsentManageValidator().validateRequestPayload(...)
obtain the request consent type (e.g., via
ConsentManageUtils.getConsentManageValidator().getConsentType(consentManageData)
or equivalent) and if
(!requestConsentType.equals(storedConsentResource.getConsentType())) log an
appropriate error and throw a ConsentException (ResponseStatus.BAD_REQUEST) to
prevent persisting mismatched types (this affects the branch that calls
updateConsent(consentManageData, consentId, storedConsentResource,
consentType)).

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/DefaultConsentManageHandlerTest.java`:
- Around line 159-172: The test methods (e.g., testHandlePostWithoutClientId in
DefaultConsentManageHandlerTest) currently catch ConsentException but do not
fail if no exception is thrown; update each such test (including the ~27 listed
like testHandlePostWithInvalidHeaders, testHandleGetWithoutClientId,
testHandleDeleteWithoutClientId, testHandlePutWithInvalidConsentId,
testHandlePatch, etc.) by inserting an explicit failure immediately after
invoking the handler (e.g., after defaultConsentManageHandler.handlePost(...) /
handleGet(...) / handleDelete(...) / handlePut(...) / handleFilePost(...)) using
Assert.fail("Expected ConsentException not thrown") so the test fails when the
expected ConsentException is not raised, and keep the existing catch block
assertions unchanged to validate the exception payload when thrown.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/util/DataProviders.java`:
- Line 33: The test maps INITIATION_WITHOUT_ACCOUNT_PERMISSION to the wrong
expected message because the fixture contains invalid JSON (unquoted
ReadBeneficiariesDetail) and will fail during payload parsing; update the
DataProviders entry for INITIATION_WITHOUT_ACCOUNT_PERMISSION to either (a)
change the expected message from "Permissions are invalid" to the parsing error
message your parser produces (so the test asserts the payload parse failure), or
(b) if you intended to test semantic permission validation, fix the fixture JSON
so ReadBeneficiariesDetail is quoted (making it valid JSON) and keep the
"Permissions are invalid" expectation; locate the mapping in class DataProviders
where INITIATION_WITHOUT_ACCOUNT_PERMISSION is defined and apply one of these
two fixes.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/service/ConsentCoreService.java`:
- Around line 123-132: The updateConsent(ConsentResource consentResource) API
lacks an explicit acting user parameter which prevents proper audit attribution;
modify the ConsentCoreService interface to add an actor parameter (e.g., String
actingUserId or a UserContext/Actor object) to the updateConsent method
signature, update all implementations of ConsentCoreService.updateConsent to
accept and propagate this actor, and ensure the audit record creation logic in
those implementations uses the new acting user value instead of pulling user
info from ConsentResource or context; also update all callers to pass the
current authenticated user when invoking updateConsent so the contract clearly
requires the actor for auditing.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/service/impl/ConsentCoreServiceImpl.java`:
- Around line 296-307: The code calls getConsent(...) and assigns to
previousConsent then immediately dereferences
previousConsent.getCurrentStatus(), which can NPE if getConsent returns null; in
ConsentCoreServiceImpl, check previousConsent for null right after calling
getConsent (before calling consentCoreDAO.updateConsentResource and before using
previousConsent.getCurrentStatus()) and if null throw a controlled
ConsentManagementException (with an appropriate message/enum) or return an error
path; ensure the null-check happens before invoking
ConsentCoreServiceUtil.postStateChange so postStateChange always receives a
valid previous status.
- Around line 299-318: After calling
consentCoreDAO.updateConsentResource(connection, consentResource) retrieve the
persisted record (e.g., call consentCoreDAO.getConsentResourceById or
equivalent) into a new variable (persistedConsent) and use persistedConsent
instead of the inbound consentResource when building consentDataMap, passing
clientID, and for the returned value; call
ConsentCoreServiceUtil.postStateChange with persistedConsent.getConsentID() and
persistedConsent.getCurrentStatus() (and previousConsent as before), commit the
transaction and return persistedConsent to ensure audit and response reflect
stored immutable fields.

---

Nitpick comments:
In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java`:
- Around line 130-148: The DATA mutation logic in getInitiationResponse is
duplicated across overloads; create a single private helper (e.g.,
populateInitiationData or buildInitiationData) that accepts the JSONObject
response and ConsentResource createdConsent, performs the dataObject retrieval
and updates (CONSENT_ID, CREATION_DATE_TIME via convertToISO8601,
STATUS_UPDATE_DATE_TIME, STATUS) and replaces the DATA entry, then return the
mutated JSONObject. Change both getInitiationResponse overloads to delegate to
that helper and keep only the overload-specific logging (with the same
.replaceAll sanitization) in each overload.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java`:
- Around line 293-302: The getStatus method currently calls
requestPayload.getJSONObject(ConsentExtensionConstants.DATA) which throws if the
"Data" key is missing, contradicting the Javadoc that promises a null return;
update getStatus to be defensive: verify payload is a JSONObject and that
requestPayload.has(ConsentExtensionConstants.DATA) (and that the DATA value is a
JSONObject) before calling getJSONObject, then check for
ConsentExtensionConstants.STATUS and return its string or null; mirror the
defensive pattern used in getValidityTime so callers get null instead of an
exception when "Data" or "Status" is absent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 62429a81-b4fc-46eb-91be-cca2ebc21153

📥 Commits

Reviewing files that changed from the base of the PR and between d08c5ec and d32ad4a.

📒 Files selected for processing (25)
  • financial-services-accelerator/accelerators/fs-is/repository/resources/apis/accelerator-extensions-v1.0.4.yaml
  • financial-services-accelerator/accelerators/fs-is/repository/resources/wso2is-7.0.0-deployment.toml
  • financial-services-accelerator/accelerators/fs-is/repository/resources/wso2is-7.1.0-deployment.toml
  • financial-services-accelerator/accelerators/fs-is/repository/resources/wso2is-7.2.0-deployment.toml
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/extension/model/ServiceExtensionTypeEnum.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.dao/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/dao/queries/ConsentMgtCommonDBQueries.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ExternalAPIUtil.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/model/ExternalAPIBasicConsentResourceRequestDTO.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/model/ExternalAPIBasicConsentResourceResponseDTO.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/impl/DefaultConsentManageHandler.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/model/ExternalAPIPostConsentUpdateRequestDTO.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/model/ExternalAPIPreConsentUpdateRequestDTO.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/model/ExternalAPIPreConsentUpdateResponseDTO.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageConstants.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ExternalAPIConsentManageUtils.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/DefaultConsentManageHandlerTest.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/util/DataProviders.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/util/TestConstants.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/util/TestUtil.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/service/ConsentCoreService.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/service/constants/ConsentCoreServiceConstants.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/service/impl/ConsentCoreServiceImpl.java
  • financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/service/impl/ConsentMgtCoreServiceTests.java

Comment on lines +1 to +11
openapi: 3.0.1
info:
title: API contract for financial accelerator extension points in WSO2 IS and APIM
description: This API defines the REST API contract for services that implements logic to extend the Open Data accelerator flow.
contact:
name: WSO2
url: https://wso2.com/solutions/financial-services/open-banking/
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
version: v1.0.3
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Bump the declared contract version to v1.0.4.

This new v1.0.4 contract still advertises v1.0.3 in info.version, so generated docs/SDKs will publish the new consent-update surface under the old version.

📝 Proposed fix
-  version: v1.0.3
+  version: v1.0.4
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
openapi: 3.0.1
info:
title: API contract for financial accelerator extension points in WSO2 IS and APIM
description: This API defines the REST API contract for services that implements logic to extend the Open Data accelerator flow.
contact:
name: WSO2
url: https://wso2.com/solutions/financial-services/open-banking/
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
version: v1.0.3
openapi: 3.0.1
info:
title: API contract for financial accelerator extension points in WSO2 IS and APIM
description: This API defines the REST API contract for services that implements logic to extend the Open Data accelerator flow.
contact:
name: WSO2
url: https://wso2.com/solutions/financial-services/open-banking/
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
version: v1.0.4
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/accelerators/fs-is/repository/resources/apis/accelerator-extensions-v1.0.4.yaml`
around lines 1 - 11, Update the OpenAPI info.version field from "v1.0.3" to
"v1.0.4" so the declared contract version matches the bumped spec; locate and
edit the info.version entry in the OpenAPI YAML (the line currently set to
v1.0.3) to v1.0.4 and verify there are no other stale version strings in the
file such as in documentation metadata or examples.

Comment on lines +44 to 45
"FS_CONSENT_ATTRIBUTE.ATT_KEY, FS_CONSENT_ATTRIBUTE.ATT_VALUE FROM FS_CONSENT LEFT JOIN " +
"FS_CONSENT_ATTRIBUTE ON FS_CONSENT.CONSENT_ID = FS_CONSENT_ATTRIBUTE.CONSENT_ID WHERE FS_CONSENT" +
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle null attribute rows before switching this to LEFT JOIN.

Lines 44-45 now return a consent even when no FS_CONSENT_ATTRIBUTE row exists, but ConsentManagementDAOUtil.setDataToConsentResourceWithAttributes(...) currently inserts ATT_KEY/ATT_VALUE into the map without a null check. That means attribute-less consents come back with a {null=null} entry instead of an empty attribute map. Please update the mapper in the same change, otherwise this query change breaks the no-attributes path.

Suggested follow-up fix in the mapper
 while (resultSet.next()) {
-    retrievedConsentAttributeMap.put(resultSet.getString(ConsentMgtDAOConstants.ATT_KEY),
-            resultSet.getString(ConsentMgtDAOConstants.ATT_VALUE));
+    String attributeKey = resultSet.getString(ConsentMgtDAOConstants.ATT_KEY);
+    if (attributeKey != null) {
+        retrievedConsentAttributeMap.put(attributeKey,
+                resultSet.getString(ConsentMgtDAOConstants.ATT_VALUE));
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.dao/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/dao/queries/ConsentMgtCommonDBQueries.java`
around lines 44 - 45, The LEFT JOIN change returns rows where
FS_CONSENT_ATTRIBUTE columns can be null, so update the mapper
ConsentManagementDAOUtil.setDataToConsentResourceWithAttributes(...) to guard
against null attribute rows: when ATT_KEY and/or ATT_VALUE are null, do not
insert an entry into the attributes map and ensure the map remains empty (or
absent) for attribute-less consents; locate where ATT_KEY/ATT_VALUE are read and
add a null-check (or Optional check) before calling map.put(...) so the query
change does not produce a {null=null} entry.

Comment on lines +452 to +460
public static ConsentResource buildConsentResource(ExternalAPIBasicConsentResourceResponseDTO basicConsentResource,
String consentID, String clientID, long createTime) {

String receipt = basicConsentResource.getReceipt() != null ?
new JSONObject(basicConsentResource.getReceipt()).toString() : "{}";
return new ConsentResource(consentID, clientID, receipt, basicConsentResource.getType(),
basicConsentResource.getFrequency(), basicConsentResource.getValidityTime(),
basicConsentResource.getRecurringIndicator(), basicConsentResource.getStatus(),
createTime, 0);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t treat the external pre-update payload as a full replacement.

Line 455 defaults a missing receipt to {}, and the ConsentResource returned on Line 457 is built solely from the extension response. If the extension only returns the fields it changed, the rest of the stored basic-consent state will be overwritten on update. Merge with the existing consent instead of rebuilding from scratch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ExternalAPIUtil.java`
around lines 452 - 460, The buildConsentResource method currently reconstructs a
ConsentResource solely from ExternalAPIBasicConsentResourceResponseDTO
(basicConsentResource) and defaults a missing receipt to "{}", which overwrites
existing stored consent data on partial updates; change buildConsentResource (or
add an overload) to accept the existing ConsentResource (or load it by
consentID) and merge values: for each field on basicConsentResource only
overwrite when the DTO field is non-null/explicitly provided, preserve existing
ConsentResource values otherwise, and for receipt parse and merge JSON objects
rather than replacing with "{}" when receipt is missing; ensure the returned
ConsentResource keeps unchanged fields from the stored consent and only updates
fields present in basicConsentResource.

Comment on lines +468 to +488
//Check whether client ID exists
if (StringUtils.isEmpty(consentManageData.getClientId())) {
log.error("Client ID missing in the request.");
throw new ConsentException(ResponseStatus.BAD_REQUEST, "Client ID missing in the request.",
ConsentOperationEnum.CONSENT_UPDATE);
}

//Check whether client ID is valid
if (!FinancialServicesUtils.isValidClientId(consentManageData.getClientId())) {
log.error("Client ID does not exist in the system.");
throw new ConsentException(ResponseStatus.BAD_REQUEST, "Client ID does not exist in the system.",
ConsentOperationEnum.CONSENT_UPDATE);
}

if (consentManageData.getRequestPath() == null) {
log.error("Resource Path Not Found");
throw new ConsentException(ResponseStatus.BAD_REQUEST, "Resource Path Not Found",
ConsentOperationEnum.CONSENT_UPDATE);
}

String consentId = ConsentManageUtils.extractConsentIdFromPath(consentManageData.getRequestPath(),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Run the standard header validation on PUT requests too.

This new path never calls validateRequestHeaders, unlike the existing GET/POST/DELETE consent handlers. That makes update requests bypass the same header checks enforced on the rest of the base consent flow.

🛡️ Proposed fix
         if (!FinancialServicesUtils.isValidClientId(consentManageData.getClientId())) {
             log.error("Client ID does not exist in the system.");
             throw new ConsentException(ResponseStatus.BAD_REQUEST, "Client ID does not exist in the system.",
                     ConsentOperationEnum.CONSENT_UPDATE);
         }
+
+        ConsentPayloadValidationResult headerValidationResult = ConsentManageUtils.getConsentManageValidator()
+                .validateRequestHeaders(consentManageData);
+        if (!headerValidationResult.isValid()) {
+            log.error(headerValidationResult.getErrorMessage().replaceAll("[\r\n]+", " "));
+            throw new ConsentException(headerValidationResult.getHttpCode(),
+                    headerValidationResult.getErrorMessage(), ConsentOperationEnum.CONSENT_UPDATE);
+        }
 
         if (consentManageData.getRequestPath() == null) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//Check whether client ID exists
if (StringUtils.isEmpty(consentManageData.getClientId())) {
log.error("Client ID missing in the request.");
throw new ConsentException(ResponseStatus.BAD_REQUEST, "Client ID missing in the request.",
ConsentOperationEnum.CONSENT_UPDATE);
}
//Check whether client ID is valid
if (!FinancialServicesUtils.isValidClientId(consentManageData.getClientId())) {
log.error("Client ID does not exist in the system.");
throw new ConsentException(ResponseStatus.BAD_REQUEST, "Client ID does not exist in the system.",
ConsentOperationEnum.CONSENT_UPDATE);
}
if (consentManageData.getRequestPath() == null) {
log.error("Resource Path Not Found");
throw new ConsentException(ResponseStatus.BAD_REQUEST, "Resource Path Not Found",
ConsentOperationEnum.CONSENT_UPDATE);
}
String consentId = ConsentManageUtils.extractConsentIdFromPath(consentManageData.getRequestPath(),
//Check whether client ID exists
if (StringUtils.isEmpty(consentManageData.getClientId())) {
log.error("Client ID missing in the request.");
throw new ConsentException(ResponseStatus.BAD_REQUEST, "Client ID missing in the request.",
ConsentOperationEnum.CONSENT_UPDATE);
}
//Check whether client ID is valid
if (!FinancialServicesUtils.isValidClientId(consentManageData.getClientId())) {
log.error("Client ID does not exist in the system.");
throw new ConsentException(ResponseStatus.BAD_REQUEST, "Client ID does not exist in the system.",
ConsentOperationEnum.CONSENT_UPDATE);
}
ConsentPayloadValidationResult headerValidationResult = ConsentManageUtils.getConsentManageValidator()
.validateRequestHeaders(consentManageData);
if (!headerValidationResult.isValid()) {
log.error(headerValidationResult.getErrorMessage().replaceAll("[\r\n]+", " "));
throw new ConsentException(headerValidationResult.getHttpCode(),
headerValidationResult.getErrorMessage(), ConsentOperationEnum.CONSENT_UPDATE);
}
if (consentManageData.getRequestPath() == null) {
log.error("Resource Path Not Found");
throw new ConsentException(ResponseStatus.BAD_REQUEST, "Resource Path Not Found",
ConsentOperationEnum.CONSENT_UPDATE);
}
String consentId = ConsentManageUtils.extractConsentIdFromPath(consentManageData.getRequestPath(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/impl/DefaultConsentManageHandler.java`
around lines 468 - 488, The PUT/update path in DefaultConsentManageHandler is
missing the standard header validation call; ensure you invoke the existing
validateRequestHeaders(...) method (the same one used by the GET/POST/DELETE
handlers) early in the PUT flow—e.g., right after entering the handler or before
any clientId/resource-path checks using consentManageData—to run the same header
checks and abort with the appropriate error if headers are invalid.

Comment on lines +493 to +530
ConsentResource storedConsentResource = consentCoreService.getConsent(consentId, true);

if (storedConsentResource == null) {
log.error("Consent not found");
throw new ConsentException(ResponseStatus.BAD_REQUEST, "Consent not found",
ConsentOperationEnum.CONSENT_UPDATE);
}

if (!storedConsentResource.getClientID().equals(consentManageData.getClientId())) {
//Throwing this error in a generic manner since client will not be able to identify if consent
// exists if consent does not belong to them
log.error(ConsentManageConstants.CLIENT_ID_MISMATCH_ERROR);
throw new ConsentException(ResponseStatus.BAD_REQUEST, ConsentManageConstants.CLIENT_ID_MISMATCH_ERROR,
ConsentOperationEnum.CONSENT_UPDATE);
}

ConsentResource updatedConsent;
if (isExtensionsEnabled && isExternalPreConsentUpdateEnabled) {
// Call external service before updating consent
ExternalAPIBasicConsentResourceRequestDTO externalAPIConsentResource =
new ExternalAPIBasicConsentResourceRequestDTO(storedConsentResource);
ExternalAPIPreConsentUpdateRequestDTO preRequestDTO =
new ExternalAPIPreConsentUpdateRequestDTO(consentManageData, externalAPIConsentResource);
ExternalAPIPreConsentUpdateResponseDTO preResponseDTO = ExternalAPIConsentManageUtils.
callExternalService(preRequestDTO);
updatedConsent = updateConsent(preResponseDTO, storedConsentResource);
} else {
String consentType = ConsentManageUtils.getConsentManageValidator().getConsentType(consentManageData);
//Validate Initiation request
ConsentPayloadValidationResult validationResponse = ConsentManageUtils.getConsentManageValidator()
.validateRequestPayload(consentManageData, consentType);
if (!validationResponse.isValid()) {
log.error(validationResponse.getErrorMessage().replaceAll("[\r\n]+", " "));
throw new ConsentException(validationResponse.getHttpCode(), validationResponse.getErrorCode(),
validationResponse.getErrorMessage());
}

updatedConsent = updateConsent(consentManageData, consentId, storedConsentResource, consentType);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject updates when the URL consent type does not match the stored consent.

Unlike GET/DELETE/file retrieval, this flow never compares the type encoded in the request path with storedConsentResource.getConsentType(). The validator therefore uses one consent type, while the update logic later persists the stored consent as another.

🧭 Proposed fix
             if (!storedConsentResource.getClientID().equals(consentManageData.getClientId())) {
                 //Throwing this error in a generic manner since client will not be able to identify if consent
                 // exists if consent does not belong to them
                 log.error(ConsentManageConstants.CLIENT_ID_MISMATCH_ERROR);
                 throw new ConsentException(ResponseStatus.BAD_REQUEST, ConsentManageConstants.CLIENT_ID_MISMATCH_ERROR,
                         ConsentOperationEnum.CONSENT_UPDATE);
             }
+
+            String requestConsentType = ConsentExtensionUtils.getConsentType(consentManageData.getRequestPath());
+            if (!requestConsentType.equals(storedConsentResource.getConsentType())) {
+                log.error(ConsentManageConstants.CONSENT_TYPE_MISMATCH_ERROR);
+                throw new ConsentException(ResponseStatus.BAD_REQUEST,
+                        ConsentManageConstants.CONSENT_TYPE_MISMATCH_ERROR,
+                        ConsentOperationEnum.CONSENT_UPDATE);
+            }
 
             ConsentResource updatedConsent;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/impl/DefaultConsentManageHandler.java`
around lines 493 - 530, In DefaultConsentManageHandler, before validating and
updating, compare the consent type derived from the request path/payload with
the stored consent's type (storedConsentResource.getConsentType()) and reject
the update if they differ; specifically, after retrieving storedConsentResource
and before calling
ConsentManageUtils.getConsentManageValidator().validateRequestPayload(...)
obtain the request consent type (e.g., via
ConsentManageUtils.getConsentManageValidator().getConsentType(consentManageData)
or equivalent) and if
(!requestConsentType.equals(storedConsentResource.getConsentType())) log an
appropriate error and throw a ConsentException (ResponseStatus.BAD_REQUEST) to
prevent persisting mismatched types (this affects the branch that calls
updateConsent(consentManageData, consentId, storedConsentResource,
consentType)).

{INITIATION_WITH_INVALID_TRANS_TO_DATES},
{INITIATION_WITH_PAST_TRANS_TO_DATES}
{"{}", "Invalid request payload"},
{INITIATION_WITHOUT_ACCOUNT_PERMISSION, "Permissions are invalid"},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the expected error for the malformed permission fixture.

Line 33 expects "Permissions are invalid", but INITIATION_WITHOUT_ACCOUNT_PERMISSION is not valid JSON (ReadBeneficiariesDetail is unquoted). This case will usually fail at payload parsing first, so the test is asserting the wrong error path.

Proposed fix
-                {INITIATION_WITHOUT_ACCOUNT_PERMISSION, "Permissions are invalid"},
+                {INITIATION_WITHOUT_ACCOUNT_PERMISSION, "Invalid request payload"},

If the intent was to test semantic permission validation instead, fix the fixture itself to be valid JSON and keep the current message.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{INITIATION_WITHOUT_ACCOUNT_PERMISSION, "Permissions are invalid"},
{INITIATION_WITHOUT_ACCOUNT_PERMISSION, "Invalid request payload"},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/util/DataProviders.java`
at line 33, The test maps INITIATION_WITHOUT_ACCOUNT_PERMISSION to the wrong
expected message because the fixture contains invalid JSON (unquoted
ReadBeneficiariesDetail) and will fail during payload parsing; update the
DataProviders entry for INITIATION_WITHOUT_ACCOUNT_PERMISSION to either (a)
change the expected message from "Permissions are invalid" to the parsing error
message your parser produces (so the test asserts the payload parse failure), or
(b) if you intended to test semantic permission validation, fix the fixture JSON
so ReadBeneficiariesDetail is quoted (making it valid JSON) and keep the
"Permissions are invalid" expectation; locate the mapping in class DataProviders
where INITIATION_WITHOUT_ACCOUNT_PERMISSION is defined and apply one of these
two fixes.

Comment on lines +123 to +132
/**
* This method is used to update an existing consent (excluding client ID and CreatedTime)
* based on the given consent.
* An audit record will be created.
*
* @param consentResource the consent resource with updated values and new associations
* @return the updated consent resource
* @throws ConsentManagementException thrown if an error occurs during the operation
*/
ConsentResource updateConsent(ConsentResource consentResource) throws ConsentManagementException;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pass the acting user through this API.

This method says it creates an audit record, but unlike the other state-changing APIs here it has no explicit userID/actor parameter. That makes the new PUT flow hard to attribute correctly and nudges callers toward stuffing auth context into ConsentResource, which weakens the contract for a consent-management API.

Suggested API shape
-    ConsentResource updateConsent(ConsentResource consentResource) throws ConsentManagementException;
+    ConsentResource updateConsent(ConsentResource consentResource, String userID)
+            throws ConsentManagementException;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* This method is used to update an existing consent (excluding client ID and CreatedTime)
* based on the given consent.
* An audit record will be created.
*
* @param consentResource the consent resource with updated values and new associations
* @return the updated consent resource
* @throws ConsentManagementException thrown if an error occurs during the operation
*/
ConsentResource updateConsent(ConsentResource consentResource) throws ConsentManagementException;
/**
* This method is used to update an existing consent (excluding client ID and CreatedTime)
* based on the given consent.
* An audit record will be created.
*
* `@param` consentResource the consent resource with updated values and new associations
* `@return` the updated consent resource
* `@throws` ConsentManagementException thrown if an error occurs during the operation
*/
ConsentResource updateConsent(ConsentResource consentResource, String userID)
throws ConsentManagementException;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/service/ConsentCoreService.java`
around lines 123 - 132, The updateConsent(ConsentResource consentResource) API
lacks an explicit acting user parameter which prevents proper audit attribution;
modify the ConsentCoreService interface to add an actor parameter (e.g., String
actingUserId or a UserContext/Actor object) to the updateConsent method
signature, update all implementations of ConsentCoreService.updateConsent to
accept and propagate this actor, and ensure the audit record creation logic in
those implementations uses the new acting user value instead of pulling user
info from ConsentResource or context; also update all callers to pass the
current authenticated user when invoking updateConsent so the contract clearly
requires the actor for auditing.

Comment on lines +296 to +307
ConsentResource previousConsent = getConsent(consentResource.getConsentID(), false);
/* Update the base consent using updated values from ConsentResource.
Immutable parameters are ignored in the update (i.e. clientId, createdTime) at DAO level.*/
consentCoreDAO.updateConsentResource(connection, consentResource);

// Add audit record
HashMap<String, Object> consentDataMap = new HashMap<>();
consentDataMap.put(ConsentCoreServiceConstants.CONSENT_RESOURCE, consentResource);

ConsentCoreServiceUtil.postStateChange(connection, consentCoreDAO, consentResource.getConsentID(),
null, consentResource.getCurrentStatus(), previousConsent.getCurrentStatus(),
ConsentCoreServiceConstants.BASIC_CONSENT_UPDATE_REASON, consentResource.getClientID(),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle the unknown-consent path before dereferencing previousConsent.

getConsent() can yield null for a missing consent, and Line 306 dereferences it unconditionally. That turns an invalid consent ID into a 500/NPE instead of a controlled ConsentManagementException.

Suggested fix
-                ConsentResource previousConsent = getConsent(consentResource.getConsentID(), false);
+                ConsentResource previousConsent = getConsent(consentResource.getConsentID(), false);
+                if (previousConsent == null) {
+                    throw new ConsentManagementException("Consent not found for consentId: "
+                            + consentResource.getConsentID());
+                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ConsentResource previousConsent = getConsent(consentResource.getConsentID(), false);
/* Update the base consent using updated values from ConsentResource.
Immutable parameters are ignored in the update (i.e. clientId, createdTime) at DAO level.*/
consentCoreDAO.updateConsentResource(connection, consentResource);
// Add audit record
HashMap<String, Object> consentDataMap = new HashMap<>();
consentDataMap.put(ConsentCoreServiceConstants.CONSENT_RESOURCE, consentResource);
ConsentCoreServiceUtil.postStateChange(connection, consentCoreDAO, consentResource.getConsentID(),
null, consentResource.getCurrentStatus(), previousConsent.getCurrentStatus(),
ConsentCoreServiceConstants.BASIC_CONSENT_UPDATE_REASON, consentResource.getClientID(),
ConsentResource previousConsent = getConsent(consentResource.getConsentID(), false);
if (previousConsent == null) {
throw new ConsentManagementException("Consent not found for consentId: "
consentResource.getConsentID());
}
/* Update the base consent using updated values from ConsentResource.
Immutable parameters are ignored in the update (i.e. clientId, createdTime) at DAO level.*/
consentCoreDAO.updateConsentResource(connection, consentResource);
// Add audit record
HashMap<String, Object> consentDataMap = new HashMap<>();
consentDataMap.put(ConsentCoreServiceConstants.CONSENT_RESOURCE, consentResource);
ConsentCoreServiceUtil.postStateChange(connection, consentCoreDAO, consentResource.getConsentID(),
null, consentResource.getCurrentStatus(), previousConsent.getCurrentStatus(),
ConsentCoreServiceConstants.BASIC_CONSENT_UPDATE_REASON, consentResource.getClientID(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/service/impl/ConsentCoreServiceImpl.java`
around lines 296 - 307, The code calls getConsent(...) and assigns to
previousConsent then immediately dereferences
previousConsent.getCurrentStatus(), which can NPE if getConsent returns null; in
ConsentCoreServiceImpl, check previousConsent for null right after calling
getConsent (before calling consentCoreDAO.updateConsentResource and before using
previousConsent.getCurrentStatus()) and if null throw a controlled
ConsentManagementException (with an appropriate message/enum) or return an error
path; ensure the null-check happens before invoking
ConsentCoreServiceUtil.postStateChange so postStateChange always receives a
valid previous status.

Comment on lines +299 to +318
consentCoreDAO.updateConsentResource(connection, consentResource);

// Add audit record
HashMap<String, Object> consentDataMap = new HashMap<>();
consentDataMap.put(ConsentCoreServiceConstants.CONSENT_RESOURCE, consentResource);

ConsentCoreServiceUtil.postStateChange(connection, consentCoreDAO, consentResource.getConsentID(),
null, consentResource.getCurrentStatus(), previousConsent.getCurrentStatus(),
ConsentCoreServiceConstants.BASIC_CONSENT_UPDATE_REASON, consentResource.getClientID(),
consentDataMap
);

DatabaseUtils.commitTransaction(connection);
log.debug("Updated consent successfully.");
return consentResource;

} catch (ConsentDataInsertionException | ConsentDataUpdationException e) {
log.error("Error during updating consent, rolling back", e);
DatabaseUtils.rollbackTransaction(connection);
throw new ConsentManagementException("Failed to update consent and create related records", e);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the persisted consent for audit and the response.

The DAO explicitly ignores immutable fields during update, but Lines 303-313 still use the inbound consentResource. If the caller sends stale/changed immutable values, the audit payload, clientID, and returned object can diverge from what was actually stored.

Suggested fix
                 consentCoreDAO.updateConsentResource(connection, consentResource);
+                ConsentResource updatedConsent =
+                        consentCoreDAO.getConsentResource(connection, consentResource.getConsentID());

                 // Add audit record
                 HashMap<String, Object> consentDataMap = new HashMap<>();
-                consentDataMap.put(ConsentCoreServiceConstants.CONSENT_RESOURCE, consentResource);
+                consentDataMap.put(ConsentCoreServiceConstants.CONSENT_RESOURCE, updatedConsent);

-                ConsentCoreServiceUtil.postStateChange(connection, consentCoreDAO, consentResource.getConsentID(),
-                        null, consentResource.getCurrentStatus(), previousConsent.getCurrentStatus(),
-                        ConsentCoreServiceConstants.BASIC_CONSENT_UPDATE_REASON, consentResource.getClientID(),
+                ConsentCoreServiceUtil.postStateChange(connection, consentCoreDAO, updatedConsent.getConsentID(),
+                        null, updatedConsent.getCurrentStatus(), previousConsent.getCurrentStatus(),
+                        ConsentCoreServiceConstants.BASIC_CONSENT_UPDATE_REASON, updatedConsent.getClientID(),
                         consentDataMap
                 );

                 DatabaseUtils.commitTransaction(connection);
                 log.debug("Updated consent successfully.");
-                return consentResource;
+                return updatedConsent;

-            } catch (ConsentDataInsertionException | ConsentDataUpdationException e) {
+            } catch (ConsentDataInsertionException | ConsentDataUpdationException |
+                     ConsentDataRetrievalException e) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.service/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/service/impl/ConsentCoreServiceImpl.java`
around lines 299 - 318, After calling
consentCoreDAO.updateConsentResource(connection, consentResource) retrieve the
persisted record (e.g., call consentCoreDAO.getConsentResourceById or
equivalent) into a new variable (persistedConsent) and use persistedConsent
instead of the inbound consentResource when building consentDataMap, passing
clientID, and for the returned value; call
ConsentCoreServiceUtil.postStateChange with persistedConsent.getConsentID() and
persistedConsent.getCurrentStatus() (and previousConsent as before), commit the
transaction and return persistedConsent to ensure audit and response reflect
stored immutable fields.

Comment on lines +539 to +571
@Test
public void testUpdateConsent() throws ConsentManagementException,
ConsentDataRetrievalException, ConsentDataUpdationException {

doReturn(ConsentMgtServiceTestData.getSampleStoredConsentResource()).when(mockedConsentCoreDAO)
.getConsentResource(any(), anyString());
doNothing().when(mockedConsentCoreDAO).updateConsentResource(any(), any());

ConsentResource result = consentCoreServiceImpl.updateConsent(
ConsentMgtServiceTestData.getSampleStoredConsentResource());
Assert.assertNotNull(result);
}

@Test(expectedExceptions = ConsentManagementException.class)
public void testUpdateConsentWithoutConsentId() throws ConsentManagementException {

ConsentResource consentResource = new ConsentResource();
consentResource.setConsentID(null);

consentCoreServiceImpl.updateConsent(consentResource);
}

@Test(expectedExceptions = ConsentManagementException.class)
public void testUpdateConsentWithConsentUpdateError() throws ConsentManagementException,
ConsentDataUpdationException, ConsentDataRetrievalException {

doReturn(ConsentMgtServiceTestData.getSampleStoredConsentResource()).when(mockedConsentCoreDAO)
.getConsentResource(any(), anyString());
doThrow(ConsentDataUpdationException.class).when(mockedConsentCoreDAO).updateConsentResource(any(), any());

ConsentResource result = consentCoreServiceImpl.updateConsent(
ConsentMgtServiceTestData.getSampleStoredConsentResource());
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Strengthen these tests to assert the actual update contract.

Right now testUpdateConsent only proves the method returns something non-null. It would still pass if the implementation simply reloaded the stored consent, overwrote immutable fields like clientID/CreatedTime, or skipped the audit/write path entirely. Please assert at least one updated field, verify the immutable fields stay unchanged, and verify the expected DAO/audit interaction for the PUT flow.

@Ashi1993 Ashi1993 closed this Mar 12, 2026
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.

1 participant