Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validating BNR permission for consent revocation #66

Merged
merged 6 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,13 @@ public void handleRevoke(ConsentAdminData consentAdminData) throws ConsentExcept
final String userID = validateAndGetQueryParam(queryParams, USER_ID);
DetailedConsentResource detailedConsentResource = this.consentCoreService.getDetailedConsent(consentID);
if (detailedConsentResource != null) {
String userId = validateAndGetQueryParam(queryParams,
CDSConsentExtensionConstants.USER_ID_KEY_NAME);
// userId can be null when the request comes from a CustomerCareOfficer
if (userId != null && (!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 +535,91 @@ 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 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 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))) {

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 ? (
<div className="actionButtons">
<div className="actionBtnDiv">
<Link
Expand Down
Loading