Skip to content

Commit

Permalink
Fixed Issue in idempotency validation for APIs without a payload
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashi1993 committed Apr 25, 2024
1 parent 559afcc commit 5b0da62
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class IdempotencyConstants {
public static final String X_IDEMPOTENCY_KEY = "x-idempotency-key";
public static final String IDEMPOTENCY_KEY_NAME = "IdempotencyKey";
public static final String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX";
public static final String EMPTY_OBJECT = "{}";
public static final String ERROR_PAYLOAD_NOT_SIMILAR = "Payloads are not similar. Hence this is not a valid" +
" idempotent request";
public static final String ERROR_AFTER_ALLOWED_TIME = "Request received after the allowed time., Hence this is" +
Expand All @@ -36,4 +37,6 @@ public class IdempotencyConstants {
" is not a valid idempotent request";
public static final String JSON_COMPARING_ERROR = "Error occurred while comparing JSON payloads";
public static final String CONSENT_RETRIEVAL_ERROR = "Error while retrieving detailed consent data";
public static final String SAME_CONSENT_ID_ERROR = "Cannot use different unique identifier for the same" +
" consent ID when the request does not contain a payload.";
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
* Class to hold idempotency validation utils.
Expand Down Expand Up @@ -60,6 +62,21 @@ static ArrayList<String> getConsentIdsFromIdempotencyKey(String idempotencyKeyNa
}
}

/**
* Method to retrieve the consent ids and idempotency key value using the idempotency key.
*
* @param idempotencyKeyName Idempotency Key Name
* @return Map of consent ids and idempotency key vallue if available, else an empty map will be returned
*/
static Map<String, String> getAttributesFromIdempotencyKey(String idempotencyKeyName) {
try {
return consentCoreService.getConsentAttributesByName(idempotencyKeyName);
} catch (ConsentManagementException e) {
log.debug("No consent ids found for the idempotency key value");
return new HashMap<>();
}
}

/**
* Method to compare the client ID sent in the request and client id retrieved from the database.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;

/**
* Class to handle idempotency related operations.
Expand Down Expand Up @@ -81,26 +82,30 @@ public IdempotencyValidationResult validateIdempotency(ConsentManageData consent
}
try {
String idempotencyKeyName = getIdempotencyAttributeName(consentManageData.getRequestPath());
// Retrieve consent ids that have the idempotency key name and value as attribute
ArrayList<String> consentIds = IdempotencyValidationUtils
.getConsentIdsFromIdempotencyKey(idempotencyKeyName, idempotencyKeyValue);
// Check whether the consent id list is not empty. If idempotency key exists in the database then
// the consent Id list will be not empty.
if (!consentIds.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Idempotency Key %s exists in the database. Hence this is an" +
" idempotent request", idempotencyKeyValue));
}
for (String consentId : consentIds) {
DetailedConsentResource consentRequest = consentCoreService.getDetailedConsent(consentId);
if (consentRequest != null) {
return validateIdempotencyConditions(consentManageData, consentRequest);
} else {
String errorMsg = String.format(IdempotencyConstants.ERROR_NO_CONSENT_DETAILS, consentId);
log.error(errorMsg);
throw new IdempotencyValidationException(errorMsg);
if (!IdempotencyConstants.EMPTY_OBJECT.equals(consentManageData.getPayload().toString())) {
// Retrieve consent ids that have the idempotency key name and value as attribute
ArrayList<String> consentIds = IdempotencyValidationUtils
.getConsentIdsFromIdempotencyKey(idempotencyKeyName, idempotencyKeyValue);
// Check whether the consent id list is not empty. If idempotency key exists in the database then
// the consent Id list will be not empty.
if (!consentIds.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Idempotency Key %s exists in the database. Hence this is an" +
" idempotent request", idempotencyKeyValue));
}
for (String consentId : consentIds) {
DetailedConsentResource consentRequest = consentCoreService.getDetailedConsent(consentId);
if (consentRequest != null) {
return validateIdempotencyConditions(consentManageData, consentRequest);
} else {
String errorMsg = String.format(IdempotencyConstants.ERROR_NO_CONSENT_DETAILS, consentId);
log.error(errorMsg);
throw new IdempotencyValidationException(errorMsg);
}
}
}
} else {
return validateIdempotencyWithoutPayload(consentManageData, idempotencyKeyName, idempotencyKeyValue);
}
} catch (IOException e) {
log.error(IdempotencyConstants.JSON_COMPARING_ERROR, e);
Expand All @@ -112,6 +117,53 @@ public IdempotencyValidationResult validateIdempotency(ConsentManageData consent
return new IdempotencyValidationResult(false, false);
}

/**
* Method to check whether the idempotency conditions are met for requests without payload.
* This method will validate the following conditions.
* - Whether the idempotency key value is different for the same consent id
* - Whether the client id sent in the request and client id retrieved from the database are equal
* - Whether the difference between two dates is less than the configured time
* - Whether payloads are equal
*
* @param consentManageData Consent Manage Data
* @param idempotencyKeyName Idempotency Key Name
* @param idempotencyKeyValue Idempotency Key value
* @return IdempotencyValidationResult
*/
private IdempotencyValidationResult validateIdempotencyWithoutPayload(ConsentManageData consentManageData,
String idempotencyKeyName,
String idempotencyKeyValue)
throws IdempotencyValidationException, IOException, ConsentManagementException {

// Retrieve consent ids and idempotency key values that have the idempotency key name
Map<String, String> attributes = IdempotencyValidationUtils.getAttributesFromIdempotencyKey(idempotencyKeyName);
// Check whether the attributes map is not empty. If idempotency key exists in the database then
// the consent Id list will be not empty.
if (!attributes.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Idempotency Key %s exists in the database. Hence this is an" +
" idempotent request", idempotencyKeyValue));
}
for (Map.Entry<String, String> entry : attributes.entrySet()) {
// If the idempotency key value is different for the same consent id then it is not a valid idempotent
if (consentManageData.getRequestPath().contains(entry.getKey()) &&
!idempotencyKeyValue.equals(entry.getValue())) {
throw new IdempotencyValidationException(IdempotencyConstants.SAME_CONSENT_ID_ERROR);
}
DetailedConsentResource consentRequest = consentCoreService.getDetailedConsent(entry.getKey());
if (consentRequest != null) {
return validateIdempotencyConditions(consentManageData, consentRequest);
} else {
String errorMsg = String.format(IdempotencyConstants.ERROR_NO_CONSENT_DETAILS, entry.getKey());
log.error(errorMsg);
throw new IdempotencyValidationException(errorMsg);
}
}

}
return new IdempotencyValidationResult(false, false);
}

/**
* Method to check whether the idempotency conditions are met.
* This method will validate the following conditions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class IdempotencyValidatorTests extends PowerMockTestCase {
private ConsentManageData consentManageData;
private ConsentCoreServiceImpl consentCoreServiceImpl;
private ArrayList<String> consentIdList;
private Map<String, String> attributeList;
private String consentId;
private Map<String, Object> configs;
private Map<String, String> headers;
Expand Down Expand Up @@ -126,6 +127,9 @@ public void beforeTest() {
consentId = UUID.randomUUID().toString();
consentIdList = new ArrayList<>();
consentIdList.add(consentId);

attributeList = new HashMap<>();
attributeList.put(consentId, "123456");
}

@BeforeMethod
Expand Down Expand Up @@ -164,6 +168,22 @@ public void testValidateIdempotency() throws ConsentManagementException, Idempot
Assert.assertEquals(consentId, result.getConsentId());
}

@Test(expectedExceptions = IdempotencyValidationException.class)
public void testValidateIdempotencyForRequestsWithoutPayload() throws ConsentManagementException,
IdempotencyValidationException {
OffsetDateTime offsetDateTime = OffsetDateTime.now();

Mockito.doReturn(attributeList).when(consentCoreServiceImpl).getConsentAttributesByName(Mockito.anyString());
Mockito.doReturn(getConsent(offsetDateTime.toEpochSecond())).when(consentCoreServiceImpl)
.getDetailedConsent(Mockito.anyString());
Mockito.doReturn(headers).when(consentManageData).getHeaders();
Mockito.doReturn(CLIENT_ID).when(consentManageData).getClientId();
Mockito.doReturn("{}").when(consentManageData).getPayload();
Mockito.doReturn("{}").when(consentManageData).getPayload();
Mockito.doReturn("/payments/".concat(consentId)).when(consentManageData).getRequestPath();
new IdempotencyValidator().validateIdempotency(consentManageData);
}

@Test
public void testValidateIdempotencyWithoutIdempotencyKeyValue() throws IdempotencyValidationException {

Expand Down

0 comments on commit 5b0da62

Please sign in to comment.