Skip to content

Commit

Permalink
Merge pull request #511 from str4d/494-fix-plugin-helper
Browse files Browse the repository at this point in the history
age-plugin: Fix `run_state_machine` to enable recipient-only or identity-only plugins
  • Loading branch information
str4d authored Aug 5, 2024
2 parents a510e76 + 2eec457 commit d2c2e89
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 37 deletions.
10 changes: 10 additions & 0 deletions age-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ and this project adheres to Rust's notion of
to 1.0.0 are beta releases.

## [Unreleased]
### Added
- `age_plugin::PluginHandler`
- `impl age_plugin::identity::IdentityPluginV1 for std::convert::Infallible`
- `impl age_plugin::recipient::RecipientPluginV1 for std::convert::Infallible`

### Fixed
- `age_plugin::run_state_machine` now takes an `impl age_plugin::PluginHandler`
argument, instead of its previous arguments.
- This fixes the change from the previous release, because the type parameters
were basically impossible to set correctly when attempting to pass `None`.

## [0.5.0] - 2024-02-04
### Changed
Expand Down
54 changes: 48 additions & 6 deletions age-plugin/examples/age-plugin-unencrypted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ use age_plugin::{
identity::{self, IdentityPluginV1},
print_new_identity,
recipient::{self, RecipientPluginV1},
run_state_machine, Callbacks,
run_state_machine, Callbacks, PluginHandler,
};
use clap::Parser;

use std::collections::HashMap;
use std::convert::Infallible;
use std::env;
use std::io;

Expand All @@ -25,6 +26,43 @@ fn explode(location: &str) {
}
}

struct FullHandler;

impl PluginHandler for FullHandler {
type RecipientV1 = RecipientPlugin;
type IdentityV1 = IdentityPlugin;

fn recipient_v1(self) -> io::Result<Self::RecipientV1> {
Ok(RecipientPlugin)
}

fn identity_v1(self) -> io::Result<Self::IdentityV1> {
Ok(IdentityPlugin)
}
}

struct RecipientHandler;

impl PluginHandler for RecipientHandler {
type RecipientV1 = RecipientPlugin;
type IdentityV1 = Infallible;

fn recipient_v1(self) -> io::Result<Self::RecipientV1> {
Ok(RecipientPlugin)
}
}

struct IdentityHandler;

impl PluginHandler for IdentityHandler {
type RecipientV1 = Infallible;
type IdentityV1 = IdentityPlugin;

fn identity_v1(self) -> io::Result<Self::IdentityV1> {
Ok(IdentityPlugin)
}
}

struct RecipientPlugin;

impl RecipientPluginV1 for RecipientPlugin {
Expand Down Expand Up @@ -149,11 +187,15 @@ fn main() -> io::Result<()> {
let opts = PluginOptions::parse();

if let Some(state_machine) = opts.age_plugin {
run_state_machine(
&state_machine,
Some(|| RecipientPlugin),
Some(|| IdentityPlugin),
)
if let Ok(s) = env::var("AGE_HALF_PLUGIN") {
match s.as_str() {
"recipient" => run_state_machine(&state_machine, RecipientHandler),
"identity" => run_state_machine(&state_machine, IdentityHandler),
_ => panic!("Env variable AGE_HALF_PLUGIN={s} has unknown value. Boom! 💥"),
}
} else {
run_state_machine(&state_machine, FullHandler)
}
} else {
// A real plugin would generate a new identity here.
print_new_identity(PLUGIN_NAME, &[], &[]);
Expand Down
22 changes: 22 additions & 0 deletions age-plugin/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use age_core::{
};
use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine};
use bech32::FromBase32;

use std::collections::HashMap;
use std::convert::Infallible;
use std::io;

use crate::{Callbacks, PLUGIN_IDENTITY_PREFIX};
Expand All @@ -16,6 +18,10 @@ const ADD_IDENTITY: &str = "add-identity";
const RECIPIENT_STANZA: &str = "recipient-stanza";

/// The interface that age implementations will use to interact with an age plugin.
///
/// Implementations of this trait will be used within the [`identity-v1`] state machine.
///
/// [`identity-v1`]: https://c2sp.org/age-plugin#unwrapping-with-identity-v1
pub trait IdentityPluginV1 {
/// Stores an identity that the user would like to use for decrypting age files.
///
Expand Down Expand Up @@ -49,6 +55,22 @@ pub trait IdentityPluginV1 {
) -> io::Result<HashMap<usize, Result<FileKey, Vec<Error>>>>;
}

impl IdentityPluginV1 for Infallible {
fn add_identity(&mut self, _: usize, _: &str, _: &[u8]) -> Result<(), Error> {
// This is never executed.
Ok(())
}

fn unwrap_file_keys(
&mut self,
_: Vec<Vec<Stanza>>,
_: impl Callbacks<Error>,
) -> io::Result<HashMap<usize, Result<FileKey, Vec<Error>>>> {
// This is never executed.
Ok(HashMap::new())
}
}

/// The interface that age plugins can use to interact with an age implementation.
struct BidirCallbacks<'a, 'b, R: io::Read, W: io::Write>(&'b mut BidirSend<'a, R, W>);

Expand Down
108 changes: 77 additions & 31 deletions age-plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,28 @@
//! identity::{self, IdentityPluginV1},
//! print_new_identity,
//! recipient::{self, RecipientPluginV1},
//! Callbacks, run_state_machine,
//! Callbacks, PluginHandler, run_state_machine,
//! };
//! use clap::Parser;
//!
//! use std::collections::HashMap;
//! use std::io;
//!
//! struct Handler;
//!
//! impl PluginHandler for Handler {
//! type RecipientV1 = RecipientPlugin;
//! type IdentityV1 = IdentityPlugin;
//!
//! fn recipient_v1(self) -> io::Result<Self::RecipientV1> {
//! Ok(RecipientPlugin)
//! }
//!
//! fn identity_v1(self) -> io::Result<Self::IdentityV1> {
//! Ok(IdentityPlugin)
//! }
//! }
//!
//! struct RecipientPlugin;
//!
//! impl RecipientPluginV1 for RecipientPlugin {
Expand Down Expand Up @@ -143,11 +158,7 @@
//!
//! if let Some(state_machine) = opts.age_plugin {
//! // The plugin was started by an age client; run the state machine.
//! run_state_machine(
//! &state_machine,
//! Some(|| RecipientPlugin),
//! Some(|| IdentityPlugin),
//! )?;
//! run_state_machine(&state_machine, Handler)?;
//! return Ok(());
//! }
//!
Expand Down Expand Up @@ -209,41 +220,76 @@ pub fn print_new_identity(plugin_name: &str, identity: &[u8], recipient: &[u8])
///
/// This should be triggered if the `--age-plugin=state_machine` flag is provided as an
/// argument when starting the plugin.
pub fn run_state_machine<R: recipient::RecipientPluginV1, I: identity::IdentityPluginV1>(
state_machine: &str,
recipient_v1: Option<impl FnOnce() -> R>,
identity_v1: Option<impl FnOnce() -> I>,
) -> io::Result<()> {
pub fn run_state_machine(state_machine: &str, handler: impl PluginHandler) -> io::Result<()> {
use age_core::plugin::{IDENTITY_V1, RECIPIENT_V1};

match state_machine {
RECIPIENT_V1 => {
if let Some(plugin) = recipient_v1 {
recipient::run_v1(plugin())
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"plugin doesn't support recipient-v1 state machine",
))
}
}
IDENTITY_V1 => {
if let Some(plugin) = identity_v1 {
identity::run_v1(plugin())
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"plugin doesn't support identity-v1 state machine",
))
}
}
RECIPIENT_V1 => recipient::run_v1(handler.recipient_v1()?),
IDENTITY_V1 => identity::run_v1(handler.identity_v1()?),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"unknown plugin state machine",
)),
}
}

/// The interfaces that age implementations will use to interact with an age plugin.
///
/// This trait exists to encapsulate the set of arguments to [`run_state_machine`] that
/// different plugins may want to provide.
///
/// # How to implement this trait
///
/// ## Full plugins
///
/// - Set all associated types to your plugin's implementations.
/// - Override all default methods of the trait.
///
/// ## Recipient-only plugins
///
/// - Set [`PluginHandler::RecipientV1`] to your plugin's implementation.
/// - Override [`PluginHandler::recipient_v1`] to return an instance of your type.
/// - Set [`PluginHandler::IdentityV1`] to [`std::convert::Infallible`].
/// - Don't override [`PluginHandler::identity_v1`].
///
/// ## Identity-only plugins
///
/// - Set [`PluginHandler::RecipientV1`] to [`std::convert::Infallible`].
/// - Don't override [`PluginHandler::recipient_v1`].
/// - Set [`PluginHandler::IdentityV1`] to your plugin's implementation.
/// - Override [`PluginHandler::identity_v1`] to return an instance of your type.
pub trait PluginHandler: Sized {
/// The plugin's [`recipient-v1`] implementation.
///
/// [`recipient-v1`]: https://c2sp.org/age-plugin#wrapping-with-recipient-v1
type RecipientV1: recipient::RecipientPluginV1;

/// The plugin's [`identity-v1`] implementation.
///
/// [`identity-v1`]: https://c2sp.org/age-plugin#unwrapping-with-identity-v1
type IdentityV1: identity::IdentityPluginV1;

/// Returns an instance of the plugin's [`recipient-v1`] implementation.
///
/// [`recipient-v1`]: https://c2sp.org/age-plugin#wrapping-with-recipient-v1
fn recipient_v1(self) -> io::Result<Self::RecipientV1> {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"plugin doesn't support recipient-v1 state machine",
))
}

/// Returns an instance of the plugin's [`identity-v1`] implementation.
///
/// [`identity-v1`]: https://c2sp.org/age-plugin#unwrapping-with-identity-v1
fn identity_v1(self) -> io::Result<Self::IdentityV1> {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"plugin doesn't support identity-v1 state machine",
))
}
}

/// The interface that age plugins can use to interact with an age implementation.
pub trait Callbacks<E> {
/// Shows a message to the user.
Expand Down
27 changes: 27 additions & 0 deletions age-plugin/src/recipient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use age_core::{
};
use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine};
use bech32::FromBase32;

use std::convert::Infallible;
use std::io;

use crate::{Callbacks, PLUGIN_IDENTITY_PREFIX, PLUGIN_RECIPIENT_PREFIX};
Expand All @@ -17,6 +19,10 @@ const WRAP_FILE_KEY: &str = "wrap-file-key";
const RECIPIENT_STANZA: &str = "recipient-stanza";

/// The interface that age implementations will use to interact with an age plugin.
///
/// Implementations of this trait will be used within the [`recipient-v1`] state machine.
///
/// [`recipient-v1`]: https://c2sp.org/age-plugin#wrapping-with-recipient-v1
pub trait RecipientPluginV1 {
/// Stores a recipient that the user would like to encrypt age files to.
///
Expand Down Expand Up @@ -48,6 +54,27 @@ pub trait RecipientPluginV1 {
) -> io::Result<Result<Vec<Vec<Stanza>>, Vec<Error>>>;
}

impl RecipientPluginV1 for Infallible {
fn add_recipient(&mut self, _: usize, _: &str, _: &[u8]) -> Result<(), Error> {
// This is never executed.
Ok(())
}

fn add_identity(&mut self, _: usize, _: &str, _: &[u8]) -> Result<(), Error> {
// This is never executed.
Ok(())
}

fn wrap_file_keys(
&mut self,
_: Vec<FileKey>,
_: impl Callbacks<Error>,
) -> io::Result<Result<Vec<Vec<Stanza>>, Vec<Error>>> {
// This is never executed.
Ok(Ok(vec![]))
}
}

/// The interface that age plugins can use to interact with an age implementation.
struct BidirCallbacks<'a, 'b, R: io::Read, W: io::Write>(&'b mut BidirSend<'a, R, W>);

Expand Down

0 comments on commit d2c2e89

Please sign in to comment.