Skip to content

Commit

Permalink
debug: credential selection
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Tate <[email protected]>
  • Loading branch information
Ryanmtate committed Oct 4, 2024
1 parent f0e9caa commit 461c51b
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 69 deletions.
79 changes: 26 additions & 53 deletions src/oid4vp/credential_callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,24 +145,6 @@ impl PermissionRequest {
self.credentials.clone()
}

/// Select the credential to satisfy the requested fields.
///
/// Returns a [PermissionResponse] object with the selected credential.
pub fn select_credential(
&self,
credential_id: Uuid,
) -> Result<Arc<PermissionResponse>, CredentialCallbackError> {
let credential = self
.credentials
.iter()
.find(|c| c.id() == credential_id)
.ok_or(CredentialCallbackError::CredentialNotFound(
credential_id.to_string(),
))?;

Ok(PermissionResponse::new(credential.clone()))
}

/// Return the requested fields for a given credential id.
pub fn requested_fields(&self) -> Vec<Arc<RequestedField>> {
self.definition
Expand Down Expand Up @@ -199,49 +181,40 @@ impl PermissionRequest {
///
/// The Requested Fields are created by calling the [PermissionRequest::requested_fields] method, and then
/// explicitly setting the permission to true or false, based on the holder's decision.
#[derive(Debug, Clone, uniffi::Object)]
#[derive(Debug, Clone, uniffi::Record)]
pub struct PermissionResponse {
selected_credential: Arc<ParsedCredential>,
}

#[uniffi::export]
impl PermissionResponse {
/// Method to create a new [PermissionResponse] object.
#[uniffi::constructor]
pub fn new(selected_credential: Arc<ParsedCredential>) -> Arc<Self> {
Arc::new(Self {
selected_credential,
})
}

/// Return the selected credential.
pub fn selected_credential(&self) -> Arc<ParsedCredential> {
self.selected_credential.clone()
}
pub selected_credential_id: Uuid,
}

/// This is a callback interface for credential operations, defined by the native code.
/// The Credential Callback Interface is used to permit the presentation of a credential.
///
/// For example, this is used to provide methods for the client to select a credential to present,
/// retrieved from the wallet.
/// This method is defined in the native code and is called by the Rust code to
/// present the credential to the user interface.
#[uniffi::export(with_foreign)]
pub trait CredentialCallbackInterface: Send + Sync + Debug {
/// Permit the verifier to request the information defined in the presentation definition.
#[async_trait::async_trait]
pub trait CredentialCallback: Send + Sync + Debug {
/// by implementing this trait in the native code, the Rust code will call this method,
/// passing in the `PermissionRequest` object and awaiting on the `PermissionResponse`.
///
/// This method is called during the presentation request, passing the required fields
/// from the presentation definition. The verifier should return a boolean indicating whether
/// the verifier can present the requested information.
/// The native code will respond with a Result signalling it has received the permission
/// request for handling.
///
/// A [PresentationRequest] is passed to the native code to allow the user to identify
/// which fields are being requested, and whether those fields will be retained.
/// The permission request includes the credentials used for selection.
///
/// The presentation request DOES NOT submit credentials or user data, but rather is used by the
/// user interface of the holder to determine whether the user will permit the verifier to
/// receive the requested the information.
///
/// If the user denies a required field request, the verifier should return a [CredentialCallbackError::PermissionDenied]
fn permit_presentation(
/// Otherwise, it will return an error.
async fn permission_request(
&self,
request: Arc<PermissionRequest>,
) -> Result<Arc<PermissionResponse>, CredentialCallbackError>;
) -> Result<(), CredentialCallbackError>;

/// Returns the ID of the credential to use as the selected credential.
///
/// The PermissionResponseCallback trait is used to handle the response to a permission request.
///
/// This trait is implemented in the native code and is called by the Rust code to handle the
/// response to the permission request.
///
/// The rust code calls the callback after receiving the permission request and
/// awaits the response from the native code.
async fn permission_response(&self) -> Result<PermissionResponse, CredentialCallbackError>;
}
47 changes: 31 additions & 16 deletions src/oid4vp/holder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ impl Holder {
pub async fn handle_oid4vp_request(
&self,
url: Url,
callback: &Arc<dyn CredentialCallbackInterface>,
// callback to return the credentials to the native code
callback: &Arc<dyn CredentialCallback>,
) -> Result<Option<Url>, OID4VPError> {
let request = self
.validate_request(url)
Expand Down Expand Up @@ -187,7 +188,7 @@ impl Holder {
// credentials returned from the VDC collection.
fn create_descriptor_map(
&self,
credential: Arc<ParsedCredential>,
credential: &Arc<ParsedCredential>,
input_descriptor_id: String,
) -> Result<DescriptorMap, OID4VPError> {
Ok(DescriptorMap::new(
Expand All @@ -207,7 +208,7 @@ impl Holder {
async fn handle_unencoded_authorization_request(
&self,
request: &AuthorizationRequestObject,
callback: &Arc<dyn CredentialCallbackInterface>,
callback: &Arc<dyn CredentialCallback>,
) -> Result<AuthorizationResponse, OID4VPError> {
// Resolve the presentation definition.
let presentation_definition = request
Expand All @@ -225,13 +226,30 @@ impl Holder {

// Permission response contains the `selected_credential` that matches
// the user's selection from the presentation request.
let permission_response = callback
.permit_presentation(PermissionRequest::new(

callback
.permission_request(PermissionRequest::new(
presentation_definition.clone(),
credentials,
credentials.clone(),
))
.await
.map_err(|e| OID4VPError::CredentialCallback(e.to_string()))?;

// Await the permission response callback.
let PermissionResponse {
selected_credential_id,
} = callback
.permission_response()
.await
.map_err(|e| OID4VPError::CredentialCallback(e.to_string()))?;

let selected_credential = credentials
.iter()
.find(|cred| cred.id() == selected_credential_id)
.ok_or(OID4VPError::CredentialCallback(
"credential not found".into(),
))?;

// // Create a descriptor map for the presentation submission based on the credentials
// // returned from the selection response.
let Some(input_descriptor_id) = presentation_definition
Expand All @@ -248,10 +266,8 @@ impl Holder {
return Err(OID4VPError::InputDescriptorNotFound);
};

let descriptor_map = self.create_descriptor_map(
permission_response.selected_credential(),
input_descriptor_id,
)?;
let descriptor_map =
self.create_descriptor_map(selected_credential, input_descriptor_id)?;

// // Create a presentation submission.
let presentation_submission = PresentationSubmission::new(
Expand All @@ -265,10 +281,7 @@ impl Holder {
.map_err(|e: anyhow::Error| OID4VPError::PresentationSubmissionCreation(e.to_string()))?;

let vp_token = self
.create_unencoded_verifiable_presentation(
request,
permission_response.selected_credential(),
)
.create_unencoded_verifiable_presentation(request, selected_credential)
.await?;

// Create a verifiable presentation object.
Expand All @@ -281,7 +294,7 @@ impl Holder {
async fn create_unencoded_verifiable_presentation(
&self,
request: &AuthorizationRequestObject,
credential: Arc<ParsedCredential>,
credential: &Arc<ParsedCredential>,
) -> Result<VpToken, OID4VPError> {
match request.client_id_scheme() {
ClientIdScheme::Did => self.verifiable_presentation_did(request, credential).await,
Expand All @@ -294,10 +307,12 @@ impl Holder {
async fn verifiable_presentation_did(
&self,
_request: &AuthorizationRequestObject,
credential: Arc<ParsedCredential>,
credential: &Arc<ParsedCredential>,
) -> Result<VpToken, OID4VPError> {
match &credential.inner {
ParsedCredentialInner::SdJwt(sd_jwt) => {
// TODO: How does the client id get passed into the VP?
// Is this looked up from the JWT?
Ok(VpToken::Single(sd_jwt.inner.as_bytes().into()))
}
// TODO: Implement support for JWT VC.
Expand Down

0 comments on commit 461c51b

Please sign in to comment.