Rmove FromId
This commit is contained in:
		
							parent
							
								
									2165c286ae
								
							
						
					
					
						commit
						2804f44a06
					
				| @ -318,72 +318,6 @@ fn get_id(json: serde_json::Value) -> Option<String> { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// 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)> { | ||||
|         request::get(id, Self::get_sender(), proxy) | ||||
|             .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; | ||||
| } | ||||
| /// 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
 | ||||
| @ -808,22 +742,6 @@ mod tests { | ||||
|     } | ||||
| 
 | ||||
|     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 FromId07<()> for MyActor { | ||||
|         type Error = (); | ||||
|         type Object = Person07; | ||||
| @ -852,22 +770,6 @@ mod tests { | ||||
|     } | ||||
| 
 | ||||
|     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 = (); | ||||
| @ -1017,15 +919,6 @@ mod tests { | ||||
|         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_basic07() { | ||||
|         let act = serde_json::to_value(build_create07()).unwrap(); | ||||
| @ -1035,18 +928,6 @@ mod tests { | ||||
|         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_multi_handlers07() { | ||||
|         let act = serde_json::to_value(build_create()).unwrap(); | ||||
| @ -1059,17 +940,6 @@ mod tests { | ||||
|         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()); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_inbox_failure07() { | ||||
|         let act = serde_json::to_value(build_create07()).unwrap(); | ||||
| @ -1082,22 +952,6 @@ mod tests { | ||||
|     } | ||||
| 
 | ||||
|     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") | ||||
| @ -1155,22 +1009,6 @@ mod tests { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[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()); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_inbox_actor_failure07() { | ||||
|         let act = serde_json::to_value(build_create07()).unwrap(); | ||||
|  | ||||
| @ -27,11 +27,10 @@ use openssl::{ | ||||
|     sign::{Signer, Verifier}, | ||||
| }; | ||||
| use plume_common::activity_pub::{ | ||||
|     inbox::{AsActor, FromId, FromId07}, | ||||
|     inbox::{AsActor, FromId07}, | ||||
|     sign, ActivityStream, ApSignature, ApSignature07, CustomGroup as CustomGroup07, Id, IntoId, | ||||
|     PublicKey, PublicKey07, Source, SourceProperty, ToAsString, ToAsUri, | ||||
| }; | ||||
| use url::Url; | ||||
| use webfinger::*; | ||||
| 
 | ||||
| pub type CustomGroup = CustomObject<ApSignature, Group>; | ||||
| @ -161,7 +160,7 @@ impl Blog { | ||||
|             .find(|l| l.mime_type == Some(String::from("application/activity+json"))) | ||||
|             .ok_or(Error::Webfinger) | ||||
|             .and_then(|l| { | ||||
|                 Blog::from_id( | ||||
|                 Blog::from_id07( | ||||
|                     conn, | ||||
|                     &l.href.ok_or(Error::MissingApProperty)?, | ||||
|                     None, | ||||
| @ -471,110 +470,6 @@ impl IntoId for Blog { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId<DbConn> for Blog { | ||||
|     type Error = Error; | ||||
|     type Object = CustomGroup; | ||||
| 
 | ||||
|     fn from_db(conn: &DbConn, id: &str) -> Result<Self> { | ||||
|         Self::find_by_ap_url(conn, id) | ||||
|     } | ||||
| 
 | ||||
|     fn from_activity(conn: &DbConn, acct: CustomGroup) -> Result<Self> { | ||||
|         let url = Url::parse(&acct.object.object_props.id_string()?)?; | ||||
|         let inst = url.host_str().ok_or(Error::Url)?; | ||||
|         let instance = Instance::find_by_domain(conn, inst).or_else(|_| { | ||||
|             Instance::insert( | ||||
|                 conn, | ||||
|                 NewInstance { | ||||
|                     public_domain: inst.to_owned(), | ||||
|                     name: inst.to_owned(), | ||||
|                     local: false, | ||||
|                     // We don't really care about all the following for remote instances
 | ||||
|                     long_description: SafeString::new(""), | ||||
|                     short_description: SafeString::new(""), | ||||
|                     default_license: String::new(), | ||||
|                     open_registrations: true, | ||||
|                     short_description_html: String::new(), | ||||
|                     long_description_html: String::new(), | ||||
|                 }, | ||||
|             ) | ||||
|         })?; | ||||
|         let icon_id = acct | ||||
|             .object | ||||
|             .object_props | ||||
|             .icon_image() | ||||
|             .ok() | ||||
|             .and_then(|icon| { | ||||
|                 let owner = icon.object_props.attributed_to_link::<Id>().ok()?; | ||||
|                 Media::save_remote( | ||||
|                     conn, | ||||
|                     icon.object_props.url_string().ok()?, | ||||
|                     &User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?, | ||||
|                 ) | ||||
|                 .ok() | ||||
|             }) | ||||
|             .map(|m| m.id); | ||||
| 
 | ||||
|         let banner_id = acct | ||||
|             .object | ||||
|             .object_props | ||||
|             .image_image() | ||||
|             .ok() | ||||
|             .and_then(|banner| { | ||||
|                 let owner = banner.object_props.attributed_to_link::<Id>().ok()?; | ||||
|                 Media::save_remote( | ||||
|                     conn, | ||||
|                     banner.object_props.url_string().ok()?, | ||||
|                     &User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?, | ||||
|                 ) | ||||
|                 .ok() | ||||
|             }) | ||||
|             .map(|m| m.id); | ||||
| 
 | ||||
|         let name = acct.object.ap_actor_props.preferred_username_string()?; | ||||
|         if name.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) { | ||||
|             return Err(Error::InvalidValue); | ||||
|         } | ||||
| 
 | ||||
|         Blog::insert( | ||||
|             conn, | ||||
|             NewBlog { | ||||
|                 actor_id: name.clone(), | ||||
|                 title: acct.object.object_props.name_string().unwrap_or(name), | ||||
|                 outbox_url: acct.object.ap_actor_props.outbox_string()?, | ||||
|                 inbox_url: acct.object.ap_actor_props.inbox_string()?, | ||||
|                 summary: acct | ||||
|                     .object | ||||
|                     .ap_object_props | ||||
|                     .source_object::<Source>() | ||||
|                     .map(|s| s.content) | ||||
|                     .unwrap_or_default(), | ||||
|                 instance_id: instance.id, | ||||
|                 ap_url: acct.object.object_props.id_string()?, | ||||
|                 public_key: acct | ||||
|                     .custom_props | ||||
|                     .public_key_publickey()? | ||||
|                     .public_key_pem_string()?, | ||||
|                 private_key: None, | ||||
|                 banner_id, | ||||
|                 icon_id, | ||||
|                 summary_html: SafeString::new( | ||||
|                     &acct | ||||
|                         .object | ||||
|                         .object_props | ||||
|                         .summary_string() | ||||
|                         .unwrap_or_default(), | ||||
|                 ), | ||||
|                 theme: None, | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     fn get_sender() -> &'static dyn sign::Signer { | ||||
|         Instance::get_local_instance_user().expect("Failed to local instance user") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId07<DbConn> for Blog { | ||||
|     type Error = Error; | ||||
|     type Object = CustomGroup07; | ||||
| @ -648,7 +543,7 @@ impl FromId07<DbConn> for Blog { | ||||
|                     Media::save_remote( | ||||
|                         conn, | ||||
|                         banner.url()?.to_as_uri()?, | ||||
|                         &User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?, | ||||
|                         &User::from_id07(conn, &owner, None, CONFIG.proxy()).ok()?, | ||||
|                     ) | ||||
|                     .ok() | ||||
|                 }) | ||||
| @ -1130,33 +1025,6 @@ pub(crate) mod tests { | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn self_federation() { | ||||
|         let conn = &db(); | ||||
|         conn.test_transaction::<_, (), _>(|| { | ||||
|             let (_users, blogs) = fill_database(&conn); | ||||
| 
 | ||||
|             let ap_repr = blogs[0].to_activity(&conn).unwrap(); | ||||
|             blogs[0].delete(&conn).unwrap(); | ||||
|             let blog = Blog::from_activity(&conn, ap_repr).unwrap(); | ||||
| 
 | ||||
|             assert_eq!(blog.actor_id, blogs[0].actor_id); | ||||
|             assert_eq!(blog.title, blogs[0].title); | ||||
|             assert_eq!(blog.summary, blogs[0].summary); | ||||
|             assert_eq!(blog.outbox_url, blogs[0].outbox_url); | ||||
|             assert_eq!(blog.inbox_url, blogs[0].inbox_url); | ||||
|             assert_eq!(blog.instance_id, blogs[0].instance_id); | ||||
|             assert_eq!(blog.ap_url, blogs[0].ap_url); | ||||
|             assert_eq!(blog.public_key, blogs[0].public_key); | ||||
|             assert_eq!(blog.fqn, blogs[0].fqn); | ||||
|             assert_eq!(blog.summary_html, blogs[0].summary_html); | ||||
|             assert_eq!(blog.icon_url(&conn), blogs[0].icon_url(&conn)); | ||||
|             assert_eq!(blog.banner_url(&conn), blogs[0].banner_url(&conn)); | ||||
| 
 | ||||
|             Ok(()) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn self_federation07() { | ||||
|         let conn = &db(); | ||||
|  | ||||
| @ -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, FromId07}, | ||||
|         inbox::{AsActor, AsObject, AsObject07, FromId07}, | ||||
|         sign::Signer, | ||||
|         Id, IntoId, ToAsString, ToAsUri, PUBLIC_VISIBILITY, | ||||
|     }, | ||||
| @ -294,141 +294,6 @@ impl Comment { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId<DbConn> for Comment { | ||||
|     type Error = Error; | ||||
|     type Object = Note; | ||||
| 
 | ||||
|     fn from_db(conn: &DbConn, id: &str) -> Result<Self> { | ||||
|         Self::find_by_ap_url(conn, id) | ||||
|     } | ||||
| 
 | ||||
|     fn from_activity(conn: &DbConn, note: Note) -> Result<Self> { | ||||
|         let comm = { | ||||
|             let previous_url = note | ||||
|                 .object_props | ||||
|                 .in_reply_to | ||||
|                 .as_ref() | ||||
|                 .ok_or(Error::MissingApProperty)? | ||||
|                 .as_str() | ||||
|                 .ok_or(Error::MissingApProperty)?; | ||||
|             let previous_comment = Comment::find_by_ap_url(conn, previous_url); | ||||
| 
 | ||||
|             let is_public = |v: &Option<serde_json::Value>| match v | ||||
|                 .as_ref() | ||||
|                 .unwrap_or(&serde_json::Value::Null) | ||||
|             { | ||||
|                 serde_json::Value::Array(v) => v | ||||
|                     .iter() | ||||
|                     .filter_map(serde_json::Value::as_str) | ||||
|                     .any(|s| s == PUBLIC_VISIBILITY), | ||||
|                 serde_json::Value::String(s) => s == PUBLIC_VISIBILITY, | ||||
|                 _ => false, | ||||
|             }; | ||||
| 
 | ||||
|             let public_visibility = is_public(¬e.object_props.to) | ||||
|                 || is_public(¬e.object_props.bto) | ||||
|                 || is_public(¬e.object_props.cc) | ||||
|                 || is_public(¬e.object_props.bcc); | ||||
| 
 | ||||
|             let comm = Comment::insert( | ||||
|                 conn, | ||||
|                 NewComment { | ||||
|                     content: SafeString::new(¬e.object_props.content_string()?), | ||||
|                     spoiler_text: note.object_props.summary_string().unwrap_or_default(), | ||||
|                     ap_url: note.object_props.id_string().ok(), | ||||
|                     in_response_to_id: previous_comment.iter().map(|c| c.id).next(), | ||||
|                     post_id: previous_comment.map(|c| c.post_id).or_else(|_| { | ||||
|                         Ok(Post::find_by_ap_url(conn, previous_url)?.id) as Result<i32> | ||||
|                     })?, | ||||
|                     author_id: User::from_id( | ||||
|                         conn, | ||||
|                         ¬e.object_props.attributed_to_link::<Id>()?, | ||||
|                         None, | ||||
|                         CONFIG.proxy(), | ||||
|                     ) | ||||
|                     .map_err(|(_, e)| e)? | ||||
|                     .id, | ||||
|                     sensitive: note.object_props.summary_string().is_ok(), | ||||
|                     public_visibility, | ||||
|                 }, | ||||
|             )?; | ||||
| 
 | ||||
|             // save mentions
 | ||||
|             if let Some(serde_json::Value::Array(tags)) = note.object_props.tag.clone() { | ||||
|                 for tag in tags { | ||||
|                     serde_json::from_value::<link::Mention>(tag) | ||||
|                         .map_err(Error::from) | ||||
|                         .and_then(|m| { | ||||
|                             let author = &Post::get(conn, comm.post_id)?.get_authors(conn)?[0]; | ||||
|                             let not_author = m.link_props.href_string()? != author.ap_url.clone(); | ||||
|                             Mention::from_activity(conn, &m, comm.id, false, not_author) | ||||
|                         }) | ||||
|                         .ok(); | ||||
|                 } | ||||
|             } | ||||
|             comm | ||||
|         }; | ||||
| 
 | ||||
|         if !comm.public_visibility { | ||||
|             let receivers_ap_url = |v: Option<serde_json::Value>| { | ||||
|                 let filter = |e: serde_json::Value| { | ||||
|                     if let serde_json::Value::String(s) = e { | ||||
|                         Some(s) | ||||
|                     } else { | ||||
|                         None | ||||
|                     } | ||||
|                 }; | ||||
|                 match v.unwrap_or(serde_json::Value::Null) { | ||||
|                     serde_json::Value::Array(v) => v, | ||||
|                     v => vec![v], | ||||
|                 } | ||||
|                 .into_iter() | ||||
|                 .filter_map(filter) | ||||
|             }; | ||||
| 
 | ||||
|             let mut note = note; | ||||
| 
 | ||||
|             let to = receivers_ap_url(note.object_props.to.take()); | ||||
|             let cc = receivers_ap_url(note.object_props.cc.take()); | ||||
|             let bto = receivers_ap_url(note.object_props.bto.take()); | ||||
|             let bcc = receivers_ap_url(note.object_props.bcc.take()); | ||||
| 
 | ||||
|             let receivers_ap_url = to | ||||
|                 .chain(cc) | ||||
|                 .chain(bto) | ||||
|                 .chain(bcc) | ||||
|                 .collect::<HashSet<_>>() // remove duplicates (don't do a query more than once)
 | ||||
|                 .into_iter() | ||||
|                 .flat_map(|v| { | ||||
|                     if let Ok(user) = User::from_id(conn, &v, None, CONFIG.proxy()) { | ||||
|                         vec![user] | ||||
|                     } else { | ||||
|                         vec![] // TODO try to fetch collection
 | ||||
|                     } | ||||
|                 }) | ||||
|                 .filter(|u| u.get_instance(conn).map(|i| i.local).unwrap_or(false)) | ||||
|                 .collect::<HashSet<User>>(); //remove duplicates (prevent db error)
 | ||||
| 
 | ||||
|             for user in &receivers_ap_url { | ||||
|                 CommentSeers::insert( | ||||
|                     conn, | ||||
|                     NewCommentSeers { | ||||
|                         comment_id: comm.id, | ||||
|                         user_id: user.id, | ||||
|                     }, | ||||
|                 )?; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         comm.notify(conn)?; | ||||
|         Ok(comm) | ||||
|     } | ||||
| 
 | ||||
|     fn get_sender() -> &'static dyn Signer { | ||||
|         Instance::get_local_instance_user().expect("Failed to local instance user") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId07<DbConn> for Comment { | ||||
|     type Error = Error; | ||||
|     type Object = Note07; | ||||
| @ -484,7 +349,7 @@ impl FromId07<DbConn> for Comment { | ||||
|                     post_id: previous_comment.map(|c| c.post_id).or_else(|_| { | ||||
|                         Ok(Post::find_by_ap_url(conn, previous_url.as_str())?.id) as Result<i32> | ||||
|                     })?, | ||||
|                     author_id: User::from_id( | ||||
|                     author_id: User::from_id07( | ||||
|                         conn, | ||||
|                         ¬e | ||||
|                             .attributed_to() | ||||
| @ -537,7 +402,7 @@ impl FromId07<DbConn> for Comment { | ||||
|             let receivers_ap_url = receiver_ids | ||||
|                 .into_iter() | ||||
|                 .flat_map(|v| { | ||||
|                     if let Ok(user) = User::from_id(conn, v.as_ref(), None, CONFIG.proxy()) { | ||||
|                     if let Ok(user) = User::from_id07(conn, v.as_ref(), None, CONFIG.proxy()) { | ||||
|                         vec![user] | ||||
|                     } else { | ||||
|                         vec![] // TODO try to fetch collection
 | ||||
|  | ||||
| @ -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, FromId07}, | ||||
|     inbox::{AsActor, AsObject, AsObject07, FromId07}, | ||||
|     sign::Signer, | ||||
|     Id, IntoId, PUBLIC_VISIBILITY, | ||||
| }; | ||||
| @ -272,38 +272,6 @@ impl AsObject07<User, FollowAct07, &DbConn> for User { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId<DbConn> for Follow { | ||||
|     type Error = Error; | ||||
|     type Object = FollowAct; | ||||
| 
 | ||||
|     fn from_db(conn: &DbConn, id: &str) -> Result<Self> { | ||||
|         Follow::find_by_ap_url(conn, id) | ||||
|     } | ||||
| 
 | ||||
|     fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> { | ||||
|         let actor = User::from_id( | ||||
|             conn, | ||||
|             &follow.follow_props.actor_link::<Id>()?, | ||||
|             None, | ||||
|             CONFIG.proxy(), | ||||
|         ) | ||||
|         .map_err(|(_, e)| e)?; | ||||
| 
 | ||||
|         let target = User::from_id( | ||||
|             conn, | ||||
|             &follow.follow_props.object_link::<Id>()?, | ||||
|             None, | ||||
|             CONFIG.proxy(), | ||||
|         ) | ||||
|         .map_err(|(_, e)| e)?; | ||||
|         Follow::accept_follow(conn, &actor, &target, follow, actor.id, target.id) | ||||
|     } | ||||
| 
 | ||||
|     fn get_sender() -> &'static dyn Signer { | ||||
|         Instance::get_local_instance_user().expect("Failed to local instance user") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId07<DbConn> for Follow { | ||||
|     type Error = Error; | ||||
|     type Object = FollowAct07; | ||||
|  | ||||
| @ -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, FromId07}, | ||||
|     inbox::{AsActor, AsObject, AsObject07, FromId07}, | ||||
|     sign::Signer, | ||||
|     Id, IntoId, PUBLIC_VISIBILITY, | ||||
| }; | ||||
| @ -158,46 +158,6 @@ impl AsObject07<User, Like07, &DbConn> for Post { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId<DbConn> for Like { | ||||
|     type Error = Error; | ||||
|     type Object = activity::Like; | ||||
| 
 | ||||
|     fn from_db(conn: &DbConn, id: &str) -> Result<Self> { | ||||
|         Like::find_by_ap_url(conn, id) | ||||
|     } | ||||
| 
 | ||||
|     fn from_activity(conn: &DbConn, act: activity::Like) -> Result<Self> { | ||||
|         let res = Like::insert( | ||||
|             conn, | ||||
|             NewLike { | ||||
|                 post_id: Post::from_id( | ||||
|                     conn, | ||||
|                     &act.like_props.object_link::<Id>()?, | ||||
|                     None, | ||||
|                     CONFIG.proxy(), | ||||
|                 ) | ||||
|                 .map_err(|(_, e)| e)? | ||||
|                 .id, | ||||
|                 user_id: User::from_id( | ||||
|                     conn, | ||||
|                     &act.like_props.actor_link::<Id>()?, | ||||
|                     None, | ||||
|                     CONFIG.proxy(), | ||||
|                 ) | ||||
|                 .map_err(|(_, e)| e)? | ||||
|                 .id, | ||||
|                 ap_url: act.object_props.id_string()?, | ||||
|             }, | ||||
|         )?; | ||||
|         res.notify(conn)?; | ||||
|         Ok(res) | ||||
|     } | ||||
| 
 | ||||
|     fn get_sender() -> &'static dyn Signer { | ||||
|         Instance::get_local_instance_user().expect("Failed to local instance user") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId07<DbConn> for Like { | ||||
|     type Error = Error; | ||||
|     type Object = Like07; | ||||
| @ -210,7 +170,7 @@ impl FromId07<DbConn> for Like { | ||||
|         let res = Like::insert( | ||||
|             conn, | ||||
|             NewLike { | ||||
|                 post_id: Post::from_id( | ||||
|                 post_id: Post::from_id07( | ||||
|                     conn, | ||||
|                     act.object_field_ref() | ||||
|                         .as_single_id() | ||||
|  | ||||
| @ -7,7 +7,7 @@ use activitystreams::{object::Image as Image07, prelude::*}; | ||||
| use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; | ||||
| use guid_create::GUID; | ||||
| use plume_common::{ | ||||
|     activity_pub::{inbox::FromId, request, Id, ToAsString, ToAsUri}, | ||||
|     activity_pub::{inbox::FromId07, request, Id, ToAsString, ToAsUri}, | ||||
|     utils::{escape, MediaProcessor}, | ||||
| }; | ||||
| use std::{ | ||||
| @ -222,7 +222,7 @@ impl Media { | ||||
|         // TODO: conditional GET
 | ||||
|         request::get( | ||||
|             remote_url.as_str(), | ||||
|             User::get_sender(), | ||||
|             User::get_sender07(), | ||||
|             CONFIG.proxy().cloned(), | ||||
|         )? | ||||
|         .copy_to(&mut dest)?; | ||||
| @ -275,7 +275,7 @@ impl Media { | ||||
|                         remote_url: None, | ||||
|                         sensitive: image.object_props.summary_string().is_ok(), | ||||
|                         content_warning: image.object_props.summary_string().ok(), | ||||
|                         owner_id: User::from_id( | ||||
|                         owner_id: User::from_id07( | ||||
|                             conn, | ||||
|                             image | ||||
|                                 .object_props | ||||
| @ -311,7 +311,7 @@ impl Media { | ||||
|         // TODO: conditional GET
 | ||||
|         request::get( | ||||
|             remote_url.as_str(), | ||||
|             User::get_sender(), | ||||
|             User::get_sender07(), | ||||
|             CONFIG.proxy().cloned(), | ||||
|         )? | ||||
|         .copy_to(&mut dest)?; | ||||
| @ -362,7 +362,7 @@ impl Media { | ||||
|                         remote_url: None, | ||||
|                         sensitive: summary.is_some(), | ||||
|                         content_warning: summary, | ||||
|                         owner_id: User::from_id( | ||||
|                         owner_id: User::from_id07( | ||||
|                             conn, | ||||
|                             &image | ||||
|                                 .attributed_to() | ||||
|  | ||||
| @ -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, FromId07}, | ||||
|         inbox::{AsActor, AsObject, AsObject07, FromId07}, | ||||
|         sign::Signer, | ||||
|         Hashtag, Hashtag07, HashtagType07, Id, IntoId, Licensed, Licensed07, | ||||
|         LicensedArticle as LicensedArticle07, Source, SourceProperty, ToAsString, ToAsUri, | ||||
| @ -863,160 +863,6 @@ impl Post { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId<DbConn> for Post { | ||||
|     type Error = Error; | ||||
|     type Object = LicensedArticle; | ||||
| 
 | ||||
|     fn from_db(conn: &DbConn, id: &str) -> Result<Self> { | ||||
|         Self::find_by_ap_url(conn, id) | ||||
|     } | ||||
| 
 | ||||
|     fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> { | ||||
|         let conn = conn; | ||||
|         let license = article.custom_props.license_string().unwrap_or_default(); | ||||
|         let article = article.object; | ||||
| 
 | ||||
|         let (blog, authors) = article | ||||
|             .object_props | ||||
|             .attributed_to_link_vec::<Id>()? | ||||
|             .into_iter() | ||||
|             .fold((None, vec![]), |(blog, mut authors), link| { | ||||
|                 let url = link; | ||||
|                 match User::from_id(conn, &url, None, CONFIG.proxy()) { | ||||
|                     Ok(u) => { | ||||
|                         authors.push(u); | ||||
|                         (blog, authors) | ||||
|                     } | ||||
|                     Err(_) => ( | ||||
|                         blog.or_else(|| Blog::from_id(conn, &url, None, CONFIG.proxy()).ok()), | ||||
|                         authors, | ||||
|                     ), | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|         let cover = article | ||||
|             .object_props | ||||
|             .icon_object::<Image>() | ||||
|             .ok() | ||||
|             .and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id)); | ||||
| 
 | ||||
|         let title = article.object_props.name_string()?; | ||||
|         let ap_url = article | ||||
|             .object_props | ||||
|             .url_string() | ||||
|             .or_else(|_| article.object_props.id_string())?; | ||||
|         let post = Post::from_db(conn, &ap_url) | ||||
|             .and_then(|mut post| { | ||||
|                 let mut updated = false; | ||||
| 
 | ||||
|                 let slug = Self::slug(&title); | ||||
|                 let content = SafeString::new(&article.object_props.content_string()?); | ||||
|                 let subtitle = article.object_props.summary_string()?; | ||||
|                 let source = article.ap_object_props.source_object::<Source>()?.content; | ||||
|                 if post.slug != slug { | ||||
|                     post.slug = slug.to_string(); | ||||
|                     updated = true; | ||||
|                 } | ||||
|                 if post.title != title { | ||||
|                     post.title = title.clone(); | ||||
|                     updated = true; | ||||
|                 } | ||||
|                 if post.content != content { | ||||
|                     post.content = content; | ||||
|                     updated = true; | ||||
|                 } | ||||
|                 if post.license != license { | ||||
|                     post.license = license.clone(); | ||||
|                     updated = true; | ||||
|                 } | ||||
|                 if post.subtitle != subtitle { | ||||
|                     post.subtitle = subtitle; | ||||
|                     updated = true; | ||||
|                 } | ||||
|                 if post.source != source { | ||||
|                     post.source = source; | ||||
|                     updated = true; | ||||
|                 } | ||||
|                 if post.cover_id != cover { | ||||
|                     post.cover_id = cover; | ||||
|                     updated = true; | ||||
|                 } | ||||
| 
 | ||||
|                 if updated { | ||||
|                     post.update(conn)?; | ||||
|                 } | ||||
| 
 | ||||
|                 Ok(post) | ||||
|             }) | ||||
|             .or_else(|_| { | ||||
|                 Post::insert( | ||||
|                     conn, | ||||
|                     NewPost { | ||||
|                         blog_id: blog.ok_or(Error::NotFound)?.id, | ||||
|                         slug: Self::slug(&title).to_string(), | ||||
|                         title, | ||||
|                         content: SafeString::new(&article.object_props.content_string()?), | ||||
|                         published: true, | ||||
|                         license, | ||||
|                         // FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields
 | ||||
|                         ap_url, | ||||
|                         creation_date: Some(article.object_props.published_utctime()?.naive_utc()), | ||||
|                         subtitle: article.object_props.summary_string()?, | ||||
|                         source: article.ap_object_props.source_object::<Source>()?.content, | ||||
|                         cover_id: cover, | ||||
|                     }, | ||||
|                 ) | ||||
|                 .and_then(|post| { | ||||
|                     for author in authors { | ||||
|                         PostAuthor::insert( | ||||
|                             conn, | ||||
|                             NewPostAuthor { | ||||
|                                 post_id: post.id, | ||||
|                                 author_id: author.id, | ||||
|                             }, | ||||
|                         )?; | ||||
|                     } | ||||
| 
 | ||||
|                     Ok(post) | ||||
|                 }) | ||||
|             })?; | ||||
| 
 | ||||
|         // save mentions and tags
 | ||||
|         let mut hashtags = md_to_html(&post.source, None, false, None) | ||||
|             .2 | ||||
|             .into_iter() | ||||
|             .collect::<HashSet<_>>(); | ||||
|         if let Some(serde_json::Value::Array(tags)) = article.object_props.tag { | ||||
|             for tag in tags { | ||||
|                 serde_json::from_value::<link::Mention>(tag.clone()) | ||||
|                     .map(|m| Mention::from_activity(conn, &m, post.id, true, true)) | ||||
|                     .ok(); | ||||
| 
 | ||||
|                 serde_json::from_value::<Hashtag>(tag.clone()) | ||||
|                     .map_err(Error::from) | ||||
|                     .and_then(|t| { | ||||
|                         let tag_name = t.name_string()?; | ||||
|                         Ok(Tag::from_activity( | ||||
|                             conn, | ||||
|                             &t, | ||||
|                             post.id, | ||||
|                             hashtags.remove(&tag_name), | ||||
|                         )) | ||||
|                     }) | ||||
|                     .ok(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         Timeline::add_to_all_timelines(conn, &post, Kind::Original)?; | ||||
| 
 | ||||
|         Ok(post) | ||||
|     } | ||||
| 
 | ||||
|     fn get_sender() -> &'static dyn Signer { | ||||
|         Instance::get_local_instance_user().expect("Failed to local instance user") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId07<DbConn> for Post { | ||||
|     type Error = Error; | ||||
|     type Object = LicensedArticle07; | ||||
| @ -1273,43 +1119,6 @@ pub struct PostUpdate { | ||||
|     pub tags: Option<serde_json::Value>, | ||||
| } | ||||
| 
 | ||||
| impl FromId<DbConn> for PostUpdate { | ||||
|     type Error = Error; | ||||
|     type Object = LicensedArticle; | ||||
| 
 | ||||
|     fn from_db(_: &DbConn, _: &str) -> Result<Self> { | ||||
|         // Always fail because we always want to deserialize the AP object
 | ||||
|         Err(Error::NotFound) | ||||
|     } | ||||
| 
 | ||||
|     fn from_activity(conn: &DbConn, updated: LicensedArticle) -> Result<Self> { | ||||
|         Ok(PostUpdate { | ||||
|             ap_url: updated.object.object_props.id_string()?, | ||||
|             title: updated.object.object_props.name_string().ok(), | ||||
|             subtitle: updated.object.object_props.summary_string().ok(), | ||||
|             content: updated.object.object_props.content_string().ok(), | ||||
|             cover: updated | ||||
|                 .object | ||||
|                 .object_props | ||||
|                 .icon_object::<Image>() | ||||
|                 .ok() | ||||
|                 .and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id)), | ||||
|             source: updated | ||||
|                 .object | ||||
|                 .ap_object_props | ||||
|                 .source_object::<Source>() | ||||
|                 .ok() | ||||
|                 .map(|x| x.content), | ||||
|             license: updated.custom_props.license_string().ok(), | ||||
|             tags: updated.object.object_props.tag, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     fn get_sender() -> &'static dyn Signer { | ||||
|         Instance::get_local_instance_user().expect("Failed to local instance user") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId07<DbConn> for PostUpdate { | ||||
|     type Error = Error; | ||||
|     type Object = LicensedArticle07; | ||||
| @ -1373,7 +1182,7 @@ impl AsObject<User, Update, &DbConn> for PostUpdate { | ||||
| 
 | ||||
|     fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { | ||||
|         let mut post = | ||||
|             Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?; | ||||
|             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
 | ||||
| @ -1445,7 +1254,7 @@ impl AsObject07<User, Update07, &DbConn> for PostUpdate { | ||||
| 
 | ||||
|     fn activity07(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { | ||||
|         let mut post = | ||||
|             Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?; | ||||
|             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
 | ||||
|  | ||||
| @ -1,12 +1,16 @@ | ||||
| use crate::{ | ||||
|     db_conn::{DbConn, DbPool}, | ||||
|     follows, | ||||
|     posts::{LicensedArticle, Post}, | ||||
|     posts::Post, | ||||
|     users::{User, UserEvent}, | ||||
|     ACTOR_SYS, CONFIG, USER_CHAN, | ||||
| }; | ||||
| use activitypub::activity::Create; | ||||
| use plume_common::activity_pub::inbox::FromId; | ||||
| use activitystreams::{ | ||||
|     activity::{ActorAndObjectRef, Create as Create07}, | ||||
|     base::AnyBase, | ||||
|     object::kind::ArticleType, | ||||
| }; | ||||
| use plume_common::activity_pub::{inbox::FromId07, LicensedArticle as LicensedArticle07}; | ||||
| use riker::actors::{Actor, ActorFactoryArgs, ActorRefFactory, Context, Sender, Subscribe, Tell}; | ||||
| use std::sync::Arc; | ||||
| use tracing::{error, info, warn}; | ||||
| @ -64,17 +68,23 @@ impl ActorFactoryArgs<DbPool> for RemoteFetchActor { | ||||
| } | ||||
| 
 | ||||
| fn fetch_and_cache_articles(user: &Arc<User>, conn: &DbConn) { | ||||
|     let create_acts = user.fetch_outbox::<Create>(); | ||||
|     let create_acts = user.fetch_outbox07::<Create07>(); | ||||
|     match create_acts { | ||||
|         Ok(create_acts) => { | ||||
|             for create_act in create_acts { | ||||
|                 match create_act.create_props.object_object::<LicensedArticle>() { | ||||
|                     Ok(article) => { | ||||
|                         Post::from_activity(conn, article) | ||||
|                 match create_act | ||||
|                     .object_field_ref() | ||||
|                     .as_single_base() | ||||
|                     .and_then(|base| { | ||||
|                         let any_base = AnyBase::from_base(base.clone()); // FIXME: Don't clone()
 | ||||
|                         any_base.extend::<LicensedArticle07, ArticleType>().ok() | ||||
|                     }) { | ||||
|                     Some(Some(article)) => { | ||||
|                         Post::from_activity07(conn, article) | ||||
|                             .expect("Article from remote user couldn't be saved"); | ||||
|                         info!("Fetched article from remote user"); | ||||
|                     } | ||||
|                     Err(e) => warn!("Error while fetching articles in background: {:?}", e), | ||||
|                     _ => warn!("Error while fetching articles in background"), | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @ -89,7 +99,7 @@ fn fetch_and_cache_followers(user: &Arc<User>, conn: &DbConn) { | ||||
|     match follower_ids { | ||||
|         Ok(user_ids) => { | ||||
|             for user_id in user_ids { | ||||
|                 let follower = User::from_id(conn, &user_id, None, CONFIG.proxy()); | ||||
|                 let follower = User::from_id07(conn, &user_id, None, CONFIG.proxy()); | ||||
|                 match follower { | ||||
|                     Ok(follower) => { | ||||
|                         let inserted = follows::Follow::insert( | ||||
|  | ||||
| @ -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, FromId07}, | ||||
|     inbox::{AsActor, AsObject, AsObject07, FromId07}, | ||||
|     sign::Signer, | ||||
|     Id, IntoId, PUBLIC_VISIBILITY, | ||||
| }; | ||||
| @ -186,46 +186,6 @@ impl AsObject07<User, Announce07, &DbConn> for Post { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId<DbConn> for Reshare { | ||||
|     type Error = Error; | ||||
|     type Object = Announce; | ||||
| 
 | ||||
|     fn from_db(conn: &DbConn, id: &str) -> Result<Self> { | ||||
|         Reshare::find_by_ap_url(conn, id) | ||||
|     } | ||||
| 
 | ||||
|     fn from_activity(conn: &DbConn, act: Announce) -> Result<Self> { | ||||
|         let res = Reshare::insert( | ||||
|             conn, | ||||
|             NewReshare { | ||||
|                 post_id: Post::from_id( | ||||
|                     conn, | ||||
|                     &act.announce_props.object_link::<Id>()?, | ||||
|                     None, | ||||
|                     CONFIG.proxy(), | ||||
|                 ) | ||||
|                 .map_err(|(_, e)| e)? | ||||
|                 .id, | ||||
|                 user_id: User::from_id( | ||||
|                     conn, | ||||
|                     &act.announce_props.actor_link::<Id>()?, | ||||
|                     None, | ||||
|                     CONFIG.proxy(), | ||||
|                 ) | ||||
|                 .map_err(|(_, e)| e)? | ||||
|                 .id, | ||||
|                 ap_url: act.object_props.id_string()?, | ||||
|             }, | ||||
|         )?; | ||||
|         res.notify(conn)?; | ||||
|         Ok(res) | ||||
|     } | ||||
| 
 | ||||
|     fn get_sender() -> &'static dyn Signer { | ||||
|         Instance::get_local_instance_user().expect("Failed to local instance user") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId07<DbConn> for Reshare { | ||||
|     type Error = Error; | ||||
|     type Object = Announce07; | ||||
| @ -238,7 +198,7 @@ impl FromId07<DbConn> for Reshare { | ||||
|         let res = Reshare::insert( | ||||
|             conn, | ||||
|             NewReshare { | ||||
|                 post_id: Post::from_id( | ||||
|                 post_id: Post::from_id07( | ||||
|                     conn, | ||||
|                     act.object_field_ref() | ||||
|                         .as_single_id() | ||||
| @ -249,7 +209,7 @@ impl FromId07<DbConn> for Reshare { | ||||
|                 ) | ||||
|                 .map_err(|(_, e)| e)? | ||||
|                 .id, | ||||
|                 user_id: User::from_id( | ||||
|                 user_id: User::from_id07( | ||||
|                     conn, | ||||
|                     act.actor_field_ref() | ||||
|                         .as_single_id() | ||||
|  | ||||
| @ -13,7 +13,7 @@ use activitypub::{ | ||||
| }; | ||||
| use activitystreams::{ | ||||
|     activity::Delete as Delete07, | ||||
|     actor::AsApActor, | ||||
|     actor::{ApActor, AsApActor}, | ||||
|     actor::{ApActor as ApActor07, Endpoints as Endpoints07, Person as Person07}, | ||||
|     base::{AnyBase, Base}, | ||||
|     collection::{ | ||||
| @ -21,7 +21,7 @@ use activitystreams::{ | ||||
|     }, | ||||
|     iri_string::types::IriString, | ||||
|     markers::Activity as Activity07, | ||||
|     object::{AsObject as _, Image as Image07, Tombstone as Tombstone07}, | ||||
|     object::{kind::ImageType, AsObject as _, Image as Image07, Tombstone as Tombstone07}, | ||||
|     prelude::*, | ||||
| }; | ||||
| use chrono::{NaiveDateTime, Utc}; | ||||
| @ -35,7 +35,7 @@ use openssl::{ | ||||
| }; | ||||
| use plume_common::{ | ||||
|     activity_pub::{ | ||||
|         inbox::{AsActor, AsObject, AsObject07, FromId, FromId07}, | ||||
|         inbox::{AsActor, AsObject, AsObject07, FromId07}, | ||||
|         request::get, | ||||
|         sign::{gen_keypair, Error as SignError, Result as SignResult, Signer}, | ||||
|         ActivityStream, ApSignature, ApSignature07, CustomPerson as CustomPerson07, Id, IntoId, | ||||
| @ -53,7 +53,6 @@ use std::{ | ||||
|     hash::{Hash, Hasher}, | ||||
|     sync::Arc, | ||||
| }; | ||||
| use url::Url; | ||||
| use webfinger::*; | ||||
| 
 | ||||
| pub type CustomPerson = CustomObject<ApSignature, Person>; | ||||
| @ -238,7 +237,7 @@ impl User { | ||||
|             .into_iter() | ||||
|             .find(|l| l.mime_type == Some(String::from("application/activity+json"))) | ||||
|             .ok_or(Error::Webfinger)?; | ||||
|         User::from_id( | ||||
|         User::from_id07( | ||||
|             conn, | ||||
|             link.href.as_ref().ok_or(Error::Webfinger)?, | ||||
|             None, | ||||
| @ -256,53 +255,84 @@ impl User { | ||||
|             .ok_or(Error::Webfinger) | ||||
|     } | ||||
| 
 | ||||
|     fn fetch(url: &str) -> Result<CustomPerson> { | ||||
|         let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?; | ||||
|     fn fetch(url: &str) -> Result<CustomPerson07> { | ||||
|         let mut res = get(url, Self::get_sender07(), CONFIG.proxy().cloned())?; | ||||
|         let text = &res.text()?; | ||||
|         // without this workaround, publicKey is not correctly deserialized
 | ||||
|         let ap_sign = serde_json::from_str::<ApSignature>(text)?; | ||||
|         let mut json = serde_json::from_str::<CustomPerson>(text)?; | ||||
|         json.custom_props = ap_sign; | ||||
|         let ap_sign = serde_json::from_str::<ApSignature07>(text)?; | ||||
|         let person = serde_json::from_str::<Person07>(text)?; | ||||
|         let json = CustomPerson07::new( | ||||
|             ApActor::new( | ||||
|                 person | ||||
|                     .clone() | ||||
|                     .id_unchecked() | ||||
|                     .ok_or(Error::MissingApProperty)? | ||||
|                     .to_owned(), | ||||
|                 person, | ||||
|             ), | ||||
|             ap_sign, | ||||
|         ); // FIXME: Don't clone()
 | ||||
|         Ok(json) | ||||
|     } | ||||
| 
 | ||||
|     pub fn fetch_from_url(conn: &DbConn, url: &str) -> Result<User> { | ||||
|         User::fetch(url).and_then(|json| User::from_activity(conn, json)) | ||||
|         User::fetch(url).and_then(|json| User::from_activity07(conn, json)) | ||||
|     } | ||||
| 
 | ||||
|     pub fn refetch(&self, conn: &Connection) -> Result<()> { | ||||
|         User::fetch(&self.ap_url.clone()).and_then(|json| { | ||||
|             let avatar = Media::save_remote( | ||||
|                 conn, | ||||
|                 json.object | ||||
|                     .object_props | ||||
|                     .icon_image()? // FIXME: Fails when icon is not set
 | ||||
|                     .object_props | ||||
|                     .url_string()?, | ||||
|                 json.ap_actor_ref() | ||||
|                     .icon() | ||||
|                     .ok_or(Error::MissingApProperty)? // FIXME: Fails when icon is not set
 | ||||
|                     .iter() | ||||
|                     .next() | ||||
|                     .and_then(|i| { | ||||
|                         i.clone() | ||||
|                             .extend::<Image07, ImageType>() // FIXME: Don't clone()
 | ||||
|                             .ok()? | ||||
|                             .and_then(|url| Some(url.id_unchecked()?.to_string())) | ||||
|                     }) | ||||
|                     .ok_or(Error::MissingApProperty)?, | ||||
|                 self, | ||||
|             ) | ||||
|             .ok(); | ||||
| 
 | ||||
|             let pub_key = &json.ext_one.public_key.public_key_pem; | ||||
|             diesel::update(self) | ||||
|                 .set(( | ||||
|                     users::username.eq(json.object.ap_actor_props.preferred_username_string()?), | ||||
|                     users::display_name.eq(json.object.object_props.name_string()?), | ||||
|                     users::outbox_url.eq(json.object.ap_actor_props.outbox_string()?), | ||||
|                     users::inbox_url.eq(json.object.ap_actor_props.inbox_string()?), | ||||
|                     users::username.eq(json | ||||
|                         .ap_actor_ref() | ||||
|                         .preferred_username() | ||||
|                         .ok_or(Error::MissingApProperty)?), | ||||
|                     users::display_name.eq(json | ||||
|                         .ap_actor_ref() | ||||
|                         .name() | ||||
|                         .ok_or(Error::MissingApProperty)? | ||||
|                         .to_as_string() | ||||
|                         .ok_or(Error::MissingApProperty)?), | ||||
|                     users::outbox_url.eq(json | ||||
|                         .ap_actor_ref() | ||||
|                         .outbox()? | ||||
|                         .ok_or(Error::MissingApProperty)? | ||||
|                         .as_str()), | ||||
|                     users::inbox_url.eq(json.ap_actor_ref().inbox()?.as_str()), | ||||
|                     users::summary.eq(SafeString::new( | ||||
|                         &json | ||||
|                             .object | ||||
|                             .object_props | ||||
|                             .summary_string() | ||||
|                             .ap_actor_ref() | ||||
|                             .summary() | ||||
|                             .and_then(|summary| summary.to_as_string()) | ||||
|                             .unwrap_or_default(), | ||||
|                     )), | ||||
|                     users::followers_endpoint.eq(json.object.ap_actor_props.followers_string()?), | ||||
|                     users::followers_endpoint.eq(json | ||||
|                         .ap_actor_ref() | ||||
|                         .followers()? | ||||
|                         .ok_or(Error::MissingApProperty)? | ||||
|                         .as_str()), | ||||
|                     users::avatar_id.eq(avatar.map(|a| a.id)), | ||||
|                     users::last_fetched_date.eq(Utc::now().naive_utc()), | ||||
|                     users::public_key.eq(json | ||||
|                         .custom_props | ||||
|                         .public_key_publickey()? | ||||
|                         .public_key_pem_string()?), | ||||
|                     users::public_key.eq(pub_key), | ||||
|                 )) | ||||
|                 .execute(conn) | ||||
|                 .map(|_| ()) | ||||
| @ -548,7 +578,7 @@ impl User { | ||||
|         Ok(coll) | ||||
|     } | ||||
|     fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> { | ||||
|         let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?; | ||||
|         let mut res = get(url, Self::get_sender07(), CONFIG.proxy().cloned())?; | ||||
|         let text = &res.text()?; | ||||
|         let json: serde_json::Value = serde_json::from_str(text)?; | ||||
|         let items = json["items"] | ||||
| @ -565,7 +595,7 @@ impl User { | ||||
|         &self, | ||||
|         url: &str, | ||||
|     ) -> Result<(Vec<T>, Option<String>)> { | ||||
|         let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?; | ||||
|         let mut res = get(url, Self::get_sender07(), CONFIG.proxy().cloned())?; | ||||
|         let text = &res.text()?; | ||||
|         let json: serde_json::Value = serde_json::from_str(text)?; | ||||
|         let items = json["items"] | ||||
| @ -581,7 +611,7 @@ impl User { | ||||
|     pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> { | ||||
|         let mut res = get( | ||||
|             &self.outbox_url[..], | ||||
|             Self::get_sender(), | ||||
|             Self::get_sender07(), | ||||
|             CONFIG.proxy().cloned(), | ||||
|         )?; | ||||
|         let text = &res.text()?; | ||||
| @ -617,7 +647,7 @@ impl User { | ||||
|     pub fn fetch_outbox07<T: Activity07 + serde::de::DeserializeOwned>(&self) -> Result<Vec<T>> { | ||||
|         let mut res = get( | ||||
|             &self.outbox_url[..], | ||||
|             Self::get_sender(), | ||||
|             Self::get_sender07(), | ||||
|             CONFIG.proxy().cloned(), | ||||
|         )?; | ||||
|         let text = &res.text()?; | ||||
| @ -653,7 +683,7 @@ impl User { | ||||
|     pub fn fetch_followers_ids(&self) -> Result<Vec<String>> { | ||||
|         let mut res = get( | ||||
|             &self.followers_endpoint[..], | ||||
|             Self::get_sender(), | ||||
|             Self::get_sender07(), | ||||
|             CONFIG.proxy().cloned(), | ||||
|         )?; | ||||
|         let text = &res.text()?; | ||||
| @ -1097,110 +1127,6 @@ impl IntoId for User { | ||||
| 
 | ||||
| impl Eq for User {} | ||||
| 
 | ||||
| impl FromId<DbConn> for User { | ||||
|     type Error = Error; | ||||
|     type Object = CustomPerson; | ||||
| 
 | ||||
|     fn from_db(conn: &DbConn, id: &str) -> Result<Self> { | ||||
|         Self::find_by_ap_url(conn, id) | ||||
|     } | ||||
| 
 | ||||
|     fn from_activity(conn: &DbConn, acct: CustomPerson) -> Result<Self> { | ||||
|         let url = Url::parse(&acct.object.object_props.id_string()?)?; | ||||
|         let inst = url.host_str().ok_or(Error::Url)?; | ||||
|         let instance = Instance::find_by_domain(conn, inst).or_else(|_| { | ||||
|             Instance::insert( | ||||
|                 conn, | ||||
|                 NewInstance { | ||||
|                     name: inst.to_owned(), | ||||
|                     public_domain: inst.to_owned(), | ||||
|                     local: false, | ||||
|                     // We don't really care about all the following for remote instances
 | ||||
|                     long_description: SafeString::new(""), | ||||
|                     short_description: SafeString::new(""), | ||||
|                     default_license: String::new(), | ||||
|                     open_registrations: true, | ||||
|                     short_description_html: String::new(), | ||||
|                     long_description_html: String::new(), | ||||
|                 }, | ||||
|             ) | ||||
|         })?; | ||||
| 
 | ||||
|         let username = acct.object.ap_actor_props.preferred_username_string()?; | ||||
| 
 | ||||
|         if username.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) { | ||||
|             return Err(Error::InvalidValue); | ||||
|         } | ||||
| 
 | ||||
|         let fqn = if instance.local { | ||||
|             username.clone() | ||||
|         } else { | ||||
|             format!("{}@{}", username, instance.public_domain) | ||||
|         }; | ||||
| 
 | ||||
|         let user = User::insert( | ||||
|             conn, | ||||
|             NewUser { | ||||
|                 display_name: acct | ||||
|                     .object | ||||
|                     .object_props | ||||
|                     .name_string() | ||||
|                     .unwrap_or_else(|_| username.clone()), | ||||
|                 username, | ||||
|                 outbox_url: acct.object.ap_actor_props.outbox_string()?, | ||||
|                 inbox_url: acct.object.ap_actor_props.inbox_string()?, | ||||
|                 role: 2, | ||||
|                 summary: acct | ||||
|                     .object | ||||
|                     .object_props | ||||
|                     .summary_string() | ||||
|                     .unwrap_or_default(), | ||||
|                 summary_html: SafeString::new( | ||||
|                     &acct | ||||
|                         .object | ||||
|                         .object_props | ||||
|                         .summary_string() | ||||
|                         .unwrap_or_default(), | ||||
|                 ), | ||||
|                 email: None, | ||||
|                 hashed_password: None, | ||||
|                 instance_id: instance.id, | ||||
|                 ap_url: acct.object.object_props.id_string()?, | ||||
|                 public_key: acct | ||||
|                     .custom_props | ||||
|                     .public_key_publickey()? | ||||
|                     .public_key_pem_string()?, | ||||
|                 private_key: None, | ||||
|                 shared_inbox_url: acct | ||||
|                     .object | ||||
|                     .ap_actor_props | ||||
|                     .endpoints_endpoint() | ||||
|                     .and_then(|e| e.shared_inbox_string()) | ||||
|                     .ok(), | ||||
|                 followers_endpoint: acct.object.ap_actor_props.followers_string()?, | ||||
|                 fqn, | ||||
|                 avatar_id: None, | ||||
|             }, | ||||
|         )?; | ||||
| 
 | ||||
|         if let Ok(icon) = acct.object.object_props.icon_image() { | ||||
|             if let Ok(url) = icon.object_props.url_string() { | ||||
|                 let avatar = Media::save_remote(conn, url, &user); | ||||
| 
 | ||||
|                 if let Ok(avatar) = avatar { | ||||
|                     user.set_avatar(conn, avatar.id)?; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         Ok(user) | ||||
|     } | ||||
| 
 | ||||
|     fn get_sender() -> &'static dyn Signer { | ||||
|         Instance::get_local_instance_user().expect("Failed to local instance user") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromId07<DbConn> for User { | ||||
|     type Error = Error; | ||||
|     type Object = CustomPerson07; | ||||
| @ -1690,32 +1616,6 @@ pub(crate) mod tests { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn self_federation() { | ||||
|         let conn = db(); | ||||
|         conn.test_transaction::<_, (), _>(|| { | ||||
|             let users = fill_database(&conn); | ||||
| 
 | ||||
|             let ap_repr = users[0].to_activity(&conn).unwrap(); | ||||
|             users[0].delete(&conn).unwrap(); | ||||
|             let user = User::from_activity(&conn, ap_repr).unwrap(); | ||||
| 
 | ||||
|             assert_eq!(user.username, users[0].username); | ||||
|             assert_eq!(user.display_name, users[0].display_name); | ||||
|             assert_eq!(user.outbox_url, users[0].outbox_url); | ||||
|             assert_eq!(user.inbox_url, users[0].inbox_url); | ||||
|             assert_eq!(user.instance_id, users[0].instance_id); | ||||
|             assert_eq!(user.ap_url, users[0].ap_url); | ||||
|             assert_eq!(user.public_key, users[0].public_key); | ||||
|             assert_eq!(user.shared_inbox_url, users[0].shared_inbox_url); | ||||
|             assert_eq!(user.followers_endpoint, users[0].followers_endpoint); | ||||
|             assert_eq!(user.avatar_url(&conn), users[0].avatar_url(&conn)); | ||||
|             assert_eq!(user.fqn, users[0].fqn); | ||||
|             assert_eq!(user.summary_html, users[0].summary_html); | ||||
|             Ok(()) | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn self_federation07() { | ||||
|         let conn = db(); | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| use plume_common::activity_pub::{ | ||||
|     inbox::FromId, | ||||
|     inbox::FromId07, | ||||
|     request::Digest, | ||||
|     sign::{verify_http_headers, Signable}, | ||||
| }; | ||||
| @ -26,7 +26,7 @@ pub fn handle_incoming( | ||||
|         .or_else(|| activity["actor"]["id"].as_str()) | ||||
|         .ok_or(status::BadRequest(Some("Missing actor id for activity")))?; | ||||
| 
 | ||||
|     let actor = User::from_id(&conn, actor_id, None, CONFIG.proxy()) | ||||
|     let actor = User::from_id07(&conn, actor_id, None, CONFIG.proxy()) | ||||
|         .expect("instance::shared_inbox: user error"); | ||||
|     if !verify_http_headers(&actor, &headers.0, &sig).is_secure() && !act.clone().verify(&actor) { | ||||
|         // maybe we just know an old key?
 | ||||
|  | ||||
| @ -11,7 +11,7 @@ use validator::{Validate, ValidationErrors}; | ||||
| use crate::inbox; | ||||
| use crate::routes::{errors::ErrorPage, rocket_uri_macro_static_files, Page, RespondOrRedirect}; | ||||
| use crate::template_utils::{IntoContext, Ructe}; | ||||
| use plume_common::activity_pub::{broadcast, inbox::FromId}; | ||||
| use plume_common::activity_pub::{broadcast, inbox::FromId07}; | ||||
| use plume_models::{ | ||||
|     admin::*, | ||||
|     blocklisted_emails::*, | ||||
| @ -404,7 +404,7 @@ pub fn interact(conn: DbConn, user: Option<User>, target: String) -> Option<Redi | ||||
|         return Some(Redirect::to(uri!(super::user::details: name = target))); | ||||
|     } | ||||
| 
 | ||||
|     if let Ok(post) = Post::from_id(&conn, &target, None, CONFIG.proxy()) { | ||||
|     if let Ok(post) = Post::from_id07(&conn, &target, None, CONFIG.proxy()) { | ||||
|         return Some(Redirect::to(uri!( | ||||
|             super::posts::details: blog = post.get_blog(&conn).expect("Can't retrieve blog").fqn, | ||||
|             slug = &post.slug, | ||||
| @ -412,7 +412,7 @@ pub fn interact(conn: DbConn, user: Option<User>, target: String) -> Option<Redi | ||||
|         ))); | ||||
|     } | ||||
| 
 | ||||
|     if let Ok(comment) = Comment::from_id(&conn, &target, None, CONFIG.proxy()) { | ||||
|     if let Ok(comment) = Comment::from_id07(&conn, &target, None, CONFIG.proxy()) { | ||||
|         if comment.can_see(&conn, user.as_ref()) { | ||||
|             let post = comment.get_post(&conn).expect("Can't retrieve post"); | ||||
|             return Some(Redirect::to(uri!( | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user