diff --git a/Cargo.lock b/Cargo.lock index 120f68278..3af6e1bd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1785,6 +1785,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "serial_test", "sha2", "strum", "strum_macros", diff --git a/config/config.hjson b/config/config.hjson index 252fca250..361c06bef 100644 --- a/config/config.hjson +++ b/config/config.hjson @@ -1,5 +1,8 @@ # See the documentation for available config fields and descriptions: # https://join-lemmy.org/docs/en/administration/configuration.html { - hostname: lemmy-alpha + hostname: lemmy-alphan + federation: { + enabled: true + } } diff --git a/crates/apub/Cargo.toml b/crates/apub/Cargo.toml index 59d0b0bee..03fb3f518 100644 --- a/crates/apub/Cargo.toml +++ b/crates/apub/Cargo.toml @@ -48,3 +48,5 @@ thiserror = "1.0.29" background-jobs = "0.9.0" reqwest = { version = "0.11.4", features = ["json"] } +[dev-dependencies] +serial_test = "0.5.1" diff --git a/crates/apub/assets/lemmy-person.json b/crates/apub/assets/lemmy-person.json new file mode 100644 index 000000000..fb1afbe9a --- /dev/null +++ b/crates/apub/assets/lemmy-person.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + { + "moderators": "as:moderators", + "sensitive": "as:sensitive", + "pt": "https://join-lemmy.org#", + "sc": "http://schema.org#", + "stickied": "as:stickied", + "matrixUserId": { + "type": "sc:Text", + "id": "as:alsoKnownAs" + }, + "comments_enabled": { + "type": "sc:Boolean", + "id": "pt:commentsEnabled" + } + }, + "https://w3id.org/security/v1" + ], + "type": "Person", + "id": "https://lemmy.ml/u/nutomic", + "preferredUsername": "nutomic", + "content": "

Lemmy maintainer. Interested in politics, video games, and many other things.

\n", + "mediaType": "text/html", + "source": { + "content": "Lemmy maintainer. Interested in politics, video games, and many other things.", + "mediaType": "text/markdown" + }, + "icon": { + "type": "Image", + "url": "https://lemmy.ml/pictrs/image/ed9ej7.jpg" + }, + "inbox": "https://lemmy.ml/u/nutomic/inbox", + "outbox": "https://lemmy.ml/u/nutomic/outbox", + "endpoints": { + "sharedInbox": "https://lemmy.ml/inbox" + }, + "publicKey": { + "id": "https://lemmy.ml/u/nutomic#main-key", + "owner": "https://lemmy.ml/u/nutomic", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0lP99/s5Vv+XbPdkeqIJ\nwoD4GFnHmBnBHdEKChEUWfWj1TtioC/rGNoXFQeXQA3Amhy4nxSceiDnUgwkkuQY\nv0MtIW58NzgknEavtllxL+LSds5pg3gANaDIk8UiWTkqXTg0GnlJMpCK1Chen0l/\nszL6DEvUyTSuS5ZYDXFgewF89Pe7U0S15V5U2Harv7AgJYDyxmUL0D1pGuUCRqcE\nl5MTHJjrXeNnH1w2g8aly8YlO/Cr0L51rFg/lBF23vni7ZLv8HbmWh6YpaAf1R8h\nE45zKR7OHqymdjzrg1ITBwovefpwMkVgnJ+Wdr4HPnFlBSkXPoZeM11+Z8L0anzA\nXwIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "published": "2020-01-17T01:38:22.348392+00:00", + "updated": "2021-08-13T00:11:15.941990+00:00" +} diff --git a/crates/apub/assets/pleroma-person.json b/crates/apub/assets/pleroma-person.json new file mode 100644 index 000000000..bc9008bab --- /dev/null +++ b/crates/apub/assets/pleroma-person.json @@ -0,0 +1,79 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://queer.hacktivis.me/schemas/litepub-0.1.jsonld", + { + "@language": "und" + } + ], + "alsoKnownAs": [], + "attachment": [], + "capabilities": { + "acceptsChatMessages": true + }, + "discoverable": false, + "endpoints": { + "oauthAuthorizationEndpoint": "https://queer.hacktivis.me/oauth/authorize", + "oauthRegistrationEndpoint": "https://queer.hacktivis.me/api/v1/apps", + "oauthTokenEndpoint": "https://queer.hacktivis.me/oauth/token", + "sharedInbox": "https://queer.hacktivis.me/inbox", + "uploadMedia": "https://queer.hacktivis.me/api/ap/upload_media" + }, + "featured": "https://queer.hacktivis.me/users/lanodan/collections/featured", + "followers": "https://queer.hacktivis.me/users/lanodan/followers", + "following": "https://queer.hacktivis.me/users/lanodan/following", + "icon": { + "type": "Image", + "url": "https://queer.hacktivis.me/media/d23cf9b0-5586-4592-aca5-9a52777a6042/avatar_HD.png" + }, + "id": "https://queer.hacktivis.me/users/lanodan", + "image": { + "type": "Image", + "url": "https://queer.hacktivis.me/media/37b6ce56-8c24-4e64-bd70-a76e84ab0c69/53a48a3a49ed5e5637a84e4f3663df17f8d764244bbc1027ba03cfc446e8b7bd.jpg" + }, + "inbox": "https://queer.hacktivis.me/users/lanodan/inbox", + "manuallyApprovesFollowers": false, + "name": "Haelwenn /элвэн/ :bzh: ", + "outbox": "https://queer.hacktivis.me/users/lanodan/outbox", + "preferredUsername": "lanodan", + "publicKey": { + "id": "https://queer.hacktivis.me/users/lanodan#main-key", + "owner": "https://queer.hacktivis.me/users/lanodan", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsWOgdjSMc010qvxC3njI\nXJlFWMJ5gJ8QXCW/PajYdsHPM6d+jxBNJ6zp9/tIRa2m7bWHTSkuHQ7QthOpt6vu\n+dAWpKRLS607SPLItn/qUcyXvgN+H8shfyhMxvkVs9jXdtlBsLUVE7UNpN0dxzqe\nI79QWbf7o4amgaIWGRYB+OYMnIxKt+GzIkivZdSVSYjfxNnBYkMCeUxm5EpPIxKS\nP5bBHAVRRambD5NUmyKILuC60/rYuc/C+vmgpY2HCWFS2q6o34dPr9enwL6t4b3m\nS1t/EJHk9rGaaDqSGkDEfyQI83/7SDebWKuETMKKFLZi1vMgQIFuOYCIhN6bIiZm\npQIDAQAB\n-----END PUBLIC KEY-----\n\n" + }, + "summary": "---
Website: https://hacktivis.me/
Lang: Français(natif), English(fluent), LSF(🤏~👌), русский (еле-еле),
Politics: Anarchist as in DIY/DIWO, freedom of association, anti-authoritarian, anti-identitarianism

Pronouns: meh, pick any, have fun
Timezone: Let's say Mars, I have a non-24h cycle
```
🦊🦄⚧🂡ⓥ :anarchy: 👿🐧 :gentoo:
Pleroma maintainer (mostly backend)
BadWolf developer
Gentoo contributor

Dayjob: yogoko.fr

That person which uses HJKL in games

Just because computer bad: X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

banner from: https://soc.flyingcube.tech/objects/56f79be2-9013-4559-9826-f7dc392417db
Federation-bots: #nobot", + "tag": [ + { + "icon": { + "type": "Image", + "url": "https://queer.hacktivis.me/emoji/custom/symbols/anarchy.png" + }, + "id": "https://queer.hacktivis.me/emoji/custom/symbols/anarchy.png", + "name": ":anarchy:", + "type": "Emoji", + "updated": "1970-01-01T00:00:00Z" + }, + { + "icon": { + "type": "Image", + "url": "https://queer.hacktivis.me/emoji/custom/mastodon.xyz/bzh.png" + }, + "id": "https://queer.hacktivis.me/emoji/custom/mastodon.xyz/bzh.png", + "name": ":bzh:", + "type": "Emoji", + "updated": "1970-01-01T00:00:00Z" + }, + { + "icon": { + "type": "Image", + "url": "https://queer.hacktivis.me/emoji/custom/gentoo.png" + }, + "id": "https://queer.hacktivis.me/emoji/custom/gentoo.png", + "name": ":gentoo:", + "type": "Emoji", + "updated": "1970-01-01T00:00:00Z" + } + ], + "type": "Person", + "url": "https://queer.hacktivis.me/users/lanodan" +} diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 5745778c7..9f0552b02 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -60,7 +60,7 @@ pub struct Note { media_type: MediaTypeHtml, source: Source, in_reply_to: CommentInReplyToMigration, - published: DateTime, + published: Option>, updated: Option>, #[serde(flatten)] unparsed: Unparsed, @@ -230,7 +230,7 @@ impl ToApub for ApubComment { media_type: MediaTypeMarkdown::Markdown, }, in_reply_to: CommentInReplyToMigration::Old(in_reply_to_vec), - published: convert_datetime(self.published), + published: Some(convert_datetime(self.published)), updated: self.updated.map(convert_datetime), unparsed: Default::default(), }; @@ -282,7 +282,7 @@ impl FromApub for ApubComment { content: content_slurs_removed, removed: None, read: None, - published: Some(note.published.naive_local()), + published: note.published.map(|u| u.to_owned().naive_local()), updated: note.updated.map(|u| u.to_owned().naive_local()), deleted: None, ap_id, diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 56a86cc85..e46dd7d00 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -69,7 +69,7 @@ pub struct Group { followers: Url, endpoints: Endpoints, public_key: PublicKey, - published: DateTime, + published: Option>, updated: Option>, #[serde(flatten)] unparsed: Unparsed, @@ -101,7 +101,7 @@ impl Group { title, description, removed: None, - published: Some(group.published.naive_local()), + published: group.published.map(|u| u.naive_local()), updated: group.updated.map(|u| u.naive_local()), deleted: None, nsfw: Some(group.sensitive.unwrap_or(false)), @@ -232,7 +232,7 @@ impl ToApub for ApubCommunity { ..Default::default() }, public_key: self.get_public_key()?, - published: convert_datetime(self.published), + published: Some(convert_datetime(self.published)), updated: self.updated.map(convert_datetime), unparsed: Default::default(), }; diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index c5f20c292..147215e91 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -53,3 +53,80 @@ where Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into()) } } + +#[cfg(test)] +mod tests { + use super::*; + use actix::Actor; + use diesel::{ + r2d2::{ConnectionManager, Pool}, + PgConnection, + }; + use lemmy_apub_lib::activity_queue::create_activity_queue; + use lemmy_db_schema::{ + establish_unpooled_connection, + get_database_url_from_env, + source::secret::Secret, + }; + use lemmy_utils::{ + rate_limit::{rate_limiter::RateLimiter, RateLimit}, + request::build_user_agent, + settings::structs::Settings, + }; + use lemmy_websocket::{chat_server::ChatServer, LemmyContext}; + use reqwest::Client; + use serde::de::DeserializeOwned; + use std::{fs::File, io::BufReader, sync::Arc}; + use tokio::sync::Mutex; + + // TODO: would be nice if we didnt have to use a full context for tests. + // or at least write a helper function so this code is shared with main.rs + pub(crate) fn init_context() -> LemmyContext { + // call this to run migrations + establish_unpooled_connection(); + let settings = Settings::init().unwrap(); + let rate_limiter = RateLimit { + rate_limiter: Arc::new(Mutex::new(RateLimiter::default())), + rate_limit_config: settings.rate_limit.to_owned().unwrap_or_default(), + }; + let client = Client::builder() + .user_agent(build_user_agent(&settings)) + .build() + .unwrap(); + let activity_queue = create_activity_queue(); + let secret = Secret { + id: 0, + jwt_secret: "".to_string(), + }; + let db_url = match get_database_url_from_env() { + Ok(url) => url, + Err(_) => settings.get_database_url(), + }; + let manager = ConnectionManager::::new(&db_url); + let pool = Pool::builder() + .max_size(settings.database.pool_size) + .build(manager) + .unwrap_or_else(|_| panic!("Error connecting to {}", db_url)); + async fn x() -> Result { + Ok("".to_string()) + } + let chat_server = ChatServer::startup( + pool.clone(), + rate_limiter.clone(), + |_, _, _, _| Box::pin(x()), + |_, _, _, _| Box::pin(x()), + client.clone(), + activity_queue.clone(), + settings.clone(), + secret.clone(), + ) + .start(); + LemmyContext::create(pool, chat_server, client, activity_queue, settings, secret) + } + + pub(crate) fn file_to_json_object(path: &str) -> T { + let file = File::open(path).unwrap(); + let reader = BufReader::new(file); + serde_json::from_reader(reader).unwrap() + } +} diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 53503d007..775adeca5 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -67,7 +67,7 @@ pub struct Person { outbox: Url, endpoints: Endpoints, public_key: PublicKey, - published: DateTime, + published: Option>, updated: Option>, #[serde(flatten)] unparsed: Unparsed, @@ -192,7 +192,7 @@ impl ToApub for ApubPerson { icon, image, matrix_user_id: self.matrix_user_id.clone(), - published: convert_datetime(self.published), + published: Some(convert_datetime(self.published)), outbox: generate_outbox_url(&self.actor_id)?.into(), endpoints: Endpoints { shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()), @@ -245,7 +245,7 @@ impl FromApub for ApubPerson { deleted: None, avatar: Some(person.icon.clone().map(|i| i.url.into())), banner: Some(person.image.clone().map(|i| i.url.into())), - published: Some(person.published.naive_local()), + published: person.published.map(|u| u.clone().naive_local()), updated: person.updated.map(|u| u.clone().naive_local()), actor_id, bio: Some(bio), @@ -266,3 +266,43 @@ impl FromApub for ApubPerson { Ok(person.into()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::objects::tests::{file_to_json_object, init_context}; + use serial_test::serial; + + #[actix_rt::test] + #[serial] + async fn test_fetch_lemmy_person() { + let json = file_to_json_object("assets/lemmy-person.json"); + let url = Url::parse("https://lemmy.ml/u/nutomic").unwrap(); + let person = ApubPerson::from_apub(&json, &init_context(), &url, &mut 0) + .await + .unwrap(); + + assert_eq!(person.actor_id.clone().into_inner(), url); + assert_eq!(person.name, "nutomic"); + assert!(person.public_key.is_some()); + assert_eq!(person.local, false); + assert!(person.bio.is_some()); + } + + #[actix_rt::test] + #[serial] + async fn test_fetch_pleroma_person() { + let json = file_to_json_object("assets/pleroma-person.json"); + let url = Url::parse("https://queer.hacktivis.me/users/lanodan").unwrap(); + let person = ApubPerson::from_apub(&json, &init_context(), &url, &mut 0) + .await + .unwrap(); + + assert_eq!(person.actor_id.clone().into_inner(), url); + assert_eq!(person.name, "lanodan"); + assert!(person.public_key.is_some()); + assert_eq!(person.local, false); + // TODO: pleroma uses summary for user profile, while we use content + //assert!(person.bio.is_some()); + } +} diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index dc6d96518..97419cfff 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -61,7 +61,7 @@ pub struct Page { pub(crate) comments_enabled: Option, sensitive: Option, pub(crate) stickied: Option, - published: DateTime, + published: Option>, updated: Option>, #[serde(flatten)] unparsed: Unparsed, @@ -196,7 +196,7 @@ impl ToApub for ApubPost { comments_enabled: Some(!self.locked), sensitive: Some(self.nsfw), stickied: Some(self.stickied), - published: convert_datetime(self.published), + published: Some(convert_datetime(self.published)), updated: self.updated.map(convert_datetime), unparsed: Default::default(), }; @@ -260,7 +260,7 @@ impl FromApub for ApubPost { community_id: community.id, removed: None, locked: page.comments_enabled.map(|e| !e), - published: Some(page.published.naive_local()), + published: page.published.map(|u| u.naive_local()), updated: page.updated.map(|u| u.naive_local()), deleted: None, nsfw: page.sensitive, diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index cde1966ed..ee6aff74d 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -46,7 +46,7 @@ pub struct Note { content: String, media_type: MediaTypeHtml, source: Source, - published: DateTime, + published: Option>, updated: Option>, #[serde(flatten)] unparsed: Unparsed, @@ -146,7 +146,7 @@ impl ToApub for ApubPrivateMessage { content: self.content.clone(), media_type: MediaTypeMarkdown::Markdown, }, - published: convert_datetime(self.published), + published: Some(convert_datetime(self.published)), updated: self.updated.map(convert_datetime), unparsed: Default::default(), }; @@ -185,7 +185,7 @@ impl FromApub for ApubPrivateMessage { creator_id: creator.id, recipient_id: recipient.id, content: note.source.content.clone(), - published: Some(note.published.naive_local()), + published: note.published.map(|u| u.to_owned().naive_local()), updated: note.updated.map(|u| u.to_owned().naive_local()), deleted: None, read: None, diff --git a/crates/websocket/src/lib.rs b/crates/websocket/src/lib.rs index fbf9d25a3..b7ab5bf28 100644 --- a/crates/websocket/src/lib.rs +++ b/crates/websocket/src/lib.rs @@ -16,12 +16,12 @@ pub mod routes; pub mod send; pub struct LemmyContext { - pub pool: DbPool, - pub chat_server: Addr, - pub client: Client, - pub activity_queue: QueueHandle, - pub settings: Settings, - pub secret: Secret, + pool: DbPool, + chat_server: Addr, + client: Client, + activity_queue: QueueHandle, + settings: Settings, + secret: Secret, } impl LemmyContext { diff --git a/scripts/update_apub_test_files.sh b/scripts/update_apub_test_files.sh new file mode 100755 index 000000000..b62fc3b6c --- /dev/null +++ b/scripts/update_apub_test_files.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +curl -H "Accept: application/activity+json" https://lemmy.ml/u/nutomic | jq \ + > crates/apub/assets/lemmy-person.json +curl -H "Accept: application/activity+json" https://queer.hacktivis.me/users/lanodan | jq \ + > crates/apub/assets/pleroma-person.json \ No newline at end of file