From 1227052b4958b3dd12d902b91c8b9a4227238a2c Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Fri, 18 Mar 2022 16:11:22 +0100 Subject: [PATCH 1/3] initial works Signed-off-by: Berend Sliedrecht --- Cargo.lock | 9 +++++++++ Cargo.toml | 1 + workflow/Cargo.toml | 11 +++++++++++ workflow/src/lib.rs | 1 + workflow/src/workflows/credential_offer.rs | 18 ++++++++++++++++++ workflow/src/workflows/mod.rs | 2 ++ workflow/src/workflows/workflow.rs | 14 ++++++++++++++ 7 files changed, 56 insertions(+) create mode 100644 workflow/Cargo.toml create mode 100644 workflow/src/lib.rs create mode 100644 workflow/src/workflows/credential_offer.rs create mode 100644 workflow/src/workflows/mod.rs create mode 100644 workflow/src/workflows/workflow.rs diff --git a/Cargo.lock b/Cargo.lock index 2c7eaba6..18ec2ba2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1413,6 +1413,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "workflow" +version = "0.1.0" +dependencies = [ + "agent", + "async-trait", + "cloudagent-python", +] + [[package]] name = "x11-clipboard" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index e6cdde35..ea7e08eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ members = [ "cloudagent-python", "agent", "cli", + "workflow" ] diff --git a/workflow/Cargo.toml b/workflow/Cargo.toml new file mode 100644 index 00000000..8c5a9a1b --- /dev/null +++ b/workflow/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "workflow" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "0.1.51" +cloudagent-python = {path = "../cloudagent-python"} +agent = {path = "../agent"} diff --git a/workflow/src/lib.rs b/workflow/src/lib.rs new file mode 100644 index 00000000..ef3e8b00 --- /dev/null +++ b/workflow/src/lib.rs @@ -0,0 +1 @@ +mod workflows; diff --git a/workflow/src/workflows/credential_offer.rs b/workflow/src/workflows/credential_offer.rs new file mode 100644 index 00000000..658d1f16 --- /dev/null +++ b/workflow/src/workflows/credential_offer.rs @@ -0,0 +1,18 @@ +use std::collections::HashMap; + +use super::workflow::Workflow; +use agent::modules::{connections::ConnectionModule, credentials::CredentialsModule}; +use async_trait::async_trait; + +struct CredentialOfferWorkflow { + connection_id: String, + credential_deifnition_id: String, + attributes: HashMap, +} + +#[async_trait] +impl Workflow for CredentialOfferWorkflow { + async fn execute(&self, agent: impl ConnectionModule + CredentialsModule) -> Result<(), ()> { + todo!() + } +} diff --git a/workflow/src/workflows/mod.rs b/workflow/src/workflows/mod.rs new file mode 100644 index 00000000..0a5e7052 --- /dev/null +++ b/workflow/src/workflows/mod.rs @@ -0,0 +1,2 @@ +pub mod credential_offer; +pub mod workflow; diff --git a/workflow/src/workflows/workflow.rs b/workflow/src/workflows/workflow.rs new file mode 100644 index 00000000..1111c7c1 --- /dev/null +++ b/workflow/src/workflows/workflow.rs @@ -0,0 +1,14 @@ +use agent::modules::{ + connections::ConnectionModule, credential_definition::CredentialDefinitionModule, + credentials::CredentialsModule, +}; +use async_trait::async_trait; + +#[async_trait] +pub trait Workflow { + // TODO: fix return type + async fn execute( + &self, + agent: impl ConnectionModule + CredentialsModule + CredentialDefinitionModule, + ) -> Result<(), ()>; +} From 21ee3ee512405e923643c54886a4245d24ad18a6 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Mon, 21 Mar 2022 14:58:57 +0100 Subject: [PATCH 2/3] feat: credential offer functionality Signed-off-by: Berend Sliedrecht --- agent/src/modules/connections.rs | 2 +- cli/src/modules/features.rs | 4 +- workflow/src/error.rs | 18 +++++++ workflow/src/lib.rs | 1 + workflow/src/workflows/credential_offer.rs | 55 ++++++++++++++++++---- workflow/src/workflows/mod.rs | 1 - workflow/src/workflows/workflow.rs | 14 ------ 7 files changed, 68 insertions(+), 27 deletions(-) create mode 100644 workflow/src/error.rs delete mode 100644 workflow/src/workflows/workflow.rs diff --git a/agent/src/modules/connections.rs b/agent/src/modules/connections.rs index 75b5c508..bacc3b65 100644 --- a/agent/src/modules/connections.rs +++ b/agent/src/modules/connections.rs @@ -71,7 +71,7 @@ pub trait ConnectionModule { ) -> Result; } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct ConnectionCreateInvitationOptions { pub auto_accept: bool, pub qr: bool, diff --git a/cli/src/modules/features.rs b/cli/src/modules/features.rs index 2cd0daba..6ceb4f8a 100644 --- a/cli/src/modules/features.rs +++ b/cli/src/modules/features.rs @@ -1,6 +1,6 @@ use agent::modules::features::FeaturesModule; use clap::Args; -use log::{debug, info}; +use log::debug; use crate::error::Result; use crate::help_strings::HelpStrings; @@ -19,7 +19,7 @@ pub async fn parse_features_args(agent: impl FeaturesModule) -> Result<()> { loader.stop(); debug!("{}", pretty_stringify_obj(&features)); features.disclose.protocols.iter().for_each(|p| { - info!("{}", p.pid); + println!("{}", p.pid); }); }) } diff --git a/workflow/src/error.rs b/workflow/src/error.rs new file mode 100644 index 00000000..1e633a99 --- /dev/null +++ b/workflow/src/error.rs @@ -0,0 +1,18 @@ +use std::fmt::{Display, Formatter}; + +#[derive(Debug)] +pub enum Error { + UnknownError, +} + +impl std::error::Error for Error {} + +pub type Result = std::result::Result>; + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Error::UnknownError => write!(f, "An unknown error occurred"), + } + } +} diff --git a/workflow/src/lib.rs b/workflow/src/lib.rs index ef3e8b00..3caf50c2 100644 --- a/workflow/src/lib.rs +++ b/workflow/src/lib.rs @@ -1 +1,2 @@ +mod error; mod workflows; diff --git a/workflow/src/workflows/credential_offer.rs b/workflow/src/workflows/credential_offer.rs index 658d1f16..ba650396 100644 --- a/workflow/src/workflows/credential_offer.rs +++ b/workflow/src/workflows/credential_offer.rs @@ -1,18 +1,55 @@ +use crate::error::Result; +use agent::modules::{ + connections::ConnectionModule, + credential_definition::CredentialDefinitionModule, + credentials::{CredentialsModule, CredentialsOfferOptions}, + schema::{SchemaCreateOptions, SchemaModule}, +}; use std::collections::HashMap; -use super::workflow::Workflow; -use agent::modules::{connections::ConnectionModule, credentials::CredentialsModule}; -use async_trait::async_trait; - -struct CredentialOfferWorkflow { +/// Credential offer workflow which offers an prebuilt credential to a connection +pub struct CredentialOfferWorkflow { connection_id: String, credential_deifnition_id: String, attributes: HashMap, } -#[async_trait] -impl Workflow for CredentialOfferWorkflow { - async fn execute(&self, agent: impl ConnectionModule + CredentialsModule) -> Result<(), ()> { - todo!() +/// All the modules an agent needs to imlement in order to execute the workflow +pub trait Agent: + ConnectionModule + CredentialsModule + SchemaModule + CredentialDefinitionModule +{ +} + +impl CredentialOfferWorkflow { + pub async fn execute(&self, agent: &dyn Agent) -> Result<()> { + let attribute_keys: Vec = self.attributes.keys().map(|e| e.to_owned()).collect(); + let attribute_values: Vec = + self.attributes.values().map(|e| e.to_owned()).collect(); + + // Create or fetch the schema + let schema = SchemaModule::create( + agent, + SchemaCreateOptions { + name: String::from("credential-offer-workflow"), + attributes: attribute_keys.to_owned(), + version: String::from("1.0"), + }, + ) + .await?; + + // Create or fetch the credential definition + let credential_definition = + CredentialDefinitionModule::create(agent, schema.schema_id).await?; + + let _ = agent + .send_offer(CredentialsOfferOptions { + keys: attribute_keys, + values: attribute_values, + connection_id: self.connection_id.to_owned(), + cred_def_id: credential_definition.credential_definition_id, + }) + .await?; + + Ok(()) } } diff --git a/workflow/src/workflows/mod.rs b/workflow/src/workflows/mod.rs index 0a5e7052..97565d85 100644 --- a/workflow/src/workflows/mod.rs +++ b/workflow/src/workflows/mod.rs @@ -1,2 +1 @@ pub mod credential_offer; -pub mod workflow; diff --git a/workflow/src/workflows/workflow.rs b/workflow/src/workflows/workflow.rs deleted file mode 100644 index 1111c7c1..00000000 --- a/workflow/src/workflows/workflow.rs +++ /dev/null @@ -1,14 +0,0 @@ -use agent::modules::{ - connections::ConnectionModule, credential_definition::CredentialDefinitionModule, - credentials::CredentialsModule, -}; -use async_trait::async_trait; - -#[async_trait] -pub trait Workflow { - // TODO: fix return type - async fn execute( - &self, - agent: impl ConnectionModule + CredentialsModule + CredentialDefinitionModule, - ) -> Result<(), ()>; -} From 7a52efc126922a678715bc3bbcea0bcec5e54f0d Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Mon, 21 Mar 2022 16:20:06 +0100 Subject: [PATCH 3/3] fix: improved credential Signed-off-by: Berend Sliedrecht --- Cargo.lock | 2 + cli/Cargo.toml | 1 + cli/src/cli.rs | 2 + cli/src/modules/connections.rs | 4 +- cli/src/modules/mod.rs | 1 + cli/src/modules/workflow.rs | 60 ++++++++++++++++++++++ cli/src/register.rs | 9 +++- cli/src/utils/logger.rs | 1 + workflow/Cargo.toml | 3 +- workflow/src/error.rs | 4 +- workflow/src/lib.rs | 2 +- workflow/src/workflows/credential_offer.rs | 43 ++++++++++------ 12 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 cli/src/modules/workflow.rs diff --git a/Cargo.lock b/Cargo.lock index 18ec2ba2..4019635a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,6 +147,7 @@ dependencies = [ "serde_yaml", "simplelog", "tokio", + "workflow", ] [[package]] @@ -1420,6 +1421,7 @@ dependencies = [ "agent", "async-trait", "cloudagent-python", + "log", ] [[package]] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index dcb8c9d0..10ebf8a5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -18,6 +18,7 @@ path = "src/main.rs" [dependencies] cloudagent-python = {path = "../cloudagent-python"} agent = {path = "../agent"} +workflow = {path = "../workflow"} base64 = "0.13.0" clap = {version = "3.1.0", features = ["derive"]} clipboard = "0.5.0" diff --git a/cli/src/cli.rs b/cli/src/cli.rs index abb46232..2fc8e214 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -4,6 +4,7 @@ use clap::{Parser, Subcommand}; use crate::help_strings::HelpStrings; +use crate::modules::workflow::WorkflowOptions; use crate::modules::{ configuration::ConfigurationOptions, connections::ConnectionOptions, credential_definition::CredentialDefinitionOptions, credentials::CredentialOptions, @@ -48,4 +49,5 @@ pub enum Commands { Message(MessageOptions), Credentials(CredentialOptions), Configuration(ConfigurationOptions), + Workflow(WorkflowOptions), } diff --git a/cli/src/modules/connections.rs b/cli/src/modules/connections.rs index 6a9386f9..955312e7 100644 --- a/cli/src/modules/connections.rs +++ b/cli/src/modules/connections.rs @@ -7,8 +7,8 @@ use log::{debug, info}; use std::str; use crate::copy; -use crate::help_strings::HelpStrings; use crate::error::{Error, Result}; +use crate::help_strings::HelpStrings; use crate::utils::logger::pretty_stringify_obj; use crate::utils::{ loader::{Loader, LoaderVariant}, @@ -107,7 +107,7 @@ pub async fn parse_connection_args( let query_parameters = split_url .get(1) .ok_or(Error::InvalidAgentInvitation)? - .split("&") + .split('&') .map(|u| u.to_owned()) .collect::>(); diff --git a/cli/src/modules/mod.rs b/cli/src/modules/mod.rs index de9c75d2..14415656 100644 --- a/cli/src/modules/mod.rs +++ b/cli/src/modules/mod.rs @@ -5,3 +5,4 @@ pub mod credentials; pub mod features; pub mod message; pub mod schema; +pub mod workflow; diff --git a/cli/src/modules/workflow.rs b/cli/src/modules/workflow.rs new file mode 100644 index 00000000..6642622b --- /dev/null +++ b/cli/src/modules/workflow.rs @@ -0,0 +1,60 @@ +use agent::modules::connections::ConnectionModule; +use agent::modules::credential_definition::CredentialDefinitionModule; +use agent::modules::credentials::CredentialsModule; +use agent::modules::schema::SchemaModule; +use clap::{Args, Subcommand}; +use colored::*; +use std::collections::HashMap; +use workflow::workflows::credential_offer::CredentialOfferWorkflow; + +use crate::error::Result; +use crate::utils::loader::{Loader, LoaderVariant}; + +#[derive(Args)] +pub struct WorkflowOptions { + #[clap(subcommand)] + pub commands: WorkflowSubcommands, +} + +#[derive(Subcommand, Debug)] +pub enum WorkflowSubcommands { + CredentialOffer { + #[clap(long, short)] + connection_id: String, + }, +} + +pub async fn parse_workflow_args( + options: &WorkflowOptions, + agent: impl ConnectionModule + CredentialsModule + SchemaModule + CredentialDefinitionModule, +) -> Result<()> { + let loader = Loader::start(LoaderVariant::default()); + + match &options.commands { + WorkflowSubcommands::CredentialOffer { connection_id } => { + // Mock credential + let mut attributes: HashMap = HashMap::new(); + attributes.insert(String::from("Name"), String::from("Joyce Brown")); + attributes.insert(String::from("Date Of Birth"), String::from("19890321")); + attributes.insert(String::from("Street"), String::from("Main Road 207")); + attributes.insert(String::from("City"), String::from("New York")); + attributes.insert(String::from("Bank"), String::from("qBank New York")); + attributes.insert( + String::from("Card Number"), + String::from("4537-6696-0666-0146"), + ); + attributes.insert(String::from("Security Code"), String::from("063")); + attributes.insert(String::from("Valid Until"), String::from("20251212")); + + let options = CredentialOfferWorkflow { + connection_id: connection_id.to_string(), + attributes, + }; + + options.execute(agent).await?; + println!("{} executed workflow", "Successfully".green()); + loader.stop(); + Ok(()) + } + } +} diff --git a/cli/src/register.rs b/cli/src/register.rs index 34c79df0..e6ede921 100644 --- a/cli/src/register.rs +++ b/cli/src/register.rs @@ -11,6 +11,7 @@ use crate::modules::configuration::parse_configuration_args; use crate::modules::credential_definition::parse_credential_definition_args; use crate::modules::credentials::parse_credentials_args; use crate::modules::message::parse_message_args; +use crate::modules::workflow::parse_workflow_args; use crate::modules::{ connections::parse_connection_args, features::parse_features_args, schema::parse_schema_args, }; @@ -28,7 +29,7 @@ pub async fn register() -> Result<()> { // prints for all of the above and trace! 2 => LevelFilter::Trace, // TODO: what does this print for? - 2.. => LevelFilter::max(), + 3.. => LevelFilter::max(), // TODO: We might want to log an error with the verbosity levels available _ => LevelFilter::Info, } @@ -62,7 +63,6 @@ pub async fn register() -> Result<()> { Commands::Connections(options) => { let agent = initialize_agent_from_cli(cli.config, cli.environment, cli.agent_url, cli.api_key)?; - // TODO: refactor cli.copy parse_connection_args(options, agent).await } Commands::Credentials(options) => { @@ -70,6 +70,11 @@ pub async fn register() -> Result<()> { initialize_agent_from_cli(cli.config, cli.environment, cli.agent_url, cli.api_key)?; parse_credentials_args(&options.commands, agent).await } + Commands::Workflow(options) => { + let agent = + initialize_agent_from_cli(cli.config, cli.environment, cli.agent_url, cli.api_key)?; + parse_workflow_args(options, agent).await + } }?; debug!("{} executed command", "Successfully".green()); diff --git a/cli/src/utils/logger.rs b/cli/src/utils/logger.rs index eda4c199..8c0d07ca 100644 --- a/cli/src/utils/logger.rs +++ b/cli/src/utils/logger.rs @@ -31,6 +31,7 @@ pub fn init(level: LevelFilter, should_copy: bool) { .set_max_level(LevelFilter::Debug) .add_filter_allow(String::from("aries_cli")) .add_filter_allow(String::from("cloudagent_")) + .add_filter_allow(String::from("workflow")) .build(), TerminalMode::default(), ColorChoice::Never, diff --git a/workflow/Cargo.toml b/workflow/Cargo.toml index 8c5a9a1b..94fa9f32 100644 --- a/workflow/Cargo.toml +++ b/workflow/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = "0.1.51" cloudagent-python = {path = "../cloudagent-python"} agent = {path = "../agent"} +async-trait = "0.1.51" +log = "0.4.14" diff --git a/workflow/src/error.rs b/workflow/src/error.rs index 1e633a99..37ebedca 100644 --- a/workflow/src/error.rs +++ b/workflow/src/error.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter}; #[derive(Debug)] pub enum Error { - UnknownError, + ConnectionNotReady, } impl std::error::Error for Error {} @@ -12,7 +12,7 @@ pub type Result = std::result::Result>; impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Error::UnknownError => write!(f, "An unknown error occurred"), + Error::ConnectionNotReady => write!(f, "Connection is not in state active"), } } } diff --git a/workflow/src/lib.rs b/workflow/src/lib.rs index 3caf50c2..c1e45cd1 100644 --- a/workflow/src/lib.rs +++ b/workflow/src/lib.rs @@ -1,2 +1,2 @@ mod error; -mod workflows; +pub mod workflows; diff --git a/workflow/src/workflows/credential_offer.rs b/workflow/src/workflows/credential_offer.rs index ba650396..dc93c0fe 100644 --- a/workflow/src/workflows/credential_offer.rs +++ b/workflow/src/workflows/credential_offer.rs @@ -1,47 +1,58 @@ -use crate::error::Result; +use crate::error::{Error, Result}; use agent::modules::{ connections::ConnectionModule, credential_definition::CredentialDefinitionModule, credentials::{CredentialsModule, CredentialsOfferOptions}, schema::{SchemaCreateOptions, SchemaModule}, }; +use log::trace; use std::collections::HashMap; /// Credential offer workflow which offers an prebuilt credential to a connection pub struct CredentialOfferWorkflow { - connection_id: String, - credential_deifnition_id: String, - attributes: HashMap, -} - -/// All the modules an agent needs to imlement in order to execute the workflow -pub trait Agent: - ConnectionModule + CredentialsModule + SchemaModule + CredentialDefinitionModule -{ + pub connection_id: String, + pub attributes: HashMap, } impl CredentialOfferWorkflow { - pub async fn execute(&self, agent: &dyn Agent) -> Result<()> { + pub async fn execute( + &self, + agent: impl ConnectionModule + CredentialsModule + SchemaModule + CredentialDefinitionModule, + ) -> Result<()> { + trace!("Starting workflow CredentialOfferWorkflow"); + trace!("{}", self.connection_id); + trace!("{:#?}", self.attributes); + let attribute_keys: Vec = self.attributes.keys().map(|e| e.to_owned()).collect(); let attribute_values: Vec = self.attributes.values().map(|e| e.to_owned()).collect(); + // Check if it as a valid connection + println!("Fetching the connection..."); + let connection = ConnectionModule::get_by_id(&agent, self.connection_id.to_owned()).await?; + if connection.state != "active" { + return Err(Error::ConnectionNotReady.into()); + } + // Create or fetch the schema + println!("Registering the schema..."); let schema = SchemaModule::create( - agent, + &agent, SchemaCreateOptions { - name: String::from("credential-offer-workflow"), + name: String::from("full-credential-offer-workflow"), attributes: attribute_keys.to_owned(), version: String::from("1.0"), }, ) .await?; + println!("Registering the credential definition..."); // Create or fetch the credential definition let credential_definition = - CredentialDefinitionModule::create(agent, schema.schema_id).await?; + CredentialDefinitionModule::create(&agent, schema.schema_id).await?; - let _ = agent + println!("Offering the credential..."); + let credential_offer_response = agent .send_offer(CredentialsOfferOptions { keys: attribute_keys, values: attribute_values, @@ -50,6 +61,8 @@ impl CredentialOfferWorkflow { }) .await?; + trace!("Workflow completed and offered a credential"); + trace!("{:#?}", credential_offer_response); Ok(()) } }