diff --git a/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSConsentPersistStep.java b/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSConsentPersistStep.java index 51c5cc59..855a82d1 100644 --- a/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSConsentPersistStep.java +++ b/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSConsentPersistStep.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2024-2025, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -54,6 +54,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.stream.Collectors; /** @@ -186,8 +187,7 @@ public void execute(ConsentPersistData consentPersistData) throws ConsentExcepti Map additionalAmendmentData = new HashMap<>(); if (nonPrimaryAccountIdAgainstUsersObj != null && userIdAgainstNonPrimaryAccountsObj != null) { additionalAmendmentData = bindNonPrimaryAccountUsersToConsent(consentResource, consentData, - (Map>) nonPrimaryAccountIdAgainstUsersObj, - (Map>) userIdAgainstNonPrimaryAccountsObj, true); + (Map>>) userIdAgainstNonPrimaryAccountsObj, true); } consentCoreService.amendDetailedConsent(cdrArrangementId, consentResource.getReceipt(), validityPeriod, authResorceId, accountIdsMapWithPermissions, @@ -239,8 +239,7 @@ public void execute(ConsentPersistData consentPersistData) throws ConsentExcepti // bind user consented accounts with the created consent for non-primary users if (nonPrimaryAccountIdAgainstUsersObj != null && userIdAgainstNonPrimaryAccountsObj != null) { bindNonPrimaryAccountUsersToConsent(consentResource, consentData, - (Map>) nonPrimaryAccountIdAgainstUsersObj, - (Map>) userIdAgainstNonPrimaryAccountsObj, false); + (Map>>) userIdAgainstNonPrimaryAccountsObj, false); } } @@ -447,19 +446,18 @@ private ArrayList getAccountIdList(JSONObject payloadData) throws Consen } @Generated(message = "Excluding from code coverage since it requires a service call") - private Map bindNonPrimaryAccountUsersToConsent(ConsentResource consentResource, - ConsentData consentData, - Map> nonPrimaryAccountIdAgainstUsers, - Map> userIdAgainstNonPrimaryAccounts, - boolean isConsentAmendment) - throws ConsentManagementException { + private Map bindNonPrimaryAccountUsersToConsent( + ConsentResource consentResource, + ConsentData consentData, + Map>> userIdAgainstNonPrimaryAccounts, + boolean isConsentAmendment) throws ConsentManagementException { List addedUserAuthTypeMappings = new ArrayList<>(); // add primary user to already added users auth type mapping list addedUserAuthTypeMappings.add(consentData.getUserId() + ":" + CDSConsentExtensionConstants.AUTH_RESOURCE_TYPE_PRIMARY); // Users who have already stored as auth resources - Map reAuthorizableResources = new HashMap<>(); + List reAuthorizableResources = new ArrayList<>(); // Users who need to store as auth resources Map> authorizableResources = new HashMap<>(); @@ -468,23 +466,19 @@ private Map bindNonPrimaryAccountUsersToConsent(ConsentResource : consentData.getConsentId(); DetailedConsentResource detailedConsent = consentCoreService.getDetailedConsent(consentId); - for (Entry> entry : nonPrimaryAccountIdAgainstUsers.entrySet()) { - Map userIdList = entry.getValue(); - for (Entry userEntry : userIdList.entrySet()) { - String userId = userEntry.getKey().toString(); - String authType = userEntry.getValue().toString(); + for (Map.Entry>> userEntry : userIdAgainstNonPrimaryAccounts.entrySet()) { + + final String userId = userEntry.getKey(); + for (Map.Entry> accountEntry : userEntry.getValue().entrySet()) { + final String authType = accountEntry.getKey(); String userAuthTypeMapping = userId + ":" + authType; if (StringUtils.isNotBlank(userId) && !addedUserAuthTypeMappings.contains(userAuthTypeMapping)) { - if (isConsentAmendment) { - for (AuthorizationResource authResource : detailedConsent.getAuthorizationResources()) { - if (userId.equals(authResource.getUserID())) { - reAuthorizableResources.put(userId, authResource); - } - } + if (isConsentAmendment && isReauthorizable(userId, accountEntry.getValue(), detailedConsent)) { + reAuthorizableResources.add(userId); } - if (!reAuthorizableResources.containsKey(userId)) { + if (!reAuthorizableResources.contains(userId)) { authorizableResources.computeIfAbsent(userId, k -> new ArrayList<>()).add(authType); } @@ -498,11 +492,11 @@ private Map bindNonPrimaryAccountUsersToConsent(ConsentResource } @Generated(message = "Excluding from code coverage since it requires a service call") - private Map createNewAuthResources(String consentId, ConsentResource consentResource, - Map> authorizableResources, - Map> userIdAgainstNonPrimaryAccounts, - boolean isConsentAmendment) - throws ConsentManagementException { + private Map createNewAuthResources( + String consentId, ConsentResource consentResource, + Map> authorizableResources, + Map>> userIdAgainstNonPrimaryAccounts, + boolean isConsentAmendment) throws ConsentManagementException { Map additionalAmendmentData = new HashMap<>(); Map secondaryUserAuthResources = new HashMap<>(); @@ -522,15 +516,23 @@ private Map createNewAuthResources(String consentId, ConsentReso // AuthorizationResources, ConsentMappingResources against the userId and returned. secondaryUserAuthResources.put(userId, secondaryAuthResource); - for (String accountId : userIdAgainstNonPrimaryAccounts.get(userId)) { + for (String accountId : userIdAgainstNonPrimaryAccounts.get(userId).get(authType)) { mappingResources.add(getSecondaryConsentMappingResource(accountId)); } secondaryUserAccountMappings.put(userId, mappingResources); } else { - AuthorizationResource authorizationResource = consentCoreService - .createConsentAuthorization(secondaryAuthResource); + final AuthorizationResource authorizationResource = + consentCoreService.createConsentAuthorization(secondaryAuthResource); + + if (log.isDebugEnabled()) { + log.debug(String.format("Inserting consented accounts for authorizationId: %s, " + + "consentId: %s, authType %s", authorizationResource.getAuthorizationID(), + consentResource.getConsentID(), authType)); + } + consentCoreService.bindUserAccountsToConsent(consentResource, userId, - authorizationResource.getAuthorizationID(), userIdAgainstNonPrimaryAccounts.get(userId), + authorizationResource.getAuthorizationID(), + new ArrayList<>(userIdAgainstNonPrimaryAccounts.get(userId).get(authType)), CDSConsentExtensionConstants.AUTHORIZED_STATUS, CDSConsentExtensionConstants.AUTHORIZED_STATUS); } @@ -674,4 +676,47 @@ private void publishConsentApprovalStatus(ConsentPersistData consentPersistData) CDSDataPublishingService.getCDSDataPublishingService() .publishAbandonedConsentFlowData(abandonedConsentFlowData); } + + /** + * Checks if the given mapping resource is re-authorizable based on the user ID and account IDs. + * + * @param newUserId the ID of the user to check. + * @param newlyConsentedAccountIDs the list of account IDs to validate. + * @param detailedConsent the detailed consent object containing authorization and mapping resources. + * @return true if the resource is re-authorizable, false otherwise. + */ + private boolean isReauthorizable(String newUserId, List newlyConsentedAccountIDs, + DetailedConsentResource detailedConsent) { + + final List existingUserIDs = detailedConsent.getAuthorizationResources().stream() + .map(AuthorizationResource::getUserID).collect(Collectors.toList()); + + if (!existingUserIDs.contains(newUserId)) { + if (log.isDebugEnabled()) { + log.debug(String.format("authorize user %s for consent %s", newUserId, detailedConsent.getConsentID())); + } + return false; + } + + final List existingAuthIDs = detailedConsent.getAuthorizationResources().stream() + .filter(auth -> auth.getUserID().equals(newUserId)) + .map(AuthorizationResource::getAuthorizationID) + .collect(Collectors.toList()); + + final List existingAccountIDs = detailedConsent.getConsentMappingResources().stream() + .filter(mapping -> existingAuthIDs.contains(mapping.getAuthorizationID())) + .map(ConsentMappingResource::getAccountID) + .collect(Collectors.toList()); + + // Return false if any accountID matches an authorization ID + if (existingAccountIDs.stream().noneMatch(newlyConsentedAccountIDs::contains)) { + if (log.isDebugEnabled()) { + log.debug(String.format("authorize user %s for consent %s", + newUserId, detailedConsent.getConsentID())); + } + return false; + } + + return true; + } } diff --git a/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/authorize/utils/CDSConsentPersistUtil.java b/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/authorize/utils/CDSConsentPersistUtil.java index 056d5df8..0e051625 100644 --- a/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/authorize/utils/CDSConsentPersistUtil.java +++ b/components/org.wso2.openbanking.cds.consent.extensions/src/main/java/org/wso2/openbanking/cds/consent/extensions/authorize/utils/CDSConsentPersistUtil.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2024-2025, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -53,7 +53,7 @@ public static void addNonPrimaryAccountDataToPersistData( ConsentPersistData consentPersistData) { Map> currentNonPrimaryAccountIdUsersMap = new HashMap<>(); - Map> currentUserIdNonPrimaryAccountsMap = new HashMap<>(); + Map>> currentUserIdNonPrimaryAccountsMap = new HashMap<>(); //Get existing non-primary account data from consentPersistData if (consentPersistData.getMetadata().get(CDSConsentExtensionConstants. @@ -67,8 +67,8 @@ public static void addNonPrimaryAccountDataToPersistData( if (consentPersistData.getMetadata().get(CDSConsentExtensionConstants. USER_ID_AGAINST_NON_PRIMARY_ACCOUNTS_MAP) != null) { - currentUserIdNonPrimaryAccountsMap = (Map>) consentPersistData.getMetadata(). - get(CDSConsentExtensionConstants.USER_ID_AGAINST_NON_PRIMARY_ACCOUNTS_MAP); + currentUserIdNonPrimaryAccountsMap = (Map>>) consentPersistData + .getMetadata().get(CDSConsentExtensionConstants.USER_ID_AGAINST_NON_PRIMARY_ACCOUNTS_MAP); } else { log.debug("UserIds against non-primary accountId map not available in consentPersistData. " + "Creating new map"); @@ -85,15 +85,26 @@ public static void addNonPrimaryAccountDataToPersistData( // Update user against accounts map // Get UserId against non-primary accounts Map from current persistence Step - Map> userIdNonPrimaryAccountsMap = getUserIdAgainstAccountsMap( - nonPrimaryAccountIdUsersMap); - for (Map.Entry> entry : userIdNonPrimaryAccountsMap.entrySet()) { - final String userId = entry.getKey(); - final List nonPrimaryAccounts = entry.getValue(); - if (currentUserIdNonPrimaryAccountsMap.containsKey(userId)) { - currentUserIdNonPrimaryAccountsMap.get(userId).addAll(nonPrimaryAccounts); - } else { - currentUserIdNonPrimaryAccountsMap.put(userId, nonPrimaryAccounts); + Map>> userIdNonPrimaryAccountsMap = + getUserIdAgainstAccountsMap(nonPrimaryAccountIdUsersMap); + + for (Map.Entry>> userAndAccounts : userIdNonPrimaryAccountsMap.entrySet()) { + final String userId = userAndAccounts.getKey(); + for (Map.Entry> authTypeAndNonPrimaryAccount : userAndAccounts.getValue().entrySet()) { + final String authType = authTypeAndNonPrimaryAccount.getKey(); + final List nonPrimaryAccounts = authTypeAndNonPrimaryAccount.getValue(); + + if (currentUserIdNonPrimaryAccountsMap.containsKey(userId)) { + if (currentUserIdNonPrimaryAccountsMap.get(userId).containsKey(authType)) { + currentUserIdNonPrimaryAccountsMap.get(userId).get(authType).addAll(nonPrimaryAccounts); + } else { + currentUserIdNonPrimaryAccountsMap.get(userId).put(authType, nonPrimaryAccounts); + } + } else { + currentUserIdNonPrimaryAccountsMap.put(userId, new HashMap>() { { + put(authType, nonPrimaryAccounts); + }}); + } } } @@ -110,19 +121,27 @@ public static void addNonPrimaryAccountDataToPersistData( * @param accountIdUserMap Map of accountId against userIds * @return a map of userId against a list of accountIds */ - private static Map> getUserIdAgainstAccountsMap(Map> accountIdUserMap) { - - Map> userIdAccountsMap = new HashMap<>(); - for (Map.Entry> entry : accountIdUserMap.entrySet()) { - final String accountId = entry.getKey(); - final List userIdList = new ArrayList<>(entry.getValue().keySet()); - for (String userId : userIdList) { + private static Map>> getUserIdAgainstAccountsMap( + Map> accountIdUserMap) { + + Map>> userIdAccountsMap = new HashMap<>(); + for (Map.Entry> accountIdUsers : accountIdUserMap.entrySet()) { + final String accountId = accountIdUsers.getKey(); + for (Map.Entry userIdAndAuthType : accountIdUsers.getValue().entrySet()) { + final String userId = userIdAndAuthType.getKey(); + final String authType = userIdAndAuthType.getValue(); + if (userIdAccountsMap.containsKey(userId)) { - userIdAccountsMap.get(userId).add(accountId); + if (userIdAccountsMap.get(userId).containsKey(authType)) { + userIdAccountsMap.get(userId).get(authType).add(accountId); + } else { + userIdAccountsMap.get(userId) + .put(authType, new ArrayList<>(Collections.singletonList(accountId))); + } } else { - userIdAccountsMap.put(userId, new ArrayList<>(Collections. - singletonList(accountId))); + userIdAccountsMap.put(userId, new HashMap>() { { + put(authType, new ArrayList<>(Collections.singletonList(accountId))); + }}); } } } diff --git a/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSBusinessAccountConsentPersistenceStepTest.java b/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSBusinessAccountConsentPersistenceStepTest.java index 7865da90..8811eb36 100644 --- a/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSBusinessAccountConsentPersistenceStepTest.java +++ b/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSBusinessAccountConsentPersistenceStepTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2024-2025, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -165,8 +165,8 @@ public void testExecute() throws Exception { verify(consentPersistDataMock).addMetadata(eq(CDSConsentExtensionConstants. USER_ID_AGAINST_NON_PRIMARY_ACCOUNTS_MAP), usersWithMultipleBusinessAccountsCaptor.capture()); - Map> usersWithMultipleJointAccountsCaptorValue = - (Map>) usersWithMultipleBusinessAccountsCaptor.getValue(); + Map>> usersWithMultipleJointAccountsCaptorValue = + (Map>>) usersWithMultipleBusinessAccountsCaptor.getValue(); assertTrue(jointAccountIdWithUsersCaptorValue.containsKey("business-account-id")); assertTrue(jointAccountIdWithUsersCaptorValue.get("business-account-id"). @@ -177,8 +177,8 @@ public void testExecute() throws Exception { assertTrue(usersWithMultipleJointAccountsCaptorValue.containsKey("nominatedUser2@wso2.com@carbon.super")); assertTrue(usersWithMultipleJointAccountsCaptorValue.containsKey("user2@wso2.com@carbon.super")); assertTrue(usersWithMultipleJointAccountsCaptorValue.get("nominatedUser3@wso2.com@carbon.super") - .contains("business-account-id")); + .get(CDSConsentExtensionConstants.NOMINATED_REPRESENTATIVE).contains("business-account-id")); assertTrue(usersWithMultipleJointAccountsCaptorValue.get("user1@wso2.com@carbon.super") - .contains("business-account-id")); + .get(CDSConsentExtensionConstants.BUSINESS_ACCOUNT_OWNER).contains("business-account-id")); } } diff --git a/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSJointAccountConsentPersistenceStepTest.java b/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSJointAccountConsentPersistenceStepTest.java index be419d14..03d57e2b 100644 --- a/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSJointAccountConsentPersistenceStepTest.java +++ b/components/org.wso2.openbanking.cds.consent.extensions/src/test/java/org/wso2/openbanking/cds/consent/extensions/authorize/impl/persist/CDSJointAccountConsentPersistenceStepTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2024-2025, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -120,8 +120,8 @@ public void testExecute() { verify(consentPersistDataMock).addMetadata(eq(CDSConsentExtensionConstants. USER_ID_AGAINST_NON_PRIMARY_ACCOUNTS_MAP), usersWithMultipleJointAccountsCaptor.capture()); - Map> usersWithMultipleJointAccountsCaptorValue = - (Map>) usersWithMultipleJointAccountsCaptor.getValue(); + Map>> usersWithMultipleJointAccountsCaptorValue = + (Map>>) usersWithMultipleJointAccountsCaptor.getValue(); assertTrue(jointAccountIdWithUsersCaptorValue.containsKey("joint-account-id")); assertTrue(jointAccountIdWithUsersCaptorValue.get("joint-account-id"). @@ -131,8 +131,8 @@ public void testExecute() { assertTrue(usersWithMultipleJointAccountsCaptorValue.containsKey("john@wso2.com@carbon.super")); assertTrue(usersWithMultipleJointAccountsCaptorValue.containsKey("amy@wso2.com@carbon.super")); assertTrue(usersWithMultipleJointAccountsCaptorValue.get("john@wso2.com@carbon.super") - .contains("joint-account-id")); + .get(CDSConsentExtensionConstants.LINKED_MEMBER_AUTH_TYPE).contains("joint-account-id")); assertTrue(usersWithMultipleJointAccountsCaptorValue.get("amy@wso2.com@carbon.super") - .contains("joint-account-id")); + .get(CDSConsentExtensionConstants.LINKED_MEMBER_AUTH_TYPE).contains("joint-account-id")); } }