Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate presentaion submission with new validator #208

Merged
merged 3 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import web5.sdk.credentials.model.InputDescriptorV2
import web5.sdk.credentials.model.PresentationDefinitionV2
import web5.sdk.credentials.model.PresentationDefinitionV2Validator
import web5.sdk.credentials.model.PresentationSubmission
import web5.sdk.credentials.model.PresentationSubmissionValidator
import java.util.UUID

/**
Expand Down Expand Up @@ -136,6 +137,26 @@ public object PresentationExchange {
PresentationDefinitionV2Validator.validate(presentationDefinition)
}

/**
* Validates whether an object is usable as a presentation submission or not.
*
* Model as specified in https://identity.foundation/presentation-exchange/#presentation-submission.
*
* The checks are as follows:
* 1. Ensures that the presentation submission's id is not empty.
* 2. Validates that the definitionId is not empty.
* 3. Validates descriptorMap is a non-empty list.
* 4. Check for unique inputDescriptor ids at top level
* 5. Verifies the input descriptor mapping ids are the same on all levels of nesting.
* 6. Ensures that the path is valid across all levels of nesting
*
* Throws an [PexValidationException] if the provided object does not conform to the Presentation Definition
*/
@Throws(PexValidationException::class)
public fun validateSubmission(presentationSubmission: PresentationSubmission) {
nitro-neal marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this specify that it's for v2 like the PD one above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope there is only one version of submission and honestly we should probably remove the v2 from definition since I think we will only be using v2

PresentationSubmissionValidator.validate(presentationSubmission)
}

private fun mapInputDescriptorsToVCs(
vcJwtList: Iterable<String>,
presentationDefinition: PresentationDefinitionV2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,77 @@ public object FieldV2Validator {
}
}

/**
* PresentationSubmission Validator.
**/
public object PresentationSubmissionValidator {

@Throws(PexValidationException::class)

/**
* Validates a PresentationSubmission.
*
* This method performs several checks to ensure the integrity of the presentation submission model object:
* 1. Ensures that the presentation submission's id is not empty.
* 2. Validates that the definitionId is not empty.
* 3. Validates descriptorMap is a non-empty list.
* 4. Check for unique inputDescriptor ids at top level
* 5. Verifies the input descriptor mapping ids are the same on all levels of nesting.
* 6. Ensures that the path is valid across all levels of nesting
* @throws PexValidationException if the PresentationDefinitionV2 is not valid.
*/
public fun validate(presentationSubmission: PresentationSubmission) {
if (presentationSubmission.id.isEmpty()) {
throw PexValidationException("PresentationSubmission id must not be empty")
}

if (presentationSubmission.definitionId.isEmpty()) {
throw PexValidationException("PresentationSubmission definitionId must not be empty")
}

if (presentationSubmission.descriptorMap.isEmpty()) {
throw PexValidationException("PresentationSubmission descriptorMap should be a non-empty list")
}

// Check for unique inputDescriptor ids
val ids = presentationSubmission.descriptorMap.map { it.id }
if (ids.size != ids.toSet().size) {
throw PexValidationException("All descriptorMap top level ids must be unique")
}

validateDescriptorMap(presentationSubmission.descriptorMap)
}

private fun validateDescriptorMap(descriptorMap: List<InputDescriptorMapping>) {
descriptorMap.forEach { descriptor ->
validateDescriptor(descriptor, descriptor.id)
}
}
private fun validateDescriptor(descriptor: InputDescriptorMapping, id: String?) {
if (descriptor.id.isEmpty()) {
throw PexValidationException("Descriptor id should not be empty")
}

if (descriptor.path.isEmpty()) {
throw PexValidationException("Descriptor path should not be empty")
}

if (descriptor.format.isEmpty()) {
throw PexValidationException("Descriptor format should not be empty")
}

if (descriptor.id != id) {
throw PexValidationException("Each descriptor should have one id in it, on all levels")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also need to check that descriptor ids are unique within the definition

}

if (runCatching { JsonPath(descriptor.path) }.isFailure) {
throw PexValidationException("Each descriptor should have a valid path id")
}

descriptor.pathNested?.let { nestedDescriptor ->
validateDescriptor(nestedDescriptor, id)
}
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import web5.sdk.credentials.model.PresentationDefinitionV2
import web5.sdk.credentials.model.PresentationSubmission
import web5.sdk.crypto.InMemoryKeyManager
import web5.sdk.dids.methods.key.DidKey
import web5.sdk.testing.TestVectors
Expand Down Expand Up @@ -621,4 +622,27 @@ class Web5TestVectorsPresentationExchange {
}
}
}

data class ValidateSubmissionTestInput(
val presentationSubmission: PresentationSubmission,
val errors: Boolean
)
@Test
fun validate_submission() {
val typeRef = object : TypeReference<TestVectors<ValidateSubmissionTestInput, Unit>>() {}
val testVectors =
mapper.readValue(File("../web5-spec/test-vectors/presentation_exchange/validate_submission.json"), typeRef)

testVectors.vectors.filterNot { it.errors ?: false }.forEach { vector ->
assertDoesNotThrow {
PresentationExchange.validateSubmission(vector.input.presentationSubmission)
}
}

testVectors.vectors.filter { it.errors ?: false }.forEach { vector ->
assertFails {
PresentationExchange.validateSubmission(vector.input.presentationSubmission)
}
}
}
}
2 changes: 1 addition & 1 deletion web5-spec
Loading