diff --git a/agent_api_rest/postman/ssi-agent.postman_collection.json b/agent_api_rest/postman/ssi-agent.postman_collection.json index ef85d4cb..d3537f8f 100644 --- a/agent_api_rest/postman/ssi-agent.postman_collection.json +++ b/agent_api_rest/postman/ssi-agent.postman_collection.json @@ -33,7 +33,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"offerId\":\"{{OFFER_ID}}\",\n \"credential\": {\n \"credentialSubject\": {\n \"type\":[\n \"AchievementSubject\"\n ],\n \"achievement\":{\n \"id\":\"https://demo.edubadges.nl/public/assertions/6pEB--n-SwiZPtWXMCB2jQ\",\n \"name\":\"Edubadge account complete\",\n \"type\":[\n \"Achievement\"\n ],\n \"image\":{\n \"id\":\"https://api-demo.edubadges.nl/media/uploads/badges/issuer_badgeclass_548517aa-cbab-4a7b-a971-55cdcce0e2a5.png\"\n },\n \"criteria\":{\n \"narrative\":\"To qualify for this edubadge:\\r\\n\\r\\n* you successfully created an eduID,\\r\\n* you successfully linked your institution to your eduID,\\r\\n* you can store and manage them safely in your backpack.\"\n },\n \"description\":\"### Welcome to edubadges. Let your life long learning begin! ###\\r\\n\\r\\nYou are now ready to collect all your edubadges in your backpack. In your backpack you can store and manage them safely.\\r\\n\\r\\nShare them anytime you like and with whom you like.\\r\\n\\r\\nEdubadges are visual representations of your knowledge, skills and competences.\"\n }\n }\n }\n}", + "raw": "{\n \"offerId\":\"{{OFFER_ID}}\",\n \"credential\": {\n \"credentialSubject\": {\n \"first_name\": \"Ferris\",\n \"last_name\": \"Crabman\",\n \"dob\": \"1982-01-01\"\n }\n }\n}", "options": { "raw": { "language": "json" @@ -214,7 +214,107 @@ "name": "Verification", "item": [ { - "name": "authorization_requests", + "name": "siopv2_authorization_requests", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const location = pm.response.headers.get(\"LOCATION\");", + "", + "if(location){", + " pm.collectionVariables.set(\"AUTHORIZATION_REQUEST_LOCATION\",location)", + "}", + "", + "const authorization_request = responseBody;", + "", + "const decodedString = decodeURIComponent(authorization_request);", + "", + "// Regular expression to match the value of request_uri", + "const regex = /request_uri=([^&]+)/;", + "", + "// Executing the regular expression on the input string", + "const match = regex.exec(decodedString);", + "", + "// Extracting the value of request_uri", + "const requestUri = match ? decodeURIComponent(match[1]) : null;", + "", + "pm.collectionVariables.set(\"REQUEST_URI\",requestUri)", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"nonce\": \"this is a nonce\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "http://{{HOST}}/v1/authorization_requests" + }, + "response": [] + }, + { + "name": "oid4vp_authorization_requests_with_presentation_definition", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const location = pm.response.headers.get(\"LOCATION\");", + "", + "if(location){", + " pm.collectionVariables.set(\"AUTHORIZATION_REQUEST_LOCATION\",location)", + "}", + "", + "const authorization_request = responseBody;", + "", + "const decodedString = decodeURIComponent(authorization_request);", + "", + "// Regular expression to match the value of request_uri", + "const regex = /request_uri=([^&]+)/;", + "", + "// Executing the regular expression on the input string", + "const match = regex.exec(decodedString);", + "", + "// Extracting the value of request_uri", + "const requestUri = match ? decodeURIComponent(match[1]) : null;", + "", + "pm.collectionVariables.set(\"REQUEST_URI\",requestUri)", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"nonce\": \"this is a nonce\",\n \"presentation_definition\": {\n \"id\":\"Verifiable Presentation request for sign-on\",\n \"input_descriptors\":[\n {\n \"id\":\"Request for Verifiable Credential\",\n \"constraints\":{\n \"fields\":[\n {\n \"path\":[\n \"$.vc.type\"\n ],\n \"filter\":{\n \"type\":\"array\",\n \"contains\":{\n \"const\":\"VerifiableCredential\"\n }\n }\n }\n ]\n }\n }\n ]\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "http://{{HOST}}/v1/authorization_requests" + }, + "response": [] + }, + { + "name": "oid4vp_authorization_requests_with_presentation_definition_id", "event": [ { "listen": "test", diff --git a/agent_api_rest/src/verification/authorization_requests.rs b/agent_api_rest/src/verification/authorization_requests.rs index 7b1def66..12eee528 100644 --- a/agent_api_rest/src/verification/authorization_requests.rs +++ b/agent_api_rest/src/verification/authorization_requests.rs @@ -13,6 +13,7 @@ use axum::{ Json, }; use hyper::header; +use oid4vp::PresentationDefinition; use serde::{Deserialize, Serialize}; use serde_json::Value; use tracing::info; @@ -37,7 +38,15 @@ pub(crate) async fn get_authorization_requests( pub struct AuthorizationRequestsEndpointRequest { pub nonce: String, pub state: Option, - pub presentation_definition_id: Option, + #[serde(flatten)] + pub presentation_definition: Option, +} + +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum PresentationDefinitionResource { + PresentationDefinitionId(String), + PresentationDefinition(PresentationDefinition), } #[axum_macros::debug_handler] @@ -50,7 +59,7 @@ pub(crate) async fn authorization_requests( let Ok(AuthorizationRequestsEndpointRequest { nonce, state, - presentation_definition_id, + presentation_definition, }) = serde_json::from_value(payload) else { return (StatusCode::BAD_REQUEST, "invalid payload").into_response(); @@ -58,19 +67,24 @@ pub(crate) async fn authorization_requests( let state = state.unwrap_or(generate_random_string()); - // TODO: This needs to be properly fixed instead of reading the presentation definitions from the file system - // everytime a request is made. `PresentationDefinition`'s should be implemented as a proper `Aggregate`. This - // current suboptimal solution requires the `./tmp:/app/agent_api_rest` volume to be mounted in the `docker-compose.yml`. - let presentation_definition = presentation_definition_id.map(|presentation_definition_id| { - let project_root_dir = env!("CARGO_MANIFEST_DIR"); - - serde_json::from_reader( - std::fs::File::open(format!( - "{project_root_dir}/../agent_verification/presentation_definitions/{presentation_definition_id}.json" - )) - .unwrap(), - ) - .unwrap() + let presentation_definition = presentation_definition.map(|presentation_definition| { + match presentation_definition { + // TODO: This needs to be properly fixed instead of reading the presentation definitions from the file system + // everytime a request is made. `PresentationDefinition`'s should be implemented as a proper `Aggregate`. This + // current suboptimal solution requires the `./tmp:/app/agent_api_rest` volume to be mounted in the `docker-compose.yml`. + PresentationDefinitionResource::PresentationDefinitionId(presentation_definition_id) => { + let project_root_dir = env!("CARGO_MANIFEST_DIR"); + + serde_json::from_reader( + std::fs::File::open(format!( + "{project_root_dir}/../agent_verification/presentation_definitions/{presentation_definition_id}.json" + )) + .unwrap(), + ) + .unwrap() + } + PresentationDefinitionResource::PresentationDefinition(presentation_definition) => presentation_definition, + } }); let command = AuthorizationRequestCommand::CreateAuthorizationRequest { @@ -129,23 +143,32 @@ pub mod tests { http::{self, Request}, Router, }; - use serde_json::json; + use rstest::rstest; use tower::Service; - pub async fn authorization_requests(app: &mut Router) -> String { + pub async fn authorization_requests(app: &mut Router, by_value: bool) -> String { + let request_body = AuthorizationRequestsEndpointRequest { + nonce: "nonce".to_string(), + state: None, + presentation_definition: Some(if by_value { + PresentationDefinitionResource::PresentationDefinition( + serde_json::from_str(include_str!( + "../../../agent_verification/presentation_definitions/presentation_definition.json" + )) + .unwrap(), + ) + } else { + PresentationDefinitionResource::PresentationDefinitionId("presentation_definition".to_string()) + }), + }; + let response = app .call( Request::builder() .method(http::Method::POST) .uri("/v1/authorization_requests") .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .body(Body::from( - serde_json::to_vec(&json!({ - "nonce": "nonce", - "presentation_definition_id": "presentation_definition" - })) - .unwrap(), - )) + .body(Body::from(serde_json::to_vec(&request_body).unwrap())) .unwrap(), ) .await @@ -188,9 +211,12 @@ pub mod tests { form_url_encoded_authorization_request } + #[rstest] + #[case::with_presentation_definition_by_value(true)] + #[case::with_presentation_definition_id(false)] #[tokio::test] #[tracing_test::traced_test] - async fn test_authorization_requests_endpoint() { + async fn test_authorization_requests_endpoint(#[case] by_value: bool) { let issuance_state = in_memory::issuance_state(Default::default()).await; let verification_state = in_memory::verification_state( test_verification_services(&config!("default_did_method").unwrap_or("did:key".to_string())), @@ -199,6 +225,6 @@ pub mod tests { .await; let mut app = app((issuance_state, verification_state)); - authorization_requests(&mut app).await; + authorization_requests(&mut app, by_value).await; } } diff --git a/agent_api_rest/src/verification/relying_party/redirect.rs b/agent_api_rest/src/verification/relying_party/redirect.rs index 17e17208..488d496e 100644 --- a/agent_api_rest/src/verification/relying_party/redirect.rs +++ b/agent_api_rest/src/verification/relying_party/redirect.rs @@ -175,7 +175,7 @@ pub mod tests { let mut app = app((issuance_state, verification_state)); - let form_url_encoded_authorization_request = authorization_requests(&mut app).await; + let form_url_encoded_authorization_request = authorization_requests(&mut app, false).await; // Extract the state from the form_url_encoded_authorization_request. let state = form_url_encoded_authorization_request diff --git a/agent_api_rest/src/verification/relying_party/request.rs b/agent_api_rest/src/verification/relying_party/request.rs index 664a935b..619f7194 100644 --- a/agent_api_rest/src/verification/relying_party/request.rs +++ b/agent_api_rest/src/verification/relying_party/request.rs @@ -78,7 +78,7 @@ pub mod tests { .await; let mut app = app((issuance_state, verification_state)); - let form_url_encoded_authorization_request = authorization_requests(&mut app).await; + let form_url_encoded_authorization_request = authorization_requests(&mut app, false).await; // Extract the state from the form_url_encoded_authorization_request. let state = form_url_encoded_authorization_request