819 lines
26 KiB
Rust
819 lines
26 KiB
Rust
use reqwest::{
|
|
header::{HeaderValue, HOST},
|
|
Url,
|
|
};
|
|
use std::fmt::Debug;
|
|
|
|
use super::{request, sign::Signer};
|
|
|
|
/// Represents an ActivityPub inbox.
|
|
///
|
|
/// It routes an incoming Activity through the registered handlers.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust
|
|
/// # extern crate activitypub;
|
|
/// # use activitypub::{actor::Person, activity::{Announce, Create}, object::Note};
|
|
/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
|
/// # use once_cell::sync::Lazy;
|
|
/// # use plume_common::activity_pub::inbox::*;
|
|
/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer};
|
|
/// #
|
|
/// # static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
|
|
/// #
|
|
/// # struct MySigner {
|
|
/// # public_key: String,
|
|
/// # private_key: String,
|
|
/// # }
|
|
/// #
|
|
/// # impl MySigner {
|
|
/// # fn new() -> Self {
|
|
/// # let (pub_key, priv_key) = gen_keypair();
|
|
/// # Self {
|
|
/// # public_key: String::from_utf8(pub_key).unwrap(),
|
|
/// # private_key: String::from_utf8(priv_key).unwrap(),
|
|
/// # }
|
|
/// # }
|
|
/// # }
|
|
/// #
|
|
/// # impl Signer for MySigner {
|
|
/// # fn get_key_id(&self) -> String {
|
|
/// # "mysigner".into()
|
|
/// # }
|
|
/// #
|
|
/// # fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
|
/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
|
/// # .unwrap();
|
|
/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
|
/// # signer.update(to_sign.as_bytes()).unwrap();
|
|
/// # signer.sign_to_vec().map_err(|_| SignError())
|
|
/// # }
|
|
/// #
|
|
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
|
/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
|
/// # .unwrap();
|
|
/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
|
/// # verifier.update(data.as_bytes()).unwrap();
|
|
/// # verifier.verify(&signature).map_err(|_| SignError())
|
|
/// # }
|
|
/// # }
|
|
/// #
|
|
/// # struct User;
|
|
/// # impl FromId<()> for User {
|
|
/// # type Error = ();
|
|
/// # type Object = Person;
|
|
/// #
|
|
/// # fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
|
|
/// # Ok(User)
|
|
/// # }
|
|
/// #
|
|
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
|
/// # Ok(User)
|
|
/// # }
|
|
/// #
|
|
/// # fn get_sender() -> &'static dyn Signer {
|
|
/// # &*MY_SIGNER
|
|
/// # }
|
|
/// # }
|
|
/// # impl AsActor<&()> for User {
|
|
/// # fn get_inbox_url(&self) -> String {
|
|
/// # String::new()
|
|
/// # }
|
|
/// # fn is_local(&self) -> bool { false }
|
|
/// # }
|
|
/// # struct Message;
|
|
/// # impl FromId<()> for Message {
|
|
/// # type Error = ();
|
|
/// # type Object = Note;
|
|
/// #
|
|
/// # fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
|
|
/// # Ok(Message)
|
|
/// # }
|
|
/// #
|
|
/// # fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
|
/// # Ok(Message)
|
|
/// # }
|
|
/// #
|
|
/// # fn get_sender() -> &'static dyn Signer {
|
|
/// # &*MY_SIGNER
|
|
/// # }
|
|
/// # }
|
|
/// # impl AsObject<User, Create, &()> for Message {
|
|
/// # type Error = ();
|
|
/// # type Output = ();
|
|
/// #
|
|
/// # fn activity(self, _: &(), _actor: User, _id: &str) -> Result<(), ()> {
|
|
/// # Ok(())
|
|
/// # }
|
|
/// # }
|
|
/// # impl AsObject<User, Announce, &()> for Message {
|
|
/// # type Error = ();
|
|
/// # type Output = ();
|
|
/// #
|
|
/// # fn activity(self, _: &(), _actor: User, _id: &str) -> Result<(), ()> {
|
|
/// # Ok(())
|
|
/// # }
|
|
/// # }
|
|
/// #
|
|
/// # let mut act = Create::default();
|
|
/// # act.object_props.set_id_string(String::from("https://test.ap/activity")).unwrap();
|
|
/// # let mut person = Person::default();
|
|
/// # person.object_props.set_id_string(String::from("https://test.ap/actor")).unwrap();
|
|
/// # act.create_props.set_actor_object(person).unwrap();
|
|
/// # act.create_props.set_object_object(Note::default()).unwrap();
|
|
/// # let activity_json = serde_json::to_value(act).unwrap();
|
|
/// #
|
|
/// # let conn = ();
|
|
/// #
|
|
/// let result: Result<(), ()> = Inbox::handle(&conn, activity_json)
|
|
/// .with::<User, Announce, Message>(None)
|
|
/// .with::<User, Create, Message>(None)
|
|
/// .done();
|
|
/// ```
|
|
pub enum Inbox<'a, C, E, R>
|
|
where
|
|
E: From<InboxError<E>> + Debug,
|
|
{
|
|
/// The activity has not been handled yet
|
|
///
|
|
/// # Structure
|
|
///
|
|
/// - the context to be passed to each handler.
|
|
/// - the activity
|
|
/// - the reason it has not been handled yet
|
|
NotHandled(&'a C, serde_json::Value, InboxError<E>),
|
|
|
|
/// A matching handler have been found but failed
|
|
///
|
|
/// The wrapped value is the error returned by the handler
|
|
Failed(E),
|
|
|
|
/// The activity was successfully handled
|
|
///
|
|
/// The wrapped value is the value returned by the handler
|
|
Handled(R),
|
|
}
|
|
|
|
/// Possible reasons of inbox failure
|
|
#[derive(Debug)]
|
|
pub enum InboxError<E: Debug> {
|
|
/// None of the registered handlers matched
|
|
NoMatch,
|
|
|
|
/// No ID was provided for the incoming activity, or it was not a string
|
|
InvalidID,
|
|
|
|
/// The activity type matched for at least one handler, but then the actor was
|
|
/// not of the expected type
|
|
InvalidActor(Option<E>),
|
|
|
|
/// Activity and Actor types matched, but not the Object
|
|
InvalidObject(Option<E>),
|
|
|
|
/// Error while dereferencing the object
|
|
DerefError,
|
|
}
|
|
|
|
impl<T: Debug> From<InboxError<T>> for () {
|
|
fn from(_: InboxError<T>) {}
|
|
}
|
|
|
|
/*
|
|
Type arguments:
|
|
- C: Context
|
|
- E: Error
|
|
- R: Result
|
|
*/
|
|
impl<'a, C, E, R> Inbox<'a, C, E, R>
|
|
where
|
|
E: From<InboxError<E>> + Debug,
|
|
{
|
|
/// Creates a new `Inbox` to handle an incoming activity.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// - `ctx`: the context to pass to each handler
|
|
/// - `json`: the JSON representation of the incoming activity
|
|
pub fn handle(ctx: &'a C, json: serde_json::Value) -> Inbox<'a, C, E, R> {
|
|
Inbox::NotHandled(ctx, json, InboxError::NoMatch)
|
|
}
|
|
|
|
/// Registers an handler on this Inbox.
|
|
pub fn with<A, V, M>(self, proxy: Option<&reqwest::Proxy>) -> Inbox<'a, C, E, R>
|
|
where
|
|
A: AsActor<&'a C> + FromId<C, Error = E>,
|
|
V: activitypub::Activity,
|
|
M: AsObject<A, V, &'a C, Error = E> + FromId<C, Error = E>,
|
|
M::Output: Into<R>,
|
|
{
|
|
if let Inbox::NotHandled(ctx, mut act, e) = self {
|
|
if serde_json::from_value::<V>(act.clone()).is_ok() {
|
|
let act_clone = act.clone();
|
|
let act_id = match act_clone["id"].as_str() {
|
|
Some(x) => x,
|
|
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidID),
|
|
};
|
|
|
|
// Get the actor ID
|
|
let actor_id = match get_id(act["actor"].clone()) {
|
|
Some(x) => x,
|
|
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(None)),
|
|
};
|
|
|
|
if Self::is_spoofed_activity(&actor_id, &act) {
|
|
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None));
|
|
}
|
|
|
|
// Transform this actor to a model (see FromId for details about the from_id function)
|
|
let actor = match A::from_id(
|
|
ctx,
|
|
&actor_id,
|
|
serde_json::from_value(act["actor"].clone()).ok(),
|
|
proxy,
|
|
) {
|
|
Ok(a) => a,
|
|
// If the actor was not found, go to the next handler
|
|
Err((json, e)) => {
|
|
if let Some(json) = json {
|
|
act["actor"] = json;
|
|
}
|
|
return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(Some(e)));
|
|
}
|
|
};
|
|
|
|
// Same logic for "object"
|
|
let obj_id = match get_id(act["object"].clone()) {
|
|
Some(x) => x,
|
|
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None)),
|
|
};
|
|
let obj = match M::from_id(
|
|
ctx,
|
|
&obj_id,
|
|
serde_json::from_value(act["object"].clone()).ok(),
|
|
proxy,
|
|
) {
|
|
Ok(o) => o,
|
|
Err((json, e)) => {
|
|
if let Some(json) = json {
|
|
act["object"] = json;
|
|
}
|
|
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(Some(e)));
|
|
}
|
|
};
|
|
|
|
// Handle the activity
|
|
match obj.activity(ctx, actor, act_id) {
|
|
Ok(res) => Inbox::Handled(res.into()),
|
|
Err(e) => Inbox::Failed(e),
|
|
}
|
|
} else {
|
|
// If the Activity type is not matching the expected one for
|
|
// this handler, try with the next one.
|
|
Inbox::NotHandled(ctx, act, e)
|
|
}
|
|
} else {
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Transforms the inbox in a `Result`
|
|
pub fn done(self) -> Result<R, E> {
|
|
match self {
|
|
Inbox::Handled(res) => Ok(res),
|
|
Inbox::NotHandled(_, _, err) => Err(E::from(err)),
|
|
Inbox::Failed(err) => Err(err),
|
|
}
|
|
}
|
|
|
|
fn is_spoofed_activity(actor_id: &str, act: &serde_json::Value) -> bool {
|
|
use serde_json::Value::{Array, Object, String};
|
|
|
|
let attributed_to = act["object"].get("attributedTo");
|
|
if attributed_to.is_none() {
|
|
return false;
|
|
}
|
|
let attributed_to = attributed_to.unwrap();
|
|
match attributed_to {
|
|
Array(v) => v.iter().all(|i| match i {
|
|
String(s) => s != actor_id,
|
|
Object(obj) => obj.get("id").map_or(true, |s| s != actor_id),
|
|
_ => false,
|
|
}),
|
|
String(s) => s != actor_id,
|
|
Object(obj) => obj.get("id").map_or(true, |s| s != actor_id),
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get the ActivityPub ID of a JSON value.
|
|
///
|
|
/// If the value is a string, its value is returned.
|
|
/// If it is an object, and that its `id` field is a string, we return it.
|
|
///
|
|
/// Otherwise, `None` is returned.
|
|
fn get_id(json: serde_json::Value) -> Option<String> {
|
|
match json {
|
|
serde_json::Value::String(s) => Some(s),
|
|
serde_json::Value::Object(map) => map.get("id")?.as_str().map(ToString::to_string),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// A trait for ActivityPub objects that can be retrieved or constructed from ID.
|
|
///
|
|
/// The two functions to implement are `from_activity` to create (and save) a new object
|
|
/// of this type from its AP representation, and `from_db` to try to find it in the database
|
|
/// using its ID.
|
|
///
|
|
/// When dealing with the "object" field of incoming activities, `Inbox` will try to see if it is
|
|
/// a full object, and if so, save it with `from_activity`. If it is only an ID, it will try to find
|
|
/// it in the database with `from_db`, and otherwise dereference (fetch) the full object and parse it
|
|
/// with `from_activity`.
|
|
pub trait FromId<C>: Sized {
|
|
/// The type representing a failure
|
|
type Error: From<InboxError<Self::Error>> + Debug;
|
|
|
|
/// The ActivityPub object type representing Self
|
|
type Object: activitypub::Object;
|
|
|
|
/// Tries to get an instance of `Self` from an ActivityPub ID.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// - `ctx`: a context to get this instance (= a database in which to search)
|
|
/// - `id`: the ActivityPub ID of the object to find
|
|
/// - `object`: optional object that will be used if the object was not found in the database
|
|
/// If absent, the ID will be dereferenced.
|
|
fn from_id(
|
|
ctx: &C,
|
|
id: &str,
|
|
object: Option<Self::Object>,
|
|
proxy: Option<&reqwest::Proxy>,
|
|
) -> Result<Self, (Option<serde_json::Value>, Self::Error)> {
|
|
match Self::from_db(ctx, id) {
|
|
Ok(x) => Ok(x),
|
|
_ => match object {
|
|
Some(o) => Self::from_activity(ctx, o).map_err(|e| (None, e)),
|
|
None => Self::from_activity(ctx, Self::deref(id, proxy.cloned())?)
|
|
.map_err(|e| (None, e)),
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Dereferences an ID
|
|
fn deref(
|
|
id: &str,
|
|
proxy: Option<reqwest::Proxy>,
|
|
) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
|
|
let mut headers = request::headers();
|
|
let url = Url::parse(id).map_err(|_| (None, InboxError::DerefError.into()))?;
|
|
if !url.has_host() {
|
|
return Err((None, InboxError::DerefError.into()));
|
|
}
|
|
let host_header_value = HeaderValue::from_str(&url.host_str().expect("Unreachable"))
|
|
.map_err(|_| (None, InboxError::DerefError.into()))?;
|
|
headers.insert(HOST, host_header_value);
|
|
if let Some(proxy) = proxy {
|
|
reqwest::ClientBuilder::new().proxy(proxy)
|
|
} else {
|
|
reqwest::ClientBuilder::new()
|
|
}
|
|
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
|
.build()
|
|
.map_err(|_| (None, InboxError::DerefError.into()))?
|
|
.get(id)
|
|
.headers(headers.clone())
|
|
.header(
|
|
"Signature",
|
|
request::signature(
|
|
Self::get_sender(),
|
|
&headers,
|
|
("get", url.path(), url.query()),
|
|
)
|
|
.map_err(|_| (None, InboxError::DerefError.into()))?,
|
|
)
|
|
.send()
|
|
.map_err(|_| (None, InboxError::DerefError))
|
|
.and_then(|mut r| {
|
|
let json: serde_json::Value = r
|
|
.json()
|
|
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
|
|
serde_json::from_value(json.clone())
|
|
.map_err(|_| (Some(json), InboxError::InvalidObject(None)))
|
|
})
|
|
.map_err(|(json, e)| (json, e.into()))
|
|
}
|
|
|
|
/// Builds a `Self` from its ActivityPub representation
|
|
fn from_activity(ctx: &C, activity: Self::Object) -> Result<Self, Self::Error>;
|
|
|
|
/// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database)
|
|
fn from_db(ctx: &C, id: &str) -> Result<Self, Self::Error>;
|
|
|
|
fn get_sender() -> &'static dyn Signer;
|
|
}
|
|
|
|
/// Should be implemented by anything representing an ActivityPub actor.
|
|
///
|
|
/// # Type arguments
|
|
///
|
|
/// - `C`: the context to be passed to this activity handler from the `Inbox` (usually a database connection)
|
|
pub trait AsActor<C> {
|
|
/// Return the URL of this actor's inbox
|
|
fn get_inbox_url(&self) -> String;
|
|
|
|
/// If this actor has shared inbox, its URL should be returned by this function
|
|
fn get_shared_inbox_url(&self) -> Option<String> {
|
|
None
|
|
}
|
|
|
|
/// `true` if this actor comes from the running ActivityPub server/instance
|
|
fn is_local(&self) -> bool;
|
|
}
|
|
|
|
/// Should be implemented by anything representing an ActivityPub object.
|
|
///
|
|
/// # Type parameters
|
|
///
|
|
/// - `A`: the actor type
|
|
/// - `V`: the ActivityPub verb/activity
|
|
/// - `O`: the ActivityPub type of the Object for this activity (usually the type corresponding to `Self`)
|
|
/// - `C`: the context needed to handle the activity (usually a database connection)
|
|
///
|
|
/// # Example
|
|
///
|
|
/// An implementation of AsObject that handles Note creation by an Account model,
|
|
/// representing the Note by a Message type, without any specific context.
|
|
///
|
|
/// ```rust
|
|
/// # extern crate activitypub;
|
|
/// # use activitypub::{activity::Create, actor::Person, object::Note};
|
|
/// # use plume_common::activity_pub::inbox::{AsActor, AsObject, FromId};
|
|
/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer};
|
|
/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
|
/// # use once_cell::sync::Lazy;
|
|
/// #
|
|
/// # static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
|
|
/// #
|
|
/// # struct MySigner {
|
|
/// # public_key: String,
|
|
/// # private_key: String,
|
|
/// # }
|
|
/// #
|
|
/// # impl MySigner {
|
|
/// # fn new() -> Self {
|
|
/// # let (pub_key, priv_key) = gen_keypair();
|
|
/// # Self {
|
|
/// # public_key: String::from_utf8(pub_key).unwrap(),
|
|
/// # private_key: String::from_utf8(priv_key).unwrap(),
|
|
/// # }
|
|
/// # }
|
|
/// # }
|
|
/// #
|
|
/// # impl Signer for MySigner {
|
|
/// # fn get_key_id(&self) -> String {
|
|
/// # "mysigner".into()
|
|
/// # }
|
|
/// #
|
|
/// # fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
|
/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
|
/// # .unwrap();
|
|
/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
|
/// # signer.update(to_sign.as_bytes()).unwrap();
|
|
/// # signer.sign_to_vec().map_err(|_| SignError())
|
|
/// # }
|
|
/// #
|
|
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
|
/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
|
/// # .unwrap();
|
|
/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
|
/// # verifier.update(data.as_bytes()).unwrap();
|
|
/// # verifier.verify(&signature).map_err(|_| SignError())
|
|
/// # }
|
|
/// # }
|
|
/// #
|
|
/// # struct Account;
|
|
/// # impl FromId<()> for Account {
|
|
/// # type Error = ();
|
|
/// # type Object = Person;
|
|
/// #
|
|
/// # fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
|
|
/// # Ok(Account)
|
|
/// # }
|
|
/// #
|
|
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
|
/// # Ok(Account)
|
|
/// # }
|
|
/// #
|
|
/// # fn get_sender() -> &'static dyn Signer {
|
|
/// # &*MY_SIGNER
|
|
/// # }
|
|
/// # }
|
|
/// # impl AsActor<()> for Account {
|
|
/// # fn get_inbox_url(&self) -> String {
|
|
/// # String::new()
|
|
/// # }
|
|
/// # fn is_local(&self) -> bool { false }
|
|
/// # }
|
|
/// #[derive(Debug)]
|
|
/// struct Message {
|
|
/// text: String,
|
|
/// }
|
|
///
|
|
/// impl FromId<()> for Message {
|
|
/// type Error = ();
|
|
/// type Object = Note;
|
|
///
|
|
/// fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
|
|
/// Ok(Message { text: "From DB".into() })
|
|
/// }
|
|
///
|
|
/// fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
|
/// Ok(Message { text: obj.object_props.content_string().map_err(|_| ())? })
|
|
/// }
|
|
///
|
|
/// fn get_sender() -> &'static dyn Signer {
|
|
/// &*MY_SIGNER
|
|
/// }
|
|
/// }
|
|
///
|
|
/// impl AsObject<Account, Create, ()> for Message {
|
|
/// type Error = ();
|
|
/// type Output = ();
|
|
///
|
|
/// fn activity(self, _: (), _actor: Account, _id: &str) -> Result<(), ()> {
|
|
/// println!("New Note: {:?}", self);
|
|
/// Ok(())
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
pub trait AsObject<A, V, C>
|
|
where
|
|
V: activitypub::Activity,
|
|
{
|
|
/// What kind of error is returned when something fails
|
|
type Error;
|
|
|
|
/// What is returned by `AsObject::activity`, if anything is returned
|
|
type Output = ();
|
|
|
|
/// Handle a specific type of activity dealing with this type of objects.
|
|
///
|
|
/// The implementations should check that the actor is actually authorized
|
|
/// to perform this action.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// - `self`: the object on which the activity acts
|
|
/// - `ctx`: the context passed to `Inbox::handle`
|
|
/// - `actor`: the actor who did this activity
|
|
/// - `id`: the ID of this activity
|
|
fn activity(self, ctx: C, actor: A, id: &str) -> Result<Self::Output, Self::Error>;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::activity_pub::sign::{
|
|
gen_keypair, Error as SignError, Result as SignResult, Signer,
|
|
};
|
|
use activitypub::{activity::*, actor::Person, object::Note};
|
|
use once_cell::sync::Lazy;
|
|
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
|
|
|
static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
|
|
|
|
struct MySigner {
|
|
public_key: String,
|
|
private_key: String,
|
|
}
|
|
|
|
impl MySigner {
|
|
fn new() -> Self {
|
|
let (pub_key, priv_key) = gen_keypair();
|
|
Self {
|
|
public_key: String::from_utf8(pub_key).unwrap(),
|
|
private_key: String::from_utf8(priv_key).unwrap(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Signer for MySigner {
|
|
fn get_key_id(&self) -> String {
|
|
"mysigner".into()
|
|
}
|
|
|
|
fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
|
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
|
.unwrap();
|
|
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
|
signer.update(to_sign.as_bytes()).unwrap();
|
|
signer.sign_to_vec().map_err(|_| SignError())
|
|
}
|
|
|
|
fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
|
.unwrap();
|
|
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
|
verifier.update(data.as_bytes()).unwrap();
|
|
verifier.verify(&signature).map_err(|_| SignError())
|
|
}
|
|
}
|
|
|
|
struct MyActor;
|
|
impl FromId<()> for MyActor {
|
|
type Error = ();
|
|
type Object = Person;
|
|
|
|
fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
|
|
Ok(MyActor)
|
|
}
|
|
|
|
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
|
Ok(MyActor)
|
|
}
|
|
|
|
fn get_sender() -> &'static dyn Signer {
|
|
&*MY_SIGNER
|
|
}
|
|
}
|
|
|
|
impl AsActor<&()> for MyActor {
|
|
fn get_inbox_url(&self) -> String {
|
|
String::from("https://test.ap/my-actor/inbox")
|
|
}
|
|
|
|
fn is_local(&self) -> bool {
|
|
false
|
|
}
|
|
}
|
|
|
|
struct MyObject;
|
|
impl FromId<()> for MyObject {
|
|
type Error = ();
|
|
type Object = Note;
|
|
|
|
fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
|
|
Ok(MyObject)
|
|
}
|
|
|
|
fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> {
|
|
Ok(MyObject)
|
|
}
|
|
|
|
fn get_sender() -> &'static dyn Signer {
|
|
&*MY_SIGNER
|
|
}
|
|
}
|
|
impl AsObject<MyActor, Create, &()> for MyObject {
|
|
type Error = ();
|
|
type Output = ();
|
|
|
|
fn activity(self, _: &(), _actor: MyActor, _id: &str) -> Result<Self::Output, Self::Error> {
|
|
println!("MyActor is creating a Note");
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl AsObject<MyActor, Like, &()> for MyObject {
|
|
type Error = ();
|
|
type Output = ();
|
|
|
|
fn activity(self, _: &(), _actor: MyActor, _id: &str) -> Result<Self::Output, Self::Error> {
|
|
println!("MyActor is liking a Note");
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl AsObject<MyActor, Delete, &()> for MyObject {
|
|
type Error = ();
|
|
type Output = ();
|
|
|
|
fn activity(self, _: &(), _actor: MyActor, _id: &str) -> Result<Self::Output, Self::Error> {
|
|
println!("MyActor is deleting a Note");
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl AsObject<MyActor, Announce, &()> for MyObject {
|
|
type Error = ();
|
|
type Output = ();
|
|
|
|
fn activity(self, _: &(), _actor: MyActor, _id: &str) -> Result<Self::Output, Self::Error> {
|
|
println!("MyActor is announcing a Note");
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn build_create() -> Create {
|
|
let mut act = Create::default();
|
|
act.object_props
|
|
.set_id_string(String::from("https://test.ap/activity"))
|
|
.unwrap();
|
|
let mut person = Person::default();
|
|
person
|
|
.object_props
|
|
.set_id_string(String::from("https://test.ap/actor"))
|
|
.unwrap();
|
|
act.create_props.set_actor_object(person).unwrap();
|
|
let mut note = Note::default();
|
|
note.object_props
|
|
.set_id_string(String::from("https://test.ap/note"))
|
|
.unwrap();
|
|
act.create_props.set_object_object(note).unwrap();
|
|
act
|
|
}
|
|
|
|
#[test]
|
|
fn test_inbox_basic() {
|
|
let act = serde_json::to_value(build_create()).unwrap();
|
|
let res: Result<(), ()> = Inbox::handle(&(), act)
|
|
.with::<MyActor, Create, MyObject>(None)
|
|
.done();
|
|
assert!(res.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_inbox_multi_handlers() {
|
|
let act = serde_json::to_value(build_create()).unwrap();
|
|
let res: Result<(), ()> = Inbox::handle(&(), act)
|
|
.with::<MyActor, Announce, MyObject>(None)
|
|
.with::<MyActor, Delete, MyObject>(None)
|
|
.with::<MyActor, Create, MyObject>(None)
|
|
.with::<MyActor, Like, MyObject>(None)
|
|
.done();
|
|
assert!(res.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_inbox_failure() {
|
|
let act = serde_json::to_value(build_create()).unwrap();
|
|
// Create is not handled by this inbox
|
|
let res: Result<(), ()> = Inbox::handle(&(), act)
|
|
.with::<MyActor, Announce, MyObject>(None)
|
|
.with::<MyActor, Like, MyObject>(None)
|
|
.done();
|
|
assert!(res.is_err());
|
|
}
|
|
|
|
struct FailingActor;
|
|
impl FromId<()> for FailingActor {
|
|
type Error = ();
|
|
type Object = Person;
|
|
|
|
fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
|
|
Err(())
|
|
}
|
|
|
|
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
|
Err(())
|
|
}
|
|
|
|
fn get_sender() -> &'static dyn Signer {
|
|
&*MY_SIGNER
|
|
}
|
|
}
|
|
impl AsActor<&()> for FailingActor {
|
|
fn get_inbox_url(&self) -> String {
|
|
String::from("https://test.ap/failing-actor/inbox")
|
|
}
|
|
|
|
fn is_local(&self) -> bool {
|
|
false
|
|
}
|
|
}
|
|
|
|
impl AsObject<FailingActor, Create, &()> for MyObject {
|
|
type Error = ();
|
|
type Output = ();
|
|
|
|
fn activity(
|
|
self,
|
|
_: &(),
|
|
_actor: FailingActor,
|
|
_id: &str,
|
|
) -> Result<Self::Output, Self::Error> {
|
|
println!("FailingActor is creating a Note");
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_inbox_actor_failure() {
|
|
let act = serde_json::to_value(build_create()).unwrap();
|
|
|
|
let res: Result<(), ()> = Inbox::handle(&(), act.clone())
|
|
.with::<FailingActor, Create, MyObject>(None)
|
|
.done();
|
|
assert!(res.is_err());
|
|
|
|
let res: Result<(), ()> = Inbox::handle(&(), act.clone())
|
|
.with::<FailingActor, Create, MyObject>(None)
|
|
.with::<MyActor, Create, MyObject>(None)
|
|
.done();
|
|
assert!(res.is_ok());
|
|
}
|
|
}
|