-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from kaifk468/master
[ES-504] added sunbirdrcVciIssuancePlugin
- Loading branch information
Showing
2 changed files
with
267 additions
and
36 deletions.
There are no files selected for viewing
36 changes: 0 additions & 36 deletions
36
...pl/src/main/java/io/mosip/esignet/sunbirdrc/integration/service/MockVCIssuancePlugin.java
This file was deleted.
Oops, something went wrong.
267 changes: 267 additions & 0 deletions
267
...c/main/java/io/mosip/esignet/sunbirdrc/integration/service/SunbirdRCVCIssuancePlugin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
/* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
package io.mosip.esignet.sunbirdrc.integration.service; | ||
|
||
|
||
import java.io.StringWriter; | ||
import java.time.LocalDateTime; | ||
import java.util.*; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import io.mosip.esignet.api.exception.VCIExchangeException; | ||
import io.mosip.esignet.api.util.ErrorConstants; | ||
import org.apache.velocity.Template; | ||
import org.apache.velocity.VelocityContext; | ||
import org.apache.velocity.app.VelocityEngine; | ||
import org.apache.velocity.runtime.RuntimeConstants; | ||
import org.apache.velocity.runtime.resource.loader.URLResourceLoader; | ||
import org.json.JSONArray; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
import org.springframework.core.ParameterizedTypeReference; | ||
import org.springframework.core.env.Environment; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.http.RequestEntity; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.stereotype.Component; | ||
import foundation.identity.jsonld.JsonLDObject; | ||
import io.mosip.esignet.api.dto.VCRequestDto; | ||
import io.mosip.esignet.api.dto.VCResult; | ||
import io.mosip.esignet.api.spi.VCIssuancePlugin; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.web.client.RestTemplate; | ||
import org.springframework.web.util.UriComponentsBuilder; | ||
|
||
import javax.annotation.PostConstruct; | ||
|
||
|
||
@ConditionalOnProperty(value = "mosip.esignet.integration.vci-plugin", havingValue = "SunbirdRCVCIssuancePlugin") | ||
@Component | ||
@Slf4j | ||
public class SunbirdRCVCIssuancePlugin implements VCIssuancePlugin { | ||
|
||
private static final String CREDENTIAL_TYPE_PROPERTY_PREFIX ="mosip.esignet.vciplugin.sunbird-rc.credential-type"; | ||
|
||
private static final String LINKED_DATA_PROOF_VC_FORMAT ="ldp_vc"; | ||
|
||
private static final String TEMPLATE_URL = "template-url"; | ||
|
||
private static final String REGISTRY_GET_URL = "registry-get-url"; | ||
|
||
private static final String CRED_SCHEMA_ID = "cred-schema-id"; | ||
|
||
private static final String CRED_SCHEMA_VESRION = "cred-schema-version"; | ||
|
||
private static final String STATIC_VALUE_MAP_ISSUER_ID = "static-value-map.issuerId"; | ||
|
||
@Autowired | ||
Environment env; | ||
|
||
@Autowired | ||
ObjectMapper mapper; | ||
|
||
@Autowired | ||
private RestTemplate restTemplate; | ||
|
||
@Value("${mosip.esignet.vciplugin.sunbird-rc.issue-credential-url}") | ||
String issueCredentialUrl; | ||
|
||
@Value("#{'${mosip.esignet.vciplugin.sunbird-rc.supported-credential-types}'.split(',')}") | ||
List<String> supportedCredentialTypes; | ||
|
||
private final Map<String, Template> credentialTypeTemplates = new HashMap<>(); | ||
|
||
private final Map<String,Map<String,String>> credentialTypeConfigMap = new HashMap<>(); | ||
|
||
private VelocityEngine vEngine; | ||
|
||
|
||
@PostConstruct | ||
public void initialize() throws VCIExchangeException { | ||
|
||
vEngine = new VelocityEngine(); | ||
vEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "url"); | ||
vEngine.setProperty("url.resource.loader.class", URLResourceLoader.class.getName()); | ||
vEngine.init(); | ||
//Validate all the supported VC | ||
for (String credentialType : supportedCredentialTypes) { | ||
validateAndCachePropertiesForCredentialType(credentialType.trim()); | ||
} | ||
} | ||
|
||
@Override | ||
public VCResult<JsonLDObject> getVerifiableCredentialWithLinkedDataProof(VCRequestDto vcRequestDto, String holderId, Map<String, Object> identityDetails) throws VCIExchangeException { | ||
if (vcRequestDto == null || vcRequestDto.getType() == null) { | ||
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); | ||
} | ||
List<String> types = vcRequestDto.getType(); | ||
if (types.isEmpty() || !types.get(0).equals("VerifiableCredential")) { | ||
log.error("Invalid request: first item in type is not VerifiableCredential"); | ||
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); | ||
} | ||
types.remove(0); | ||
String requestedCredentialType = String.join("-", types); | ||
//Check if the key is in the supported-credential-types | ||
if (!supportedCredentialTypes.contains(requestedCredentialType)) { | ||
log.error("Credential type is not supported"); | ||
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); | ||
} | ||
//Validate context of vcrequestdto with template | ||
List<String> contextList=vcRequestDto.getContext(); | ||
for(String supportedType:supportedCredentialTypes){ | ||
Template template=credentialTypeTemplates.get(supportedType); | ||
validateContextUrl(template,contextList); | ||
} | ||
String osid = (identityDetails.containsKey("sub")) ? (String) identityDetails.get("sub") : null; | ||
if (osid == null) { | ||
log.error("Invalid request: osid is null"); | ||
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); | ||
} | ||
String registryUrl=credentialTypeConfigMap.get(requestedCredentialType).get(REGISTRY_GET_URL); | ||
Map<String,Object> responseRegistryMap =fetchRegistryObject(registryUrl+osid); | ||
Map<String,Object> credentialRequestMap = createCredentialIssueRequest(requestedCredentialType, responseRegistryMap,vcRequestDto,holderId); | ||
Map<String,Object> vcResponseMap =sendCredentialIssueRequest(credentialRequestMap); | ||
|
||
VCResult vcResult = new VCResult(); | ||
JsonLDObject vcJsonLdObject = JsonLDObject.fromJsonObject(vcResponseMap); | ||
vcResult.setCredential(vcJsonLdObject); | ||
vcResult.setFormat(LINKED_DATA_PROOF_VC_FORMAT); | ||
return vcResult; | ||
} | ||
|
||
|
||
@Override | ||
public VCResult<String> getVerifiableCredential(VCRequestDto vcRequestDto, String holderId, Map<String, Object> identityDetails) throws VCIExchangeException { | ||
throw new VCIExchangeException(ErrorConstants.NOT_IMPLEMENTED); | ||
} | ||
|
||
private Map<String,Object> fetchRegistryObject(String entityUrl) throws VCIExchangeException { | ||
RequestEntity requestEntity = RequestEntity | ||
.get(UriComponentsBuilder.fromUriString(entityUrl).build().toUri()).build(); | ||
ResponseEntity<Map<String,Object>> responseEntity = restTemplate.exchange(requestEntity, | ||
new ParameterizedTypeReference<Map<String,Object>>() {}); | ||
if (responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) { | ||
return responseEntity.getBody(); | ||
}else { | ||
log.error("Sunbird service is not running. Status Code: " ,responseEntity.getStatusCode()); | ||
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); | ||
} | ||
} | ||
|
||
private Map<String,Object> createCredentialIssueRequest(String requestedCredentialType, Map<String,Object> registryObjectMap, VCRequestDto vcRequestDto, String holderId) throws VCIExchangeException { | ||
|
||
Template template=credentialTypeTemplates.get(requestedCredentialType); | ||
Map<String,String> configMap=credentialTypeConfigMap.get(requestedCredentialType); | ||
StringWriter writer = new StringWriter(); | ||
VelocityContext context = new VelocityContext(); | ||
Map<String,Object> requestMap=new HashMap<>(); | ||
context.put("currentDate", LocalDateTime.now()); | ||
context.put("issuerId", configMap.get(STATIC_VALUE_MAP_ISSUER_ID)); | ||
for (Map.Entry<String, Object> entry : registryObjectMap.entrySet()) { | ||
String key = entry.getKey(); | ||
Object value = entry.getValue(); | ||
if (value instanceof List) { | ||
JSONArray jsonArray = new JSONArray((List<String>) value); | ||
context.put(key, jsonArray); | ||
} else { | ||
context.put(key, value); | ||
} | ||
} | ||
template.merge(context, writer); | ||
try{ | ||
Map<String,Object> credentialObject =mapper.readValue(writer.toString(),Map.class); | ||
((Map<String, Object>) credentialObject.get("credentialSubject")).put("id", holderId); | ||
requestMap.put("credential", credentialObject); | ||
requestMap.put("credentialSchemaId",configMap.get(CRED_SCHEMA_ID)); | ||
requestMap.put("credentialSchemaVersion",configMap.get(CRED_SCHEMA_VESRION)); | ||
requestMap.put("tags",new ArrayList<>()); | ||
}catch (JsonProcessingException e){ | ||
log.error("Error while parsing the template ",e); | ||
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); | ||
} | ||
//TODO This need to be removed since it can contain PII | ||
log.info("VC requset is {}",requestMap); | ||
return requestMap; | ||
} | ||
|
||
private Map<String, Object> sendCredentialIssueRequest(Map<String,Object> credentialRequestMap) throws VCIExchangeException { | ||
try{ | ||
String requestBody=mapper.writeValueAsString(credentialRequestMap); | ||
RequestEntity requestEntity = RequestEntity | ||
.post(UriComponentsBuilder.fromUriString(issueCredentialUrl).build().toUri()) | ||
.contentType(MediaType.APPLICATION_JSON_UTF8) | ||
.body(requestBody); | ||
ResponseEntity<Map<String,Object>> responseEntity = restTemplate.exchange(requestEntity, | ||
new ParameterizedTypeReference<Map<String,Object>>(){}); | ||
if (responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null){ | ||
//TODO This need to be removed since it can contain PII | ||
log.debug("getting response {}", responseEntity); | ||
return responseEntity.getBody(); | ||
}else{ | ||
log.error("Sunbird service is not running. Status Code: " , responseEntity.getStatusCode()); | ||
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); | ||
} | ||
}catch (Exception e){ | ||
log.error("Unable to parse the Registry Object :{}",credentialRequestMap); | ||
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); | ||
} | ||
} | ||
|
||
private void validateAndCachePropertiesForCredentialType(String credentialType) throws VCIExchangeException { | ||
Map<String,String> configMap=new HashMap<>(); | ||
validateAndLoadProperty(CREDENTIAL_TYPE_PROPERTY_PREFIX + "." + credentialType + "." + TEMPLATE_URL,TEMPLATE_URL,configMap); | ||
validateAndLoadProperty(CREDENTIAL_TYPE_PROPERTY_PREFIX + "." + credentialType + "." + REGISTRY_GET_URL,REGISTRY_GET_URL,configMap); | ||
validateAndLoadProperty(CREDENTIAL_TYPE_PROPERTY_PREFIX + "." + credentialType + "." + CRED_SCHEMA_ID,CRED_SCHEMA_ID,configMap); | ||
validateAndLoadProperty(CREDENTIAL_TYPE_PROPERTY_PREFIX + "." + credentialType + "." + CRED_SCHEMA_VESRION,CRED_SCHEMA_VESRION,configMap); | ||
validateAndLoadProperty(CREDENTIAL_TYPE_PROPERTY_PREFIX + "." + credentialType + "." + STATIC_VALUE_MAP_ISSUER_ID,STATIC_VALUE_MAP_ISSUER_ID,configMap); | ||
|
||
String templateUrl = env.getProperty(CREDENTIAL_TYPE_PROPERTY_PREFIX +"." + credentialType + "." + TEMPLATE_URL); | ||
validateAndCacheTemplate(templateUrl,credentialType); | ||
// cache configuration with their credential type | ||
credentialTypeConfigMap.put(credentialType,configMap); | ||
} | ||
|
||
private void validateAndLoadProperty(String propertyName, String credentialProp, Map<String,String> configMap) throws VCIExchangeException { | ||
String propertyValue = env.getProperty(propertyName); | ||
if (propertyValue == null || propertyValue.isEmpty()) { | ||
throw new VCIExchangeException("Property " + propertyName + " is not set Properly."); | ||
} | ||
configMap.put(credentialProp,propertyValue); | ||
} | ||
|
||
private void validateAndCacheTemplate(String templateUrl, String credentialType){ | ||
Template template = vEngine.getTemplate(templateUrl); | ||
//Todo Validate if all the templates are valid JSON-LD documents | ||
credentialTypeTemplates.put(credentialType, template); | ||
} | ||
|
||
private void validateContextUrl(Template template,List<String> vcRequestContextList) throws VCIExchangeException { | ||
try{ | ||
StringWriter writer = new StringWriter(); | ||
template.merge(new VelocityContext(),writer); | ||
Map<String,Object> templateMap = mapper.readValue(writer.toString(),Map.class); | ||
List<String> contextList=(List<String>) templateMap.get("@context"); | ||
for(String contextUrl:vcRequestContextList){ | ||
if(!contextList.contains(contextUrl)){ | ||
log.error("ContextUrl is not supported"); | ||
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); | ||
} | ||
} | ||
}catch ( JsonProcessingException e){ | ||
log.error("Error while parsing the template ",e); | ||
throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); | ||
} | ||
} | ||
|
||
private static Date calculateNowPlus30Days() { | ||
// Implement your logic to calculate current date + 30 days | ||
Calendar calendar = Calendar.getInstance(); | ||
calendar.add(Calendar.DAY_OF_MONTH, 30); | ||
return calendar.getTime(); | ||
} | ||
} |