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