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

feat: initial VP support #21

Merged
merged 66 commits into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from 64 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
a4bc4b7
Add support for Request by reference
nanderstabel Apr 14, 2023
35c0605
fix: remove custom serde
nanderstabel Apr 19, 2023
438f17f
Add claims and scope parameters
nanderstabel Apr 19, 2023
ee5fb3d
Add Storage and RelyingParty test improvement
nanderstabel Apr 19, 2023
70e3d57
Update README example
nanderstabel Apr 19, 2023
66bbf82
Move Storage trait to test_utils
nanderstabel Apr 19, 2023
82d39dc
Remove storage.rs
nanderstabel Apr 19, 2023
3094f9f
fix: fex rebase to dev
nanderstabel Apr 25, 2023
c3ae81a
fix: fix rebase to dev
nanderstabel Apr 25, 2023
96d6d08
feat: add Claim trait with associated types
nanderstabel May 12, 2023
5dde5a3
fix: build
nanderstabel May 23, 2023
1899763
fix: remove build.rs and change crate name in doc tests
nanderstabel May 24, 2023
3208367
feat: add missing ID Token claim parameters
nanderstabel May 30, 2023
48ff6a4
fix: remove skeptic crate
nanderstabel Apr 25, 2023
b556b11
feat: allow json arguments for claims() method
nanderstabel May 12, 2023
7a06c62
style: add specific request folder
nanderstabel May 26, 2023
10eaf01
test: improve RequestBuilder tests
nanderstabel May 31, 2023
260ffbf
Add support for Request by reference
nanderstabel Apr 14, 2023
98d003a
fix: remove custom serde
nanderstabel Apr 19, 2023
f55d4f9
Add claims and scope parameters
nanderstabel Apr 19, 2023
f9cf526
Add Storage and RelyingParty test improvement
nanderstabel Apr 19, 2023
4292149
Update README example
nanderstabel Apr 19, 2023
20708d8
Move Storage trait to test_utils
nanderstabel Apr 19, 2023
b9111b0
fix: fex rebase to dev
nanderstabel Apr 25, 2023
cb5cbc3
feat: add Claim trait with associated types
nanderstabel May 12, 2023
b9807a5
feat: improve request builder
nanderstabel May 31, 2023
922aeb2
fix: fix rebase
nanderstabel Jun 5, 2023
ba36618
refactor: rename Registration to ClientMetadata
nanderstabel Jun 6, 2023
3a19e01
feat: Add SubjectSyntaxType
nanderstabel Jun 6, 2023
d6c4eab
feat: add Subjects, Validators and Sign trait
nanderstabel Jun 7, 2023
d2cf2e3
feat: add switch subject functionality
nanderstabel Jun 8, 2023
ef36f48
refactor: simplify DidMethod, add methods
nanderstabel Jun 8, 2023
cf35509
feat: add find_validator
nanderstabel Jun 8, 2023
3545a3a
fix: fix rebase
nanderstabel Jun 11, 2023
aaff7b6
feat: add getters
nanderstabel Jun 11, 2023
0b1804d
feat: add getters
nanderstabel Jun 11, 2023
a5cebd7
refactor: migrate test to manager integration test
nanderstabel Jun 15, 2023
0cc3ba9
refactor: migrate provider methods to manager
nanderstabel Jun 15, 2023
b7fab0a
refactor: migrate key method to manager
nanderstabel Jun 15, 2023
0360953
feat: add managers
nanderstabel Jun 16, 2023
e159bd7
refactor: refactor managers
nanderstabel Jun 16, 2023
1ed3161
feat: decoder
nanderstabel Jun 19, 2023
4ae5895
fix: remove some TODO's
nanderstabel Jun 19, 2023
f9152ed
feat: Add iota method
nanderstabel Jun 19, 2023
f6a462e
Add support for Request by reference
nanderstabel Mar 31, 2023
38bcf0c
fix: fix rebase
nanderstabel May 4, 2023
45e6ce1
feat: add workspace
nanderstabel May 31, 2023
6803201
style: refactor
nanderstabel Jun 5, 2023
d8e4829
feat: improve monorepo structure
nanderstabel Jun 9, 2023
a1bbe17
refactor: move presentation-exchange to its crate
nanderstabel Jun 9, 2023
a537b21
fix: remove custom VerifiablePresentation
nanderstabel Jun 9, 2023
f4a2ff4
fix: fix rebase
nanderstabel Jun 19, 2023
eb744ae
fix: fix rebase
nanderstabel Jun 19, 2023
6aabd74
WIP
nanderstabel Jun 20, 2023
207907b
WIP
nanderstabel Jun 20, 2023
82f2180
feat: add integration test and PS creation
nanderstabel Jun 21, 2023
a2205ef
fix: uncomment response.rs unit tests
nanderstabel Jun 21, 2023
47218fb
fix: fix rebase
nanderstabel Jun 21, 2023
bfbdb79
fix: remove duplicate
nanderstabel Jun 21, 2023
1ca7a20
feat: add credential validation
nanderstabel Jun 22, 2023
b393732
feat: make Sign and Verify Send + Sync
nanderstabel Jun 23, 2023
ddd98b9
feat: impl formencoded for AuthorizationResponse
nanderstabel Jun 23, 2023
385d8f5
feat: migrate to oid4vc-core
nanderstabel Jun 28, 2023
f73ee3f
fix: thread error fix
nanderstabel Jun 29, 2023
1209fdd
fix: fix commented unit tests
nanderstabel Jun 30, 2023
2e287c2
fix: PR review feedback
nanderstabel Jul 3, 2023
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
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repository = "https://github.com/impierce/openid4vc"

[workspace]
members = [
"oid4vc-core",
"oid4vci",
"oid4vp",
"siopv2",
Expand All @@ -22,6 +23,14 @@ homepage = "https://www.impierce.com/"
license = "Apache-2.0"
repository = "https://github.com/impierce/openid4vc"

[workspace.dependencies]
tokio = { version = "1.26.0", features = ["rt", "macros", "rt-multi-thread"] }
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
serde_with = "3.0"
url = { version = "2.3", features = ["serde"] }
getset = "0.1"

# TODO: Fix these dependencies once publishing to crates.io is automated.
[dependencies]
oid4vci = { path = "oid4vci" }
Expand Down
9 changes: 9 additions & 0 deletions dif-presentation-exchange/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,12 @@ homepage.workspace = true
keywords = ["oid4vc", "dif", "presentation", "exchange", "OpenID"]
license.workspace = true
repository.workspace = true

[dependencies]
serde.workspace = true
serde_json.workspace = true
serde_with.workspace = true
url.workspace = true
getset.workspace = true
jsonpath_lib = "0.3"
jsonschema = "0.17"
320 changes: 320 additions & 0 deletions dif-presentation-exchange/src/input_evaluation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
use crate::InputDescriptor;
use jsonpath_lib as jsonpath;
use jsonschema::JSONSchema;

#[derive(Debug, Clone, PartialEq)]
pub enum FieldQueryResult {
Some { value: serde_json::Value, path: String },
None,
Invalid,
}

impl FieldQueryResult {
pub fn is_valid(&self) -> bool {
!self.is_invalid()
}

pub fn is_invalid(&self) -> bool {
*self == FieldQueryResult::Invalid
}
}

/// Input Evaluation as described in section [8. Input
/// Evaluation](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-evaluation) of the DIF
/// Presentation Exchange specification.
pub fn evaluate_input(input_descriptor: &InputDescriptor, value: &serde_json::Value) -> bool {
let selector = &mut jsonpath::selector(value);

input_descriptor
.constraints()
.fields()
.as_ref()
.map(|fields| {
let results: Vec<FieldQueryResult> = fields
.iter()
.map(|field| {
let filter = field
.filter()
.as_ref()
.map(JSONSchema::compile)
.transpose()
.ok()
.flatten();

// For each JSONPath expression in the `path` array (incrementing from the 0-index),
// evaluate the JSONPath expression against the candidate input and repeat the following
// subsequence on the result.
field
.path()
.iter()
// Repeat until a Field Query Result is found, or the path array elements are exhausted:
.find_map(|path| {
// If the result returned no JSONPath match, skip to the next path array element.
// Else, evaluate the first JSONPath match (candidate) as follows:
selector(path).ok().and_then(|values| {
values.into_iter().find_map(|result| {
// If the fields object has no `filter`, or if candidate validates against
// the JSON Schema descriptor specified in `filter`, then:
filter
.as_ref()
.map(|filter| filter.is_valid(result))
.unwrap_or(true)
// set Field Query Result to be candidate
.then(|| FieldQueryResult::Some {
value: result.to_owned(),
path: path.to_owned(),
})
// Else, skip to the next `path` array element.
})
})
})
// If no value is located for any of the specified `path` queries, and the fields
// object DOES NOT contain the `optional` property or it is set to `false`, reject the
// field as invalid. If no value is located for any of the specified `path` queries and
// the fields object DOES contain the `optional` property set to the value `true`,
// treat the field as valid and proceed to the next fields object.
.or_else(|| field.optional().and_then(|opt| opt.then(|| FieldQueryResult::None)))
.unwrap_or(FieldQueryResult::Invalid)
})
.collect();
results.iter().all(FieldQueryResult::is_valid)
})
.unwrap_or(false)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{
presentation_definition::{Constraints, Field},
InputDescriptor,
};
use serde::de::DeserializeOwned;
use std::{fs::File, path::Path};

fn json_example<T>(path: &str) -> T
where
T: DeserializeOwned,
{
let file_path = Path::new(path);
let file = File::open(file_path).expect("file does not exist");
serde_json::from_reader::<_, T>(file).expect("could not parse json")
}

fn input_descriptor(constraints: Constraints) -> InputDescriptor {
InputDescriptor {
id: "test_input_descriptor".to_string(),
name: None,
purpose: None,
format: None,
constraints,
schema: None,
}
}

#[test]
fn test_constraints() {
let credential = json_example::<serde_json::Value>("../oid4vp/tests/examples/credentials/jwt_vc.json");

// Has NO fields.
assert!(!evaluate_input(&input_descriptor(Constraints::default()), &credential));

// Has ONE VALID field.
assert!(evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![Field {
path: vec!["$.vc.type".to_string()],
..Default::default()
}]),
..Default::default()
}),
&credential
));

// // Has ONE INVALID field.
assert!(!evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![Field {
path: vec!["$.vc.foo".to_string()],
..Default::default()
}]),
..Default::default()
}),
&credential
));

// First field is INVALID.
assert!(!evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![
Field {
path: vec!["$.vc.foo".to_string()],
..Default::default()
},
Field {
path: vec!["$.vc.type".to_string()],
..Default::default()
},
]),
..Default::default()
}),
&credential
));

// Second field is INVALID.
assert!(!evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![
Field {
path: vec!["$.vc.type".to_string()],
..Default::default()
},
Field {
path: vec!["$.vc.foo".to_string()],
..Default::default()
},
]),
..Default::default()
}),
&credential
));

// Second field is INVALID but optional.
assert!(evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![
Field {
path: vec!["$.vc.type".to_string()],
..Default::default()
},
Field {
path: vec!["$.vc.foo".to_string()],
optional: Some(true),
..Default::default()
},
]),
..Default::default()
}),
&credential
));
}

#[test]
fn test_field() {
let credential = json_example::<serde_json::Value>("../oid4vp/tests/examples/credentials/jwt_vc.json");

// Has NO path.
assert!(!evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![Field::default()]),
..Default::default()
}),
&credential
));

// Has ONE path.
assert!(evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![Field {
path: vec!["$.vc.type".to_string()],
..Default::default()
}]),
..Default::default()
}),
&credential
));

// Has TWO paths. First is NO match, second is a match without filter.
assert!(evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![Field {
path: vec!["$.vc.foo".to_string(), "$.vc.type".to_string()],
..Default::default()
}]),
..Default::default()
}),
&credential
));

// Has TWO paths. First is a match, with filter.
assert!(evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![Field {
path: vec!["$.vc.type".to_string(), "$.vc.foo".to_string()],
filter: Some(serde_json::json!({
"type": "array",
"contains": {
"const": "IDCredential"
}
})),
..Default::default()
}]),
..Default::default()
}),
&credential
));

// Has ONE paths. With non-matching filter.
assert!(!evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![Field {
path: vec!["$.vc.type".to_string()],
filter: Some(serde_json::json!({
"type": "array",
"contains": {
"const": "Foo"
}
})),
..Default::default()
}]),
..Default::default()
}),
&credential
));

// Has ONE path. With non-matching filter. Is optional
assert!(evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![Field {
path: vec!["$.vc.type".to_string()],
filter: Some(serde_json::json!({
"type": "array",
"contains": {
"const": "Foo"
}
})),
optional: Some(true),
..Default::default()
}]),
..Default::default()
}),
&credential
));

// Has ONE path, which does not exist. Is optional
assert!(evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![Field {
path: vec!["$.vc.foo".to_string()],
optional: Some(true),
..Default::default()
}]),
..Default::default()
}),
&credential
));

// Has ONE path, which does not exist. Is NOT optional (explicitly).
assert!(!evaluate_input(
&input_descriptor(Constraints {
fields: Some(vec![Field {
path: vec!["$.vc.foo".to_string()],
optional: Some(false),
..Default::default()
}]),
..Default::default()
}),
&credential
));
}
}
7 changes: 7 additions & 0 deletions dif-presentation-exchange/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub mod input_evaluation;
pub mod presentation_definition;
pub mod presentation_submission;

pub use input_evaluation::evaluate_input;
pub use presentation_definition::{ClaimFormatDesignation, InputDescriptor, PresentationDefinition};
pub use presentation_submission::{InputDescriptorMappingObject, PathNested, PresentationSubmission};
Loading