diff --git a/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/admin/impl/CDSConsentAdminHandler.java b/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/admin/impl/CDSConsentAdminHandler.java index a8cbd0d1..ef5bf1a5 100644 --- a/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/admin/impl/CDSConsentAdminHandler.java +++ b/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/admin/impl/CDSConsentAdminHandler.java @@ -54,8 +54,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import static org.wso2.openbanking.cds.consent.extensions.common.CDSConsentExtensionConstants.AUTH_RESOURCE_TYPE_PRIMARY; @@ -110,6 +112,11 @@ public void handleSearch(ConsentAdminData consentAdminData) throws ConsentExcept throw new OpenBankingRuntimeException("Error while adding sharing dates to permissions", e); } } + + if (consentAdminData.getQueryParams().containsKey(CDSConsentExtensionConstants.CONSENT_IDS_QUERY_PARAM_NAME)) { + addBNRRevokeStatus(consentAdminData); + } + } @Override @@ -124,6 +131,16 @@ public void handleRevoke(ConsentAdminData consentAdminData) throws ConsentExcept final String userID = validateAndGetQueryParam(queryParams, USER_ID); DetailedConsentResource detailedConsentResource = this.consentCoreService.getDetailedConsent(consentID); if (detailedConsentResource != null) { + ArrayList userIDs = (ArrayList) consentAdminData.getQueryParams() + .get(CDSConsentExtensionConstants.USER_ID_KEY_NAME); + // userIDs can be null or empty when the request comes from a CustomerCareOfficer + if (userIDs != null && !userIDs.isEmpty()) { + String userId = userIDs.get(0); + if (!canRevokeByBNR(detailedConsentResource, userId)) { + throw new ConsentException(ResponseStatus.FORBIDDEN, + "User is not authorized to revoke the consent"); + } + } if (StringUtils.isNotBlank(userID) && !isPrimaryUserRevoking(detailedConsentResource, userID)) { // Deactivate consent mappings as secondary consent holder deactivateAccountMappings(detailedConsentResource, userID); @@ -521,6 +538,92 @@ private boolean isActionByPrimaryUser(DetailedConsentResource detailedConsentRes return false; } + private void addBNRRevokeStatus(ConsentAdminData consentAdminData) { + + JSONObject responsePayload = consentAdminData.getResponsePayload(); + JSONArray consentData = (JSONArray) responsePayload.get(CDSConsentExtensionConstants.DATA); + ArrayList userIDs = (ArrayList) consentAdminData.getQueryParams() + .get(CDSConsentExtensionConstants.USER_IDS_QUERY_PARAM_NAME); + if (userIDs == null || userIDs.isEmpty()) { + // Customer care officer scenario + consentData.forEach(consent -> { + JSONObject consentObject = (JSONObject) consent; + consentObject.put(CDSConsentExtensionConstants.CAN_REVOKE, true); + }); + } else { + String userId = userIDs.get(0); + consentData.forEach(consent -> { + JSONObject consentObject = (JSONObject) consent; + consentObject.put(CDSConsentExtensionConstants.CAN_REVOKE, canRevokeByBNR(consentAdminData, userId)); + }); + } + } + + private boolean canRevokeByBNR(DetailedConsentResource detailedConsentResource, String userId) { + + Set activeAccountIDs = new HashSet<>(); + for (ConsentMappingResource consentMappingResource : detailedConsentResource.getConsentMappingResources()) { + if (consentMappingResource.getMappingStatus().equals(CDSConsentExtensionConstants.ACTIVE_STATUS)) { + activeAccountIDs.add(consentMappingResource.getAccountID()); + } + } + + return canRevokeByBNR(activeAccountIDs, userId); + } + + private boolean canRevokeByBNR(ConsentAdminData consentAdminData, String userId) { + + Set activeAccountIDs = new HashSet<>(); + + for (Object item : (JSONArray) consentAdminData.getResponsePayload(). + get(CDSConsentExtensionConstants.DATA)) { + JSONObject itemJSONObject = (JSONObject) item; + JSONArray consentMappingResourcesArray = (JSONArray) itemJSONObject. + get(CDSConsentExtensionConstants.CONSENT_MAPPING_RESOURCES); + for (Object consentMappingResource : consentMappingResourcesArray) { + JSONObject consentMappingResourceObject = (JSONObject) consentMappingResource; + if (consentMappingResourceObject.getAsString(CDSConsentExtensionConstants.MAPPING_STATUS). + equals(CDSConsentExtensionConstants.ACTIVE_STATUS)) { + activeAccountIDs.add(consentMappingResourceObject.getAsString( + CDSConsentExtensionConstants.ACCOUNT_ID)); + } + } + } + return canRevokeByBNR(activeAccountIDs, userId); + } + + private boolean canRevokeByBNR(Set activeAccountIDs, String userId) { + + if (activeAccountIDs.isEmpty()) { + return true; + } + + AccountMetadataServiceImpl accountMetadataService = AccountMetadataServiceImpl.getInstance(); + + /* + * For all the active accounts, if the user has AUTHORIZE or REVOKE permission for BNR, then the user can + * revoke that consent. (i.e. For any given active account in consent mappings, if the user does not have + * AUTHORIZE or REVOKE permission, he/she cannot revoke the consent) + */ + for (String accountID : activeAccountIDs) { + try { + String permissionStatus = accountMetadataService.getAccountMetadataByKey(accountID, userId, + CDSConsentExtensionConstants.BNR_PERMISSION); + if (StringUtils.isNotBlank(permissionStatus) && + !(CDSConsentExtensionConstants.BNR_AUTHORIZE_PERMISSION.equals(permissionStatus) || + CDSConsentExtensionConstants.BNR_REVOKE_PERMISSION.equals(permissionStatus))) { + + return false; + } + } catch (OpenBankingException e) { + log.error(e.getMessage()); + throw new OpenBankingRuntimeException("Error while getting BNR permissions.", e); + } + } + + return true; + } + public void updateDOMSStatusForConsentData(ConsentAdminData consentAdminData) { try { AccountMetadataService accountMetadataService = AccountMetadataServiceImpl.getInstance(); diff --git a/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/common/CDSConsentExtensionConstants.java b/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/common/CDSConsentExtensionConstants.java index 08500ac9..fbdf70fa 100644 --- a/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/common/CDSConsentExtensionConstants.java +++ b/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/common/CDSConsentExtensionConstants.java @@ -77,6 +77,8 @@ public class CDSConsentExtensionConstants { public static final String CONSENT_EXPIRY = "consent_expiration"; public static final String ACCOUNT_MASKING_ENABLED = "account_masking_enabled"; public static final String USER_ID_KEY_NAME = "userID"; + public static final String USER_IDS_QUERY_PARAM_NAME = "userIDs"; + public static final String CONSENT_IDS_QUERY_PARAM_NAME = "consentIDs"; public static final String CONSENT_STATUS_REVOKED = "revoked"; public static final String CONSENT_ATTRIBUTES = "consentAttributes"; public static final String INCLUDE_SHARING_DATES = "includeSharingDates"; @@ -141,6 +143,7 @@ public class CDSConsentExtensionConstants { public static final String BNR_AUTHORIZE_PERMISSION = "AUTHORIZE"; public static final String BNR_VIEW_PERMISSION = "VIEW"; public static final String BNR_REVOKE_PERMISSION = "REVOKE"; + public static final String CAN_REVOKE = "can_revoke"; //Multi Profile Constants public static final String INDIVIDUAL_PROFILE_TYPE = "Individual"; diff --git a/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/admin/impl/CDSConsentAdminHandlerTest.java b/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/admin/impl/CDSConsentAdminHandlerTest.java index e502fc53..ee94c059 100644 --- a/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/admin/impl/CDSConsentAdminHandlerTest.java +++ b/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/admin/impl/CDSConsentAdminHandlerTest.java @@ -116,19 +116,19 @@ public void setUp() throws ConsentManagementException { mapping1.setMappingID(MAPPING_ID_1); mapping1.setAccountID(JOINT_ACCOUNT_ID); mapping1.setAuthorizationID(AUTH_ID_PRIMARY); - mapping1.setMappingStatus("true"); + mapping1.setMappingStatus("active"); ConsentMappingResource mapping2 = new ConsentMappingResource(); mapping2.setMappingID(MAPPING_ID_2); mapping2.setAccountID("test-regular-account-id"); mapping2.setAuthorizationID(AUTH_ID_PRIMARY); - mapping2.setMappingStatus("true"); + mapping2.setMappingStatus("active"); ConsentMappingResource mapping3 = new ConsentMappingResource(); mapping3.setMappingID(MAPPING_ID_3); mapping3.setAccountID(JOINT_ACCOUNT_ID); mapping3.setAuthorizationID(AUTH_ID_SECONDARY); - mapping3.setMappingStatus("true"); + mapping3.setMappingStatus("active"); detailedConsentResource = new DetailedConsentResource(); detailedConsentResource.setAuthorizationResources(new ArrayList<>(Arrays.asList(authResource1, authResource2))); @@ -164,6 +164,10 @@ public void testHandleRevoke() throws ConsentManagementException { ConsentAdminData consentAdminDataMock = mock(ConsentAdminData.class); when(consentAdminDataMock.getQueryParams()).thenReturn(queryParams); + this.accountMetadataServiceMock = mock(AccountMetadataServiceImpl.class); + mockStatic(AccountMetadataServiceImpl.class); + when(AccountMetadataServiceImpl.getInstance()).thenReturn(accountMetadataServiceMock); + uut.handleRevoke(consentAdminDataMock); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(ArrayList.class); @@ -185,6 +189,10 @@ public void testHandleRevokeForPrimaryUser() { ConsentAdminData consentAdminDataMock = mock(ConsentAdminData.class); when(consentAdminDataMock.getQueryParams()).thenReturn(queryParams); + this.accountMetadataServiceMock = mock(AccountMetadataServiceImpl.class); + mockStatic(AccountMetadataServiceImpl.class); + when(AccountMetadataServiceImpl.getInstance()).thenReturn(accountMetadataServiceMock); + uut.handleRevoke(consentAdminDataMock); verify(consentAdminDataMock).setResponseStatus(ResponseStatus.NO_CONTENT); diff --git a/react-apps/self-care-portal-frontend/toolkit/src/detailedAgreementPage/ProfileMain.jsx b/react-apps/self-care-portal-frontend/toolkit/src/detailedAgreementPage/ProfileMain.jsx index 52793442..9ac5f37e 100644 --- a/react-apps/self-care-portal-frontend/toolkit/src/detailedAgreementPage/ProfileMain.jsx +++ b/react-apps/self-care-portal-frontend/toolkit/src/detailedAgreementPage/ProfileMain.jsx @@ -72,7 +72,7 @@ export const ProfileMain = ({consent, infoLabel, appicationName, logoURL}) => { {consent.currentStatus.toLowerCase() === - specConfigurations.status.authorised.toLowerCase() && isNotExpired() ? ( + specConfigurations.status.authorised.toLowerCase() && isNotExpired() && consent.can_revoke_by_bnr ? (