Remove AsObject

This commit is contained in:
Kitaiti Makoto 2022-05-02 17:38:08 +09:00
parent d8a2e1925f
commit 33afe9111e
7 changed files with 6 additions and 470 deletions

View File

@ -519,146 +519,6 @@ pub trait AsActor<C> {
/// }
/// }
/// ```
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>;
}
/// 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 AsObject07<A, V, C>
where
V: activitystreams::markers::Activity,
@ -769,47 +629,6 @@ mod tests {
}
}
struct MyObject;
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(())
}
}
struct MyObject07;
impl FromId<()> for MyObject07 {
type Error = ();
@ -962,21 +781,6 @@ mod tests {
}
}
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(())
}
}
impl FromId<()> for FailingActor {
type Error = ();
type Object = Person07;

View File

@ -30,7 +30,7 @@ use chrono::{self, NaiveDateTime, TimeZone, Utc};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use plume_common::{
activity_pub::{
inbox::{AsActor, AsObject, AsObject07, FromId},
inbox::{AsActor, AsObject07, FromId},
sign::Signer,
Id, IntoId, ToAsString, ToAsUri, PUBLIC_VISIBILITY,
},
@ -431,45 +431,6 @@ impl FromId<DbConn> for Comment {
}
}
impl AsObject<User, Create, &DbConn> for Comment {
type Error = Error;
type Output = Self;
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Self> {
// The actual creation takes place in the FromId impl
Ok(self)
}
}
impl AsObject<User, Delete, &DbConn> for Comment {
type Error = Error;
type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
if self.author_id != actor.id {
return Err(Error::Unauthorized);
}
for m in Mention::list_for_comment(conn, self.id)? {
for n in Notification::find_for_mention(conn, &m)? {
n.delete(conn)?;
}
m.delete(conn)?;
}
for n in Notification::find_for_comment(conn, &self)? {
n.delete(&**conn)?;
}
diesel::update(comments::table)
.filter(comments::in_response_to_id.eq(self.id))
.set(comments::in_response_to_id.eq(self.in_response_to_id))
.execute(&**conn)?;
diesel::delete(&self).execute(&**conn)?;
Ok(())
}
}
impl AsObject07<User, Create07, &DbConn> for Comment {
type Error = Error;
type Output = Self;

View File

@ -12,7 +12,7 @@ use activitystreams::{
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use plume_common::activity_pub::{
broadcast, broadcast07,
inbox::{AsActor, AsObject, AsObject07, FromId},
inbox::{AsActor, AsObject07, FromId},
sign::Signer,
Id, IntoId, PUBLIC_VISIBILITY,
};
@ -242,22 +242,6 @@ impl Follow {
}
}
impl AsObject<User, FollowAct, &DbConn> for User {
type Error = Error;
type Output = Follow;
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Follow> {
// Mastodon (at least) requires the full Follow object when accepting it,
// so we rebuilt it here
let mut follow = FollowAct::default();
follow.object_props.set_id_string(id.to_string())?;
follow
.follow_props
.set_actor_link::<Id>(actor.clone().into_id())?;
Follow::accept_follow(conn, &actor, &self, follow, actor.id, self.id)
}
}
impl AsObject07<User, FollowAct07, &DbConn> for User {
type Error = Error;
type Output = Follow;
@ -312,27 +296,6 @@ impl FromId<DbConn> for Follow {
}
}
impl AsObject<User, Undo, &DbConn> for Follow {
type Error = Error;
type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
let conn = conn;
if self.follower_id == actor.id {
diesel::delete(&self).execute(&**conn)?;
// delete associated notification if any
if let Ok(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) {
diesel::delete(&notif).execute(&**conn)?;
}
Ok(())
} else {
Err(Error::Unauthorized)
}
}
}
impl AsObject07<User, Undo07, &DbConn> for Follow {
type Error = Error;
type Output = ();

View File

@ -12,7 +12,7 @@ use activitystreams::{
use chrono::NaiveDateTime;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use plume_common::activity_pub::{
inbox::{AsActor, AsObject, AsObject07, FromId},
inbox::{AsActor, AsObject07, FromId},
sign::Signer,
Id, IntoId, PUBLIC_VISIBILITY,
};
@ -118,26 +118,6 @@ impl Like {
}
}
impl AsObject<User, activity::Like, &DbConn> for Post {
type Error = Error;
type Output = Like;
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Like> {
let res = Like::insert(
conn,
NewLike {
post_id: self.id,
user_id: actor.id,
ap_url: id.to_string(),
},
)?;
res.notify(conn)?;
Timeline::add_to_all_timelines(conn, &self, Kind::Like(&actor))?;
Ok(res)
}
}
impl AsObject07<User, Like07, &DbConn> for Post {
type Error = Error;
type Output = Like;
@ -207,25 +187,6 @@ impl FromId<DbConn> for Like {
}
}
impl AsObject<User, activity::Undo, &DbConn> for Like {
type Error = Error;
type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
if actor.id == self.user_id {
diesel::delete(&self).execute(&**conn)?;
// delete associated notification if any
if let Ok(notif) = Notification::find(conn, notification_kind::LIKE, self.id) {
diesel::delete(&notif).execute(&**conn)?;
}
Ok(())
} else {
Err(Error::Unauthorized)
}
}
}
impl AsObject07<User, Undo07, &DbConn> for Like {
type Error = Error;
type Output = ();

View File

@ -26,7 +26,7 @@ use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl};
use once_cell::sync::Lazy;
use plume_common::{
activity_pub::{
inbox::{AsActor, AsObject, AsObject07, FromId},
inbox::{AsActor, AsObject07, FromId},
sign::Signer,
Hashtag, Hashtag07, HashtagType07, Id, IntoId, Licensed, Licensed07,
LicensedArticle as LicensedArticle07, Source, SourceProperty, ToAsString, ToAsUri,
@ -1054,16 +1054,6 @@ impl FromId<DbConn> for Post {
}
}
impl AsObject<User, Create, &DbConn> for Post {
type Error = Error;
type Output = Post;
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Post> {
// TODO: check that _actor is actually one of the author?
Ok(self)
}
}
impl AsObject07<User, Create07, &DbConn> for Post {
type Error = Error;
type Output = Self;
@ -1074,23 +1064,6 @@ impl AsObject07<User, Create07, &DbConn> for Post {
}
}
impl AsObject<User, Delete, &DbConn> for Post {
type Error = Error;
type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
let can_delete = self
.get_authors(conn)?
.into_iter()
.any(|a| actor.id == a.id);
if can_delete {
self.delete(conn).map(|_| ())
} else {
Err(Error::Unauthorized)
}
}
}
impl AsObject07<User, Delete07, &DbConn> for Post {
type Error = Error;
type Output = ();
@ -1176,78 +1149,6 @@ impl FromId<DbConn> for PostUpdate {
}
}
impl AsObject<User, Update, &DbConn> for PostUpdate {
type Error = Error;
type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
let mut post =
Post::from_id07(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?;
if !post.is_author(conn, actor.id)? {
// TODO: maybe the author was added in the meantime
return Err(Error::Unauthorized);
}
if let Some(title) = self.title {
post.slug = Post::slug(&title).to_string();
post.title = title;
}
if let Some(content) = self.content {
post.content = SafeString::new(&content);
}
if let Some(subtitle) = self.subtitle {
post.subtitle = subtitle;
}
post.cover_id = self.cover;
if let Some(source) = self.source {
post.source = source;
}
if let Some(license) = self.license {
post.license = license;
}
let mut txt_hashtags = md_to_html(&post.source, None, false, None)
.2
.into_iter()
.collect::<HashSet<_>>();
if let Some(serde_json::Value::Array(mention_tags)) = self.tags {
let mut mentions = vec![];
let mut tags = vec![];
let mut hashtags = vec![];
for tag in mention_tags {
serde_json::from_value::<link::Mention>(tag.clone())
.map(|m| mentions.push(m))
.ok();
serde_json::from_value::<Hashtag>(tag.clone())
.map_err(Error::from)
.and_then(|t| {
let tag_name = t.name_string()?;
if txt_hashtags.remove(&tag_name) {
hashtags.push(t);
} else {
tags.push(t);
}
Ok(())
})
.ok();
}
post.update_mentions(conn, mentions)?;
post.update_tags(conn, tags)?;
post.update_hashtags(conn, hashtags)?;
}
post.update(conn)?;
Ok(())
}
}
impl AsObject07<User, Update07, &DbConn> for PostUpdate {
type Error = Error;
type Output = ();

View File

@ -12,7 +12,7 @@ use activitystreams::{
use chrono::NaiveDateTime;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use plume_common::activity_pub::{
inbox::{AsActor, AsObject, AsObject07, FromId},
inbox::{AsActor, AsObject07, FromId},
sign::Signer,
Id, IntoId, PUBLIC_VISIBILITY,
};
@ -144,27 +144,6 @@ impl Reshare {
}
}
impl AsObject<User, Announce, &DbConn> for Post {
type Error = Error;
type Output = Reshare;
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Reshare> {
let conn = conn;
let reshare = Reshare::insert(
conn,
NewReshare {
post_id: self.id,
user_id: actor.id,
ap_url: id.to_string(),
},
)?;
reshare.notify(conn)?;
Timeline::add_to_all_timelines(conn, &self, Kind::Reshare(&actor))?;
Ok(reshare)
}
}
impl AsObject07<User, Announce07, &DbConn> for Post {
type Error = Error;
type Output = Reshare;
@ -235,26 +214,6 @@ impl FromId<DbConn> for Reshare {
}
}
impl AsObject<User, Undo, &DbConn> for Reshare {
type Error = Error;
type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
if actor.id == self.user_id {
diesel::delete(&self).execute(&**conn)?;
// delete associated notification if any
if let Ok(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) {
diesel::delete(&notif).execute(&**conn)?;
}
Ok(())
} else {
Err(Error::Unauthorized)
}
}
}
impl AsObject07<User, Undo07, &DbConn> for Reshare {
type Error = Error;
type Output = ();

View File

@ -35,7 +35,7 @@ use openssl::{
};
use plume_common::{
activity_pub::{
inbox::{AsActor, AsObject, AsObject07, FromId},
inbox::{AsActor, AsObject07, FromId},
request::get,
sign::{gen_keypair, Error as SignError, Result as SignResult, Signer},
ActivityStream, ApSignature, ApSignature07, CustomPerson as CustomPerson07, Id, IntoId,
@ -1248,19 +1248,6 @@ impl AsActor<&DbConn> for User {
}
}
impl AsObject<User, Delete, &DbConn> for User {
type Error = Error;
type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
if self.id == actor.id {
self.delete(conn).map(|_| ())
} else {
Err(Error::Unauthorized)
}
}
}
impl AsObject07<User, Delete07, &DbConn> for User {
type Error = Error;
type Output = ();