From c5ced6fa5e0a5f80c9e58c3a4bf199434194195a Mon Sep 17 00:00:00 2001 From: Felix Date: Fri, 17 Apr 2020 17:33:55 +0200 Subject: [PATCH] Added documentation for most functions --- server/src/apub/activities.rs | 12 +++++++--- server/src/apub/community.rs | 29 ++++------------------- server/src/apub/community_inbox.rs | 3 +++ server/src/apub/fetcher.rs | 19 +++++++++++---- server/src/apub/mod.rs | 38 +++++++++++++++--------------- server/src/apub/post.rs | 3 +++ server/src/apub/signatures.rs | 5 ++-- server/src/apub/user.rs | 2 ++ server/src/apub/user_inbox.rs | 4 ++++ 9 files changed, 62 insertions(+), 53 deletions(-) diff --git a/server/src/apub/activities.rs b/server/src/apub/activities.rs index 8885f5558..f31be9db3 100644 --- a/server/src/apub/activities.rs +++ b/server/src/apub/activities.rs @@ -30,6 +30,7 @@ fn populate_object_props( Ok(()) } +/// Send an activity to a list of recipients, using the correct headers etc. fn send_activity(activity: &A, to: Vec) -> Result<(), Error> where A: Serialize + Debug, @@ -47,7 +48,8 @@ where Ok(()) } -fn get_followers(conn: &PgConnection, community: &Community) -> Result, Error> { +/// For a given community, returns the inboxes of all followers. +fn get_follower_inboxes(conn: &PgConnection, community: &Community) -> Result, Error> { Ok( CommunityFollowerView::for_community(conn, community.id)? .iter() @@ -57,6 +59,7 @@ fn get_followers(conn: &PgConnection, community: &Community) -> Result Result<(), Error> { let page = post.as_page(conn)?; let community = Community::read(conn, post.community_id)?; @@ -70,10 +73,11 @@ pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result< .create_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(page)?; - send_activity(&create, get_followers(conn, &community)?)?; + send_activity(&create, get_follower_inboxes(conn, &community)?)?; Ok(()) } +/// Send out information about an edited post, to the followers of the community. pub fn post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> { let page = post.as_page(conn)?; let community = Community::read(conn, post.community_id)?; @@ -87,10 +91,11 @@ pub fn post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result< .update_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_object_base_box(page)?; - send_activity(&update, get_followers(conn, &community)?)?; + send_activity(&update, get_follower_inboxes(conn, &community)?)?; Ok(()) } +/// As a given local user, send out a follow request to a remote community. pub fn follow_community( community: &Community, user: &User_, @@ -111,6 +116,7 @@ pub fn follow_community( Ok(()) } +/// As a local community, accept the follow request from a remote user. pub fn accept_follow(follow: &Follow) -> Result<(), Error> { let mut accept = Accept::new(); accept diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs index 0bea47055..a49357a86 100644 --- a/server/src/apub/community.rs +++ b/server/src/apub/community.rs @@ -7,7 +7,6 @@ use crate::db::establish_unpooled_connection; use crate::db::post::Post; use crate::db::user::User_; use crate::db::Crud; -use crate::settings::Settings; use crate::{convert_datetime, naive_now}; use activitystreams::actor::properties::ApActorProperties; use activitystreams::collection::OrderedCollection; @@ -30,30 +29,8 @@ pub struct CommunityQuery { community_name: String, } -pub async fn get_apub_community_list( - db: web::Data>>, -) -> Result, Error> { - // TODO: implement pagination - let communities = Community::list_local(&db.get().unwrap())? - .iter() - .map(|c| c.as_group(&db.get().unwrap())) - .collect::, Error>>()?; - let mut collection = UnorderedCollection::default(); - let oprops: &mut ObjectProperties = collection.as_mut(); - oprops.set_context_xsd_any_uri(context())?.set_id(format!( - "{}://{}/federation/communities", - get_apub_protocol_string(), - Settings::get().hostname - ))?; - - collection - .collection_props - .set_total_items(communities.len() as u64)? - .set_many_items_base_boxes(communities)?; - Ok(create_apub_response(&collection)) -} - impl Community { + // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network. fn as_group(&self, conn: &PgConnection) -> Result { let mut group = Group::default(); let oprops: &mut ObjectProperties = group.as_mut(); @@ -104,6 +81,7 @@ impl Community { } impl CommunityForm { + /// Parse an ActivityPub group received from another instance into a Lemmy community. pub fn from_group(group: &GroupExt, conn: &PgConnection) -> Result { let oprops = &group.base.base.object_props; let aprops = &group.base.extension; @@ -142,6 +120,7 @@ impl CommunityForm { } } +/// Return the community json over HTTP. pub async fn get_apub_community_http( info: Path, db: web::Data>>, @@ -151,6 +130,7 @@ pub async fn get_apub_community_http( Ok(create_apub_response(&c)) } +/// Returns an empty followers collection, only populating the siz (for privacy). pub async fn get_apub_community_followers( info: Path, db: web::Data>>, @@ -173,6 +153,7 @@ pub async fn get_apub_community_followers( Ok(create_apub_response(&collection)) } +/// Returns an UnorderedCollection with the latest posts from the community. pub async fn get_apub_community_outbox( info: Path, db: web::Data>>, diff --git a/server/src/apub/community_inbox.rs b/server/src/apub/community_inbox.rs index caadecf13..ea0d91054 100644 --- a/server/src/apub/community_inbox.rs +++ b/server/src/apub/community_inbox.rs @@ -22,6 +22,7 @@ pub struct Params { community_name: String, } +/// Handler for all incoming activities to community inboxes. pub async fn community_inbox( input: web::Json, params: web::Query, @@ -38,6 +39,8 @@ pub async fn community_inbox( } } +/// Handle a follow request from a remote user, adding it to the local database and returning an +/// Accept activity. fn handle_follow(follow: &Follow, conn: &PgConnection) -> Result { // TODO: make sure this is a local community let community_uri = follow diff --git a/server/src/apub/fetcher.rs b/server/src/apub/fetcher.rs index 3be53ea72..368aa4dc4 100644 --- a/server/src/apub/fetcher.rs +++ b/server/src/apub/fetcher.rs @@ -20,6 +20,7 @@ use serde::Deserialize; use std::time::Duration; use url::Url; +// Fetch nodeinfo metadata from a remote instance. fn _fetch_node_info(domain: &str) -> Result { let well_known_uri = Url::parse(&format!( "{}://{}/.well-known/nodeinfo", @@ -60,7 +61,9 @@ fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result } } -// TODO: add an optional param last_updated and only fetch if its too old +/// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation, +/// timeouts etc. +/// TODO: add an optional param last_updated and only fetch if its too old pub fn fetch_remote_object(url: &Url) -> Result where Response: for<'de> Deserialize<'de>, @@ -81,6 +84,7 @@ where Ok(res) } +/// The types of ActivityPub objects that can be fetched directly by searching for their ID. #[serde(untagged)] #[derive(serde::Deserialize)] pub enum SearchAcceptedObjects { @@ -89,6 +93,12 @@ pub enum SearchAcceptedObjects { Page(Box), } +/// Attempt to parse the query as URL, and fetch an ActivityPub object from it. +/// +/// Some working examples for use with the docker/federation/ setup: +/// http://lemmy_alpha:8540/federation/c/main +/// http://lemmy_alpha:8540/federation/u/lemmy_alpha +/// http://lemmy_alpha:8540/federation/p/3 pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result { let query_url = Url::parse(&query)?; let mut response = SearchResponse { @@ -98,10 +108,6 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result(&query_url)? { SearchAcceptedObjects::Person(p) => { let u = upsert_user(&UserForm::from_person(&p)?, conn)?; @@ -120,6 +126,7 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result Result, Error> { let outbox_url = Url::parse(&community.get_outbox_url())?; let outbox = fetch_remote_object::(&outbox_url)?; @@ -137,12 +144,14 @@ fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result< ) } +/// Fetch a user, insert/update it in the database and return the user. pub fn fetch_remote_user(apub_id: &Url, conn: &PgConnection) -> Result { let person = fetch_remote_object::(apub_id)?; let uf = UserForm::from_person(&person)?; upsert_user(&uf, conn) } +/// Fetch a community, insert/update it in the database and return the community. pub fn fetch_remote_community(apub_id: &Url, conn: &PgConnection) -> Result { let group = fetch_remote_object::(apub_id)?; let cf = CommunityForm::from_group(&group, conn)?; diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs index 04b462bff..a7f0668a3 100644 --- a/server/src/apub/mod.rs +++ b/server/src/apub/mod.rs @@ -13,6 +13,7 @@ use activitystreams::ext::Ext; use actix_web::body::Body; use actix_web::HttpResponse; use openssl::{pkey::PKey, rsa::Rsa}; +use serde::ser::Serialize; use url::Url; type GroupExt = Ext, PublicKeyExtension>; @@ -27,18 +28,22 @@ pub enum EndpointType { Comment, } -fn create_apub_response(json: &T) -> HttpResponse +/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub +/// headers. +fn create_apub_response(data: &T) -> HttpResponse where - T: serde::ser::Serialize, + T: Serialize, { HttpResponse::Ok() .content_type(APUB_JSON_CONTENT_TYPE) - .json(json) + .json(data) } -// TODO: we will probably need to change apub endpoint urls so that html and activity+json content -// types are handled at the same endpoint, so that you can copy the url into mastodon search -// and have it fetch the object. +/// Generates the ActivityPub ID for a given object type and name. +/// +/// TODO: we will probably need to change apub endpoint urls so that html and activity+json content +/// types are handled at the same endpoint, so that you can copy the url into mastodon search +/// and have it fetch the object. pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url { let point = match endpoint_type { EndpointType::Community => "c", @@ -67,21 +72,16 @@ pub fn get_apub_protocol_string() -> &'static str { } } -pub fn gen_keypair() -> (Vec, Vec) { +/// Generate the asymmetric keypair for ActivityPub HTTP signatures. +pub fn gen_keypair_str() -> (String, String) { let rsa = Rsa::generate(2048).expect("sign::gen_keypair: key generation error"); let pkey = PKey::from_rsa(rsa).expect("sign::gen_keypair: parsing error"); - ( - pkey - .public_key_to_pem() - .expect("sign::gen_keypair: public key encoding error"), - pkey - .private_key_to_pem_pkcs8() - .expect("sign::gen_keypair: private key encoding error"), - ) -} - -pub fn gen_keypair_str() -> (String, String) { - let (public_key, private_key) = gen_keypair(); + let public_key = pkey + .public_key_to_pem() + .expect("sign::gen_keypair: public key encoding error"); + let private_key = pkey + .private_key_to_pem_pkcs8() + .expect("sign::gen_keypair: private key encoding error"); (vec_bytes_to_str(public_key), vec_bytes_to_str(private_key)) } diff --git a/server/src/apub/post.rs b/server/src/apub/post.rs index b574d09c0..edae92d06 100644 --- a/server/src/apub/post.rs +++ b/server/src/apub/post.rs @@ -20,6 +20,7 @@ pub struct PostQuery { post_id: String, } +/// Return the post json over HTTP. pub async fn get_apub_post( info: Path, db: web::Data>>, @@ -30,6 +31,7 @@ pub async fn get_apub_post( } impl Post { + // Turn a Lemmy post into an ActivityPub page that can be sent out over the network. pub fn as_page(&self, conn: &PgConnection) -> Result { let mut page = Page::default(); let oprops: &mut ObjectProperties = page.as_mut(); @@ -67,6 +69,7 @@ impl Post { } impl PostForm { + /// Parse an ActivityPub page received from another instance into a Lemmy post. pub fn from_page(page: &Page, conn: &PgConnection) -> Result { let oprops = &page.object_props; let creator_id = Url::parse(&oprops.get_attributed_to_xsd_any_uri().unwrap().to_string())?; diff --git a/server/src/apub/signatures.rs b/server/src/apub/signatures.rs index 5bb3c5344..0348acb85 100644 --- a/server/src/apub/signatures.rs +++ b/server/src/apub/signatures.rs @@ -1,11 +1,12 @@ // For this example, we'll use the Extensible trait, the Extension trait, the Actor trait, and // the Person type use activitystreams::{actor::Actor, ext::Extension}; +use serde::{Deserialize, Serialize}; // The following is taken from here: // https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html -#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PublicKey { pub id: String, @@ -13,7 +14,7 @@ pub struct PublicKey { pub public_key_pem: String, } -#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PublicKeyExtension { pub public_key: PublicKey, diff --git a/server/src/apub/user.rs b/server/src/apub/user.rs index d10093d4c..b5a819114 100644 --- a/server/src/apub/user.rs +++ b/server/src/apub/user.rs @@ -22,6 +22,7 @@ pub struct UserQuery { user_name: String, } +// Turn a Lemmy user into an ActivityPub person and return it as json. pub async fn get_apub_user( info: Path, db: web::Data>>, @@ -64,6 +65,7 @@ pub async fn get_apub_user( } impl UserForm { + /// Parse an ActivityPub person received from another instance into a Lemmy user. pub fn from_person(person: &PersonExt) -> Result { let oprops = &person.base.base.object_props; let aprops = &person.base.extension; diff --git a/server/src/apub/user_inbox.rs b/server/src/apub/user_inbox.rs index 3b8d1df35..7d1463083 100644 --- a/server/src/apub/user_inbox.rs +++ b/server/src/apub/user_inbox.rs @@ -22,6 +22,7 @@ pub struct Params { user_name: String, } +/// Handler for all incoming activities to user inboxes. pub async fn user_inbox( input: web::Json, params: web::Query, @@ -38,6 +39,7 @@ pub async fn user_inbox( } } +/// Handle create activities and insert them in the database. fn handle_create(create: &Create, conn: &PgConnection) -> Result { let page = create .create_props @@ -52,6 +54,7 @@ fn handle_create(create: &Create, conn: &PgConnection) -> Result Result { let page = update .update_props @@ -67,6 +70,7 @@ fn handle_update(update: &Update, conn: &PgConnection) -> Result Result { // TODO: make sure that we actually requested a follow // TODO: at this point, indicate to the user that they are following the community