Skip to content

Commit

Permalink
[MOSIP-35455] Unlimited download support and changing the transaction…
Browse files Browse the repository at this point in the history
…Allowed (Using request param) for share to be created in standalone mode. (#211)

* [MOSIP-35455] Unlimited download support and changing the transactionAllowed (Using request param) for share to be created in standalone mode.

Signed-off-by: Ashok Kumar Sharma <[email protected]>

* [MOSIP-35455] Unlimited download support and changing the transactionAllowed (Using request param) for share to be created in standalone mode.

Signed-off-by: Ashok Kumar Sharma <[email protected]>

---------

Signed-off-by: Ashok Kumar Sharma <[email protected]>
Signed-off-by: Vishwa <[email protected]>
  • Loading branch information
ashok-ksharma authored Oct 7, 2024
1 parent 9a43ad4 commit b7c3104
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 22 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ Refer to [MOSIP docs](https://docs.mosip.io/1.2.0/modules/datashare).
## APIs
API documentation is available [here](https://docs.mosip.io/1.2.0/api).

## Durian in standalone mode
Durian application has dependency on other MOSIP module services such as partner management service for policy verification and key management service for crypto operations such as signature computation, Encryption etc.
Durian application can be configured in the standalone mode where it doesn't depend on the external services to perform the operations.
To configure the same, below properties should be used:
1. **mosip.data.share.standalone.mode.enabled:** This property enables application to run in standalone mode. The value for the property should be **true**.
2. **mosip.data.share.static-policy.policy-json:** This property contains policy JSON which will used as static policy for the data share creation in standalone mode. The property value can be below:
{"typeOfShare":"","transactionsAllowed":"2","shareDomain":"datashare.datashare","encryptionType":"NONE","source":"","validForInMinutes":"30"}
**transactionsAllowed** attribute value of -1 allows unlimited transaction on the created share.
**encryptionType** attribute is kept as **NONE** so that encryption won't be performed and dependency on key manager will not be there.
3. **mosip.data.share.static-policy.policy-id:** This property contains the policy id which will be used for creating the data share. This property must match with the {policyId} received in the **/create** API otherwise error will be thrown.
4. **mosip.data.share.static-policy.subscriber-id:** This property contains the subscriber id which will be used for creating the data share. This property must match with the {subscriberId} received in the **/create** API otherwise error will be thrown.
5. **mosip.data.share.signature.disabled:** This property enables/disables the signature computation for the created data share. This property value must be **true**.

Standalone mode enablement is not advisable as part of MOSIP identity platform deployment because it bypasses the policy verification and signature computation for the created data share. It makes difficult to detect the integrity issue and restricts dynamic policy based data share generation.

## License
This project is licensed under the terms of [Mozilla Public License 2.0](LICENSE).

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.mosip.datashare.controller;

import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
Expand All @@ -15,6 +16,7 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import io.mosip.datashare.dto.DataShare;
Expand Down Expand Up @@ -70,10 +72,11 @@ public class DataShareController {
@ApiResponse(responseCode = "403", description = "Forbidden" ,content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", description = "Not Found" ,content = @Content(schema = @Schema(hidden = true)))})
public ResponseEntity<Object> createDataShare(@RequestBody MultipartFile file,
@PathVariable("policyId") String policyId, @PathVariable("subscriberId") String subscriberId) {

@PathVariable("policyId") String policyId, @PathVariable("subscriberId") String subscriberId,
@Parameter(description = "Usage count for standalone mode") @RequestParam(required = false, name = "usageCountForStandaloneMode") String usageCountForStandaloneMode) {

DataShare dataShare = dataShareService.createDataShare(policyId, subscriberId, file);

DataShare dataShare = dataShareService.createDataShare(policyId, subscriberId, file, usageCountForStandaloneMode);
return ResponseEntity.status(HttpStatus.OK)
.body(buildDataShareResponse(dataShare));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ public interface DataShareService {
* @param policyId the policy id
* @param subscriberId the subscriber id
* @param file the file
* @param usageCountForStandaloneMode the usage count for standalone mode
* @return the data share
*/
public DataShare createDataShare(String policyId, String subscriberId, MultipartFile file);
public DataShare createDataShare(String policyId, String subscriberId,
MultipartFile file, String usageCountForStandaloneMode);

/**
* Gets the data file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ public class DataShareServiceImpl implements DataShareService {

/** The Constant DATETIME_PATTERN. */
private static final String DATETIME_PATTERN = "mosip.data.share.datetime.pattern";

/** The constant defines unlimited usage count for the created share */
public static final int UNLIMITED_USAGE_COUNT = -1;

/*
* (non-Javadoc)
*
Expand All @@ -138,7 +142,8 @@ public class DataShareServiceImpl implements DataShareService {
* java.lang.String, org.springframework.web.multipart.MultipartFile)
*/
@Override
public DataShare createDataShare(String policyId, String subscriberId, MultipartFile file) {
public DataShare createDataShare(String policyId, String subscriberId, MultipartFile file,
String usageCountForStandaloneMode) {
LOGGER.debug(LoggerFileConstant.SESSIONID.toString(), LoggerFileConstant.POLICYID.toString(), policyId,
"DataShareServiceImpl::createDataShare()::entry");
DataShare dataShare = new DataShare();
Expand All @@ -155,7 +160,7 @@ public DataShare createDataShare(String policyId, String subscriberId, Multipart
dataSharePolicy = policyDetailResponse.getPolicies().getDataSharePolicies();
policyPublishDate = policyDetailResponse.getPublishDate();
} else {
dataSharePolicy = policyUtil.getStaticDataSharePolicy(policyId, subscriberId);
dataSharePolicy = policyUtil.getStaticDataSharePolicy(policyId, subscriberId, usageCountForStandaloneMode);
}
byte[] encryptedData = null;
if (PARTNERBASED.equalsIgnoreCase(dataSharePolicy.getEncryptionType())) {
Expand Down Expand Up @@ -330,6 +335,12 @@ private boolean getAndUpdateMetaData(String randomShareKey, String policyId, Str
LOGGER.info(LoggerFileConstant.SESSIONID.toString(), LoggerFileConstant.POLICYID.toString(), policyId,
"Successfully update the metadata");
}
/* Unlimited usage is allowed hence not updating the metadata*/
if(transactionAllowed == UNLIMITED_USAGE_COUNT) {
isDataShareAllow = true;
LOGGER.info(LoggerFileConstant.SESSIONID.toString(), LoggerFileConstant.POLICYID.toString(), policyId,
"Unlimited usage of data share is configured hence not updating metadata");
}

}
LOGGER.debug(LoggerFileConstant.SESSIONID.toString(), LoggerFileConstant.POLICYID.toString(), policyId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Objects;

import io.mosip.datashare.dto.DataShareDto;
import io.mosip.datashare.service.impl.DataShareServiceImpl;
import jakarta.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -132,16 +133,25 @@ public void emptyPartnerPolicyCache() {
* Provides static data share policy for sharing the data.
* @param policyId Policy Id from request
* @param subscriberId Subscriber Id from request
* @param usageCountForStandaloneMode Usage count for standalone mode from request
* @return the DataShareDto object
*/
public DataShareDto getStaticDataSharePolicy(String policyId, String subscriberId) {
public DataShareDto getStaticDataSharePolicy(String policyId, String subscriberId, String usageCountForStandaloneMode) {
LOGGER.debug(LoggerFileConstant.SESSIONID.toString(), LoggerFileConstant.POLICYID.toString(),
policyId, "PolicyUtil::getStaticDataSharePolicy()::entry");
try {
if (!policyId.equals(staticPolicyId) || !subscriberId.equals(staticSubscriberId))
throw new PolicyException("Either Policy Id or Subscriber Id not matching with configured in system");

DataShareDto dataShareDto = mapper.readValue(staticPolicyJson, DataShareDto.class);
/* usageCountForStandaloneMode attribute from request will take precedence
over the transactionAllowed configured in static data share policy in standalone mode */
if(StringUtils.isNotEmpty(usageCountForStandaloneMode)) {
validateUsageCountForStandaloneMode(usageCountForStandaloneMode);
LOGGER.debug(LoggerFileConstant.SESSIONID.toString(), LoggerFileConstant.POLICYID.toString(), policyId,
"Overriding the transactionAllowed configured in static data share policy : " + usageCountForStandaloneMode);
dataShareDto.setTransactionsAllowed(usageCountForStandaloneMode);
}
LOGGER.debug(LoggerFileConstant.SESSIONID.toString(), LoggerFileConstant.POLICYID.toString(), policyId,
"PolicyUtil::getStaticDataSharePolicy()::exit");
return dataShareDto;
Expand All @@ -155,6 +165,17 @@ public DataShareDto getStaticDataSharePolicy(String policyId, String subscriberI
}
}

private void validateUsageCountForStandaloneMode(String usageCountForStandaloneMode) {
try {
int usageCount = Integer.parseInt(usageCountForStandaloneMode);
if(usageCount == 0 || usageCount < DataShareServiceImpl.UNLIMITED_USAGE_COUNT)
throw new PolicyException("usageCountForStandaloneMode must not be 0 or less than " +
DataShareServiceImpl.UNLIMITED_USAGE_COUNT);
} catch (NumberFormatException e) {
throw new PolicyException("usageCountForStandaloneMode must be a number");
}
}

/** This method validates the properties configured for the standalone mode for the data-share application.*/
@PostConstruct
private void validateStandaloneDataShareProperties() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void setup() throws Exception {
public void testDataShareSuccess() throws Exception {
DataShare dataShare=new DataShare();
doReturn(dataShare).when(dataShareService).createDataShare(Mockito.anyString(), Mockito.anyString(),
Mockito.any(MultipartFile.class));
Mockito.any(MultipartFile.class), Mockito.anyString());
String sample = "Test";
Mockito.when(env.getProperty("mosip.data.share.datetime.pattern"))
.thenReturn("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,29 +162,29 @@ public void createDataShareSuccessWithoutEncryptionTest() {
policyResponseDto.setPolicies(policyAttributesDto);
Mockito.when(policyUtil.getPolicyDetail(Mockito.anyString(), Mockito.anyString()))
.thenReturn(policyResponseDto);
DataShare dataShare = dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile);
DataShare dataShare = dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile, null);
assertEquals("Data Share created successfully", POLICY_ID, dataShare.getPolicyId());
}

@Test
public void createDataShareSuccessTest() {

DataShare dataShare = dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile);
DataShare dataShare = dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile, null);
assertEquals("Data Share created successfully", POLICY_ID, dataShare.getPolicyId());
}

@Test
public void createDataShareSuccesswithShortUrlTest() {
Mockito.when(env.getProperty("mosip.data.share.key.length")).thenReturn("8");
ReflectionTestUtils.setField(dataShareServiceImpl, "isShortUrl", true);
DataShare dataShare = dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile);
DataShare dataShare = dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile, null);
assertEquals("Data Share created successfully", POLICY_ID, dataShare.getPolicyId());
}

@Test(expected = FileException.class)
public void fileExceptionTest() {
multiPartFile=null;
dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile);
dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile, null);
}

@Test
Expand Down Expand Up @@ -252,26 +252,26 @@ public void createStaticDataShareSuccessTest() {
dataShareDto.setEncryptionType("NONE");
dataShareDto.setSource("");
dataShareDto.setValidForInMinutes("30");
Mockito.when(policyUtil.getStaticDataSharePolicy(Mockito.anyString(), Mockito.anyString()))
Mockito.when(policyUtil.getStaticDataSharePolicy(Mockito.anyString(), Mockito.anyString(), Mockito.isNull()))
.thenReturn(dataShareDto);
String policyId = "static-policyid";
String subscriberId = "static-subscriberid";
DataShare dataShare = dataShareServiceImpl.createDataShare(policyId, subscriberId, multiPartFile);
DataShare dataShare = dataShareServiceImpl.createDataShare(policyId, subscriberId, multiPartFile, null);
assertEquals("Data Share created successfully", policyId, dataShare.getPolicyId());
}

@Test(expected = PolicyException.class)
public void createStaticDataSharePolicyExceptionTest() {
ReflectionTestUtils.setField(dataShareServiceImpl, "standaloneModeEnabled", true);
Mockito.doThrow(new PolicyException()).
when(policyUtil).getStaticDataSharePolicy(Mockito.anyString(), Mockito.anyString());
dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile);
when(policyUtil).getStaticDataSharePolicy(Mockito.anyString(), Mockito.anyString(), Mockito.isNull());
dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile, null);
}

@Test
public void disableSignatureSuccessTest() {
ReflectionTestUtils.setField(dataShareServiceImpl, "isSignatureDisabled", true);
DataShare dataShare = dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile);
DataShare dataShare = dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile, null);
Mockito.verify(digitalSignatureUtil, Mockito.never()).jwtSign(Mockito.any(),
Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
assertEquals("Data Share created successfully", POLICY_ID, dataShare.getPolicyId());
Expand All @@ -280,9 +280,22 @@ public void disableSignatureSuccessTest() {
@Test
public void enableSignatureSuccessTest() {
ReflectionTestUtils.setField(dataShareServiceImpl, "isSignatureDisabled", false);
DataShare dataShare = dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile);
DataShare dataShare = dataShareServiceImpl.createDataShare(POLICY_ID, SUBSCRIBER_ID, multiPartFile, null);
Mockito.verify(digitalSignatureUtil, Mockito.atLeastOnce()).jwtSign(Mockito.any(),
Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
assertEquals("Data Share created successfully", POLICY_ID, dataShare.getPolicyId());
}

@Test
public void getDataFileWithUnlimitedUsageSuccessTest() {
metaDataMap = new HashMap<String, Object>();
metaDataMap.put("transactionsallowed", "-1");
Mockito.when(objectStoreAdapter.getMetaData(Mockito.anyString(), Mockito.anyString(), Mockito.any(),
Mockito.any(), Mockito.anyString()
)).thenReturn(metaDataMap);
dataShareServiceImpl.getDataFile(POLICY_ID, SUBSCRIBER_ID, "12dfsdff");
Mockito.verify(objectStoreAdapter, Mockito.never()).decMetadata(Mockito.anyString(),
Mockito.anyString(), Mockito.anyString(), Mockito.anyString(),
Mockito.anyString(), Mockito.anyString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public void staticPolicyIdNotMatchingWithRequest() {
ReflectionTestUtils.setField(policyUtil, "staticPolicyId", "static-policyid");
ReflectionTestUtils.setField(policyUtil, "staticSubscriberId", "static-subscriberid");
Exception exception = assertThrows(PolicyException.class, () -> {
policyUtil.getStaticDataSharePolicy("1234", "static-subscriberid");
policyUtil.getStaticDataSharePolicy("1234", "static-subscriberid", null);
});
assertTrue(exception.getMessage().contains("DAT-SER-007"));
}
Expand All @@ -168,7 +168,7 @@ public void staticSubscriberIdNotMatchingWithRequest() {
ReflectionTestUtils.setField(policyUtil, "staticPolicyId", "static-policyid");
ReflectionTestUtils.setField(policyUtil, "staticSubscriberId", "static-subscriberid");
Exception exception = assertThrows(PolicyException.class, () -> {
policyUtil.getStaticDataSharePolicy("static-policyid", "1234");
policyUtil.getStaticDataSharePolicy("static-policyid", "1234", null);
});
assertTrue(exception.getMessage().contains("DAT-SER-007"));
}
Expand All @@ -183,7 +183,7 @@ public void invalidStaticJsonPolicy() throws JsonProcessingException {
Mockito.when(objectMapper.readValue(Mockito.anyString(), Mockito.any(Class.class))).
thenThrow(new JsonParseException("Exception"));
Exception exception = assertThrows(PolicyException.class, () -> {
policyUtil.getStaticDataSharePolicy("static-policyid", "static-subscriberid");
policyUtil.getStaticDataSharePolicy("static-policyid", "static-subscriberid", null);
});
assertTrue(exception.getMessage().contains("DAT-SER-007"));
}
Expand All @@ -205,7 +205,58 @@ public void getStaticDateSharePolicySuccessTest() throws JsonProcessingException
dataShareDto.setValidForInMinutes("30");
Mockito.when(objectMapper.readValue(Mockito.anyString(), Mockito.any(Class.class))).
thenReturn(dataShareDto);
DataShareDto response = policyUtil.getStaticDataSharePolicy("static-policyid", "static-subscriberid");
DataShareDto response = policyUtil.getStaticDataSharePolicy("static-policyid", "static-subscriberid", null);
assertEquals(dataShareDto.getEncryptionType(), response.getEncryptionType());
}

@Test
public void getStaticDateSharePolicyWithTransactionAllowedSuccessTest() throws JsonProcessingException {
ReflectionTestUtils.setField(policyUtil, "standaloneModeEnabled", true);
ReflectionTestUtils.setField(policyUtil, "staticPolicyId", "static-policyid");
ReflectionTestUtils.setField(policyUtil, "staticSubscriberId", "static-subscriberid");
ReflectionTestUtils.setField(policyUtil, "staticPolicyJson",
"{\"typeOfShare\":\"\",\"transactionsAllowed\":\"2\"," +
"\"shareDomain\":\"datashare.datashare\",\"encryptionType\":\"NONE\",\"source\":\"\",\"validForInMinutes\":\"30\"}");
DataShareDto dataShareDto = new DataShareDto();
dataShareDto.setTypeOfShare("");
dataShareDto.setTransactionsAllowed("2");
dataShareDto.setShareDomain("datashare.datashare");
dataShareDto.setEncryptionType("NONE");
dataShareDto.setSource("");
dataShareDto.setValidForInMinutes("30");
Mockito.when(objectMapper.readValue(Mockito.anyString(), Mockito.any(Class.class))).
thenReturn(dataShareDto);
String usageCountForStandaloneMode = "10";
DataShareDto response = policyUtil.getStaticDataSharePolicy("static-policyid", "static-subscriberid", usageCountForStandaloneMode);
assertEquals(response.getTransactionsAllowed(), usageCountForStandaloneMode);
}

@Test(expected = PolicyException.class)
public void validateUsageCountForStandaloneModeWithInvalidValue() {
String usageCountForStandaloneMode = "abc";
ReflectionTestUtils.invokeMethod(policyUtil,
"validateUsageCountForStandaloneMode", usageCountForStandaloneMode);
}

@Test(expected = PolicyException.class)
public void validateUsageCountForStandaloneModeWithZero() {
String usageCountForStandaloneMode = "0";
ReflectionTestUtils.invokeMethod(policyUtil,
"validateUsageCountForStandaloneMode", usageCountForStandaloneMode);
}

@Test(expected = PolicyException.class)
public void validateUsageCountForStandaloneModeWithValueLessThanOne() {
String usageCountForStandaloneMode = "-2";
ReflectionTestUtils.invokeMethod(policyUtil,
"validateUsageCountForStandaloneMode", usageCountForStandaloneMode);
}

@Test
public void validateUsageCountForStandaloneModeWithValidValue() {
String usageCountForStandaloneMode = "10";
ReflectionTestUtils.invokeMethod(policyUtil,
"validateUsageCountForStandaloneMode", usageCountForStandaloneMode);
}

}

0 comments on commit b7c3104

Please sign in to comment.