diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java index 594df8f08..5ea1c2fea 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java @@ -38,6 +38,7 @@ public class StringPool { public static final String VALIDATE_EXPIRY_DATE = "validateExpiryDate"; public static final String DID_DOCUMENT = "didDocument"; public static final String VEHICLE_DISMANTLE = "vehicleDismantle"; + public static final String CREATED_AT = "createdAt"; private StringPool() { throw new IllegalStateException("Constant class"); diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/HoldersCredentialRepository.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/HoldersCredentialRepository.java index 49fa1260d..7ba31da2a 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/HoldersCredentialRepository.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/HoldersCredentialRepository.java @@ -60,7 +60,7 @@ public interface HoldersCredentialRepository extends BaseRepository getByHolderDidAndType(String holderDid, String type); - List getByHolderDidAndIssuerDidAndType(String holderDid, String issuerDid, String type); + List getByHolderDidAndIssuerDidAndTypeAndStored(String holderDid, String issuerDid, String type, boolean stored); /** * Exists by holder did and type boolean. diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialService.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialService.java index 0b7da81a8..f684cc107 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialService.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialService.java @@ -44,7 +44,6 @@ import org.eclipse.tractusx.managedidentitywallets.dto.IssueMembershipCredentialRequest; import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; import org.eclipse.tractusx.managedidentitywallets.exception.DuplicateCredentialProblem; -import org.eclipse.tractusx.managedidentitywallets.exception.DuplicateSummaryCredentialProblem; import org.eclipse.tractusx.managedidentitywallets.exception.ForbiddenException; import org.eclipse.tractusx.managedidentitywallets.utils.CommonUtils; import org.eclipse.tractusx.managedidentitywallets.utils.Validate; @@ -470,23 +469,19 @@ private boolean isSelfIssued(String holderBpn) { */ private void updateSummeryCredentials(DidDocument issuerDidDocument, byte[] issuerPrivateKey, String issuerDid, String holderBpn, String holderDid, String type) { - //get summery VC of holder - List vcs = holdersCredentialRepository.getByHolderDidAndIssuerDidAndType(holderDid, issuerDid, MIWVerifiableCredentialType.SUMMARY_CREDENTIAL); + //get last issued summary vc to holder to update items + Page filter = getLastIssuedSummaryCredential(issuerDid, holderDid); List items; - if (CollectionUtils.isEmpty(vcs)) { - log.debug("No summery VC found for did ->{}", holderDid); - items = List.of(type); - } else { - Validate.isTrue(vcs.size() > 1).launch(new DuplicateSummaryCredentialProblem("Something is not right, there should be only one summery VC of holder at a time")); - HoldersCredential summeryCredential = vcs.get(0); + if (!filter.getContent().isEmpty()) { + IssuersCredential issuersCredential = filter.getContent().get(0); //check if summery VC has subject - Validate.isTrue(summeryCredential.getData().getCredentialSubject().isEmpty()).launch(new BadDataException("VC subject not found in existing su,,ery VC")); + Validate.isTrue(issuersCredential.getData().getCredentialSubject().isEmpty()).launch(new BadDataException("VC subject not found in existing su,,ery VC")); //Check if we have only one subject in summery VC - Validate.isTrue(summeryCredential.getData().getCredentialSubject().size() > 1).launch(new BadDataException("VC subjects can more then 1 in case of summery VC")); + Validate.isTrue(issuersCredential.getData().getCredentialSubject().size() > 1).launch(new BadDataException("VC subjects can more then 1 in case of summery VC")); - VerifiableCredentialSubject subject = summeryCredential.getData().getCredentialSubject().get(0); + VerifiableCredentialSubject subject = issuersCredential.getData().getCredentialSubject().get(0); if (subject.containsKey(StringPool.ITEMS)) { items = (List) subject.get(StringPool.ITEMS); if (!items.contains(type)) { @@ -495,7 +490,17 @@ private void updateSummeryCredentials(DidDocument issuerDidDocument, byte[] issu } else { items = List.of(type); } - //delete old summery VC from holder table + } else { + items = List.of(type); + } + log.debug("Issuing summary VC with items ->{}", items); + + //get summery VC of holder + List vcs = holdersCredentialRepository.getByHolderDidAndIssuerDidAndTypeAndStored(holderDid, issuerDid, MIWVerifiableCredentialType.SUMMARY_CREDENTIAL, false); //deleted only not stored VC + if (CollectionUtils.isEmpty(vcs)) { + log.debug("No summery VC found for did ->{}, checking in issuer", holderDid); + } else { + //delete old summery VC from holder table, delete only not stored VC holdersCredentialRepository.deleteAll(vcs); } @@ -525,4 +530,22 @@ private void updateSummeryCredentials(DidDocument issuerDidDocument, byte[] issu log.info("Summery VC updated for holder did -> {}", holderDid); } + + private Page getLastIssuedSummaryCredential(String issuerDid, String holderDid) { + FilterRequest filterRequest = new FilterRequest(); + + //we need latest one record + filterRequest.setPage(0); + filterRequest.setSize(1); + Sort sort = new Sort(); + sort.setColumn(StringPool.CREATED_AT); + sort.setSortType(SortType.valueOf("desc".toUpperCase())); + filterRequest.setSort(sort); + + filterRequest.appendCriteria(StringPool.HOLDER_DID, Operator.EQUALS, holderDid); + filterRequest.appendCriteria(StringPool.ISSUER_DID, Operator.EQUALS, issuerDid); + filterRequest.appendCriteria(StringPool.TYPE, Operator.EQUALS, MIWVerifiableCredentialType.SUMMARY_CREDENTIAL); + + return filter(filterRequest); + } } \ No newline at end of file diff --git a/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/MembershipHoldersCredentialTest.java b/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/MembershipHoldersCredentialTest.java index 31442fe37..700f71a95 100644 --- a/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/MembershipHoldersCredentialTest.java +++ b/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/MembershipHoldersCredentialTest.java @@ -53,6 +53,7 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {ManagedIdentityWalletsApplication.class}) @@ -96,19 +97,100 @@ void issueMembershipCredentialTest403() { } @Test - void issueMembershipCredentialToBaseWalletTest409() throws JsonProcessingException { + void testIssueSummeryVCAfterDeleteSummaryVCFromHolderWallet() throws JsonProcessingException { String bpn = UUID.randomUUID().toString(); String did = "did:web:localhost:" + bpn; // create wallet, in background bpn and summary credential generated - TestUtils.getWalletFromString(TestUtils.createWallet(bpn, bpn, restTemplate).getBody()); + Wallet wallet = TestUtils.getWalletFromString(TestUtils.createWallet(bpn, bpn, restTemplate).getBody()); + + List byHolderDid = holdersCredentialRepository.getByHolderDid(did); - // create manually 2nd summary credential - issueSummaryCredentialinDB(did); + //delete all VC + holdersCredentialRepository.deleteAll(byHolderDid); - // get 409 status as 2 summary credential found in holder table + //issue membership ResponseEntity response = TestUtils.issueMembershipVC(restTemplate, bpn, miwSettings.authorityWalletBpn()); - Assertions.assertEquals(HttpStatus.CONFLICT.value(), response.getStatusCode().value()); + Assertions.assertEquals(response.getStatusCode().value(), HttpStatus.CREATED.value()); + + //check summary VC in holder wallet + List summaryVcs = holdersCredentialRepository.getByHolderDidAndIssuerDidAndTypeAndStored(did, miwSettings.authorityWalletDid(), MIWVerifiableCredentialType.SUMMARY_CREDENTIAL, false); + Assertions.assertFalse(summaryVcs.isEmpty()); + + //check items, it should be 2 + List items = (List) summaryVcs.get(0).getData().getCredentialSubject().get(0).get(StringPool.ITEMS); + + Assertions.assertTrue(items.contains(MIWVerifiableCredentialType.MEMBERSHIP_CREDENTIAL_CX)); + Assertions.assertTrue(items.contains(MIWVerifiableCredentialType.BPN_CREDENTIAL_CX)); + } + + @Test + void testStoredSummaryVCTest() throws JsonProcessingException { + String bpn = UUID.randomUUID().toString(); + String did = "did:web:localhost:" + bpn; + + // create wallet, in background bpn and summary credential generated + Wallet wallet = TestUtils.getWalletFromString(TestUtils.createWallet(bpn, bpn, restTemplate).getBody()); + + + String vc = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "urn:uuid:12345678-1234-1234-1234-123456789abc", + "type": [ + "VerifiableCredential", + "SummaryCredential" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "issuanceDate": "2023-06-02T12:00:00Z", + "expirationDate": "2022-06-16T18:56:59Z", + "credentialSubject": [{ + "id": "did:web:localhost:BPNL000000000000", + "holderIdentifier": "BPN of holder", + "type": "Summary-List", + "name": "CX-Credentials", + "items": [ + "cx-active-member", + "cx-dismantler", + "cx-pcf", + "cx-sustainability", + "cx-quality", + "cx-traceability", + "cx-behavior-twin", + "cx-bpn" + ], + "contract-templates": "https://public.catena-x.org/contracts/" + },{ + "name":"test name" + }], + "proof": { + "type": "Ed25519Signature2018", + "created": "2023-06-02T12:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:example.com#key-1", + "jws": "eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjE2MjM1NzA3NDEsImV4cCI6MTYyMzU3NDM0MSwianRpIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5YWJjIiwicHJvb2YiOnsiaWQiOiJkaWQ6d2ViOmV4YW1wbGUuY29tIiwibmFtZSI6IkJlaXNwaWVsLU9yZ2FuaXNhdGlvbiJ9fQ.SignedExampleSignature" + } + } + """; + HttpHeaders headers = AuthenticationUtils.getValidUserHttpHeaders(bpn); + + Map map = objectMapper.readValue(vc.replace("##did", did), Map.class); + HttpEntity entity = new HttpEntity<>(map, headers); + + ResponseEntity response = restTemplate.exchange(RestURI.API_WALLETS_IDENTIFIER_CREDENTIALS, HttpMethod.POST, entity, Map.class, bpn); + Assertions.assertEquals(HttpStatus.CREATED.value(), response.getStatusCode().value()); + + //issue membership + ResponseEntity response1 = TestUtils.issueMembershipVC(restTemplate, bpn, miwSettings.authorityWalletBpn()); + Assertions.assertEquals(HttpStatus.CREATED.value(), response1.getStatusCode().value()); + + //stored VC should not be deleted + List summaryCredential = holdersCredentialRepository.getByHolderDidAndIssuerDidAndTypeAndStored(wallet.getDid(), "did:web:localhost:BPNL000000000000", "SummaryCredential", true); + Assertions.assertFalse(summaryCredential.isEmpty()); + } @Test @@ -120,7 +202,7 @@ void issueMembershipCredentialToBaseWalletTest400() throws JsonProcessingExcepti Wallet wallet = TestUtils.getWalletFromString(TestUtils.createWallet(bpn, bpn, restTemplate).getBody()); //add 2 subject in VC for testing - List vcs = holdersCredentialRepository.getByHolderDidAndType(wallet.getDid(), MIWVerifiableCredentialType.SUMMARY_CREDENTIAL); + List vcs = issuersCredentialRepository.getByIssuerDidAndHolderDidAndType(miwSettings.authorityWalletDid(), wallet.getDid(), MIWVerifiableCredentialType.SUMMARY_CREDENTIAL); String vc = """ { @@ -167,7 +249,7 @@ void issueMembershipCredentialToBaseWalletTest400() throws JsonProcessingExcepti VerifiableCredential verifiableCredential = new VerifiableCredential(new ObjectMapper().readValue(vc, Map.class)); vcs.get(0).setData(verifiableCredential); - holdersCredentialRepository.save(vcs.get(0)); + issuersCredentialRepository.save(vcs.get(0)); //Check if we do not have items in subject ResponseEntity response = TestUtils.issueMembershipVC(restTemplate, bpn, miwSettings.authorityWalletBpn()); @@ -175,7 +257,7 @@ void issueMembershipCredentialToBaseWalletTest400() throws JsonProcessingExcepti vcs.get(0).getData().getCredentialSubject().remove(1); vcs.get(0).getData().getCredentialSubject().get(0).remove(StringPool.ITEMS); - holdersCredentialRepository.save(vcs.get(0)); + issuersCredentialRepository.save(vcs.get(0)); } @@ -292,51 +374,4 @@ private void validateTypes(VerifiableCredential verifiableCredential, String hol Assertions.assertTrue(verifiableCredential.getTypes().contains(MIWVerifiableCredentialType.MEMBERSHIP_CREDENTIAL_CX)); Assertions.assertEquals(verifiableCredential.getCredentialSubject().get(0).get(StringPool.HOLDER_IDENTIFIER), holderBpn); } - - private void issueSummaryCredentialinDB(String did) throws JsonProcessingException { - String vc = """ - { - "id": "http://example.edu/credentials/3732", - "@context": - [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1" - ], - "type": - [ - "SummaryCredential", "VerifiableCredential" - ], - "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f", - "issuanceDate": "2019-06-16T18:56:59Z", - "expirationDate": "2019-06-17T18:56:59Z", - "credentialSubject": - [ - { - "id": "##did", - "college": "Test-University" - } - ], - "proof": - { - "type": "Ed25519Signature2018", - "created": "2021-11-17T22:20:27Z", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1", - "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg" - } - } - """; - VerifiableCredential verifiableCredential = new VerifiableCredential(new ObjectMapper().readValue(vc, Map.class)); - HoldersCredential holdersCredential = HoldersCredential.builder() - .credentialId(verifiableCredential.getId().toString()) - .data(new VerifiableCredential(verifiableCredential)) - .holderDid(did) - .type(MIWVerifiableCredentialType.SUMMARY_CREDENTIAL) - .selfIssued(false) - .stored(false) - .issuerDid(miwSettings.authorityWalletDid()) - .build(); - holdersCredentialRepository.save(holdersCredential); - } - }