Skip to content

Commit

Permalink
Add BNR permission validation for consent revoke
Browse files Browse the repository at this point in the history
  • Loading branch information
anjuchamantha committed Dec 9, 2024
1 parent 50e11e9 commit 8b3a849
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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<String> userIDs = (ArrayList<String>) 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);
Expand Down Expand Up @@ -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<String> userIDs = (ArrayList<String>) 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<String> 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<String> 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<String> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand Down Expand Up @@ -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<ArrayList> argumentCaptor = ArgumentCaptor.forClass(ArrayList.class);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const ProfileMain = ({consent, infoLabel, appicationName, logoURL}) => {
</a>
</div>
{consent.currentStatus.toLowerCase() ===
specConfigurations.status.authorised.toLowerCase() && isNotExpired() ? (
specConfigurations.status.authorised.toLowerCase() && isNotExpired() && consent.can_revoke_by_bnr ? (
<div className="actionButtons">
<div className="actionBtnDiv">
<Link
Expand Down

0 comments on commit 8b3a849

Please sign in to comment.