Added documentation for most functions

This commit is contained in:
Felix 2020-04-17 17:33:55 +02:00
parent 8908c8b184
commit c5ced6fa5e
9 changed files with 62 additions and 53 deletions

View file

@ -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<A>(activity: &A, to: Vec<String>) -> Result<(), Error>
where
A: Serialize + Debug,
@ -47,7 +48,8 @@ where
Ok(())
}
fn get_followers(conn: &PgConnection, community: &Community) -> Result<Vec<String>, Error> {
/// For a given community, returns the inboxes of all followers.
fn get_follower_inboxes(conn: &PgConnection, community: &Community) -> Result<Vec<String>, Error> {
Ok(
CommunityFollowerView::for_community(conn, community.id)?
.iter()
@ -57,6 +59,7 @@ fn get_followers(conn: &PgConnection, community: &Community) -> Result<Vec<Strin
)
}
/// Send out information about a newly created post, to the followers of the community.
pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> 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

View file

@ -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<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse<Body>, Error> {
// TODO: implement pagination
let communities = Community::list_local(&db.get().unwrap())?
.iter()
.map(|c| c.as_group(&db.get().unwrap()))
.collect::<Result<Vec<GroupExt>, 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<GroupExt, Error> {
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<Self, Error> {
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<CommunityQuery>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
@ -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<CommunityQuery>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
@ -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<CommunityQuery>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,

View file

@ -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<CommunityAcceptedObjects>,
params: web::Query<Params>,
@ -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<HttpResponse, Error> {
// TODO: make sure this is a local community
let community_uri = follow

View file

@ -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<NodeInfo, Error> {
let well_known_uri = Url::parse(&format!(
"{}://{}/.well-known/nodeinfo",
@ -60,7 +61,9 @@ fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, Error>
}
}
// 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<Response>(url: &Url) -> Result<Response, Error>
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<Page>),
}
/// 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<SearchResponse, Error> {
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<SearchRespo
communities: vec![],
users: vec![],
};
// test with:
// http://lemmy_alpha:8540/federation/c/main
// http://lemmy_alpha:8540/federation/u/lemmy_alpha
// http://lemmy_alpha:8540/federation/p/3
match fetch_remote_object::<SearchAcceptedObjects>(&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<SearchRespo
Ok(response)
}
/// Fetch all posts in the outbox of the given user, and insert them into the database.
fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result<Vec<Post>, Error> {
let outbox_url = Url::parse(&community.get_outbox_url())?;
let outbox = fetch_remote_object::<OrderedCollection>(&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<User_, Error> {
let person = fetch_remote_object::<PersonExt>(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<Community, Error> {
let group = fetch_remote_object::<GroupExt>(apub_id)?;
let cf = CommunityForm::from_group(&group, conn)?;

View file

@ -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<Ext<Group, ApActorProperties>, PublicKeyExtension>;
@ -27,18 +28,22 @@ pub enum EndpointType {
Comment,
}
fn create_apub_response<T>(json: &T) -> HttpResponse<Body>
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
/// headers.
fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
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<u8>, Vec<u8>) {
/// 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))
}

View file

@ -20,6 +20,7 @@ pub struct PostQuery {
post_id: String,
}
/// Return the post json over HTTP.
pub async fn get_apub_post(
info: Path<PostQuery>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
@ -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<Page, Error> {
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<PostForm, Error> {
let oprops = &page.object_props;
let creator_id = Url::parse(&oprops.get_attributed_to_xsd_any_uri().unwrap().to_string())?;

View file

@ -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,

View file

@ -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<UserQuery>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
@ -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<Self, Error> {
let oprops = &person.base.base.object_props;
let aprops = &person.base.extension;

View file

@ -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<UserAcceptedObjects>,
params: web::Query<Params>,
@ -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<HttpResponse, Error> {
let page = create
.create_props
@ -52,6 +54,7 @@ fn handle_create(create: &Create, conn: &PgConnection) -> Result<HttpResponse, E
Ok(HttpResponse::Ok().finish())
}
/// Handle update activities and insert them in the database.
fn handle_update(update: &Update, conn: &PgConnection) -> Result<HttpResponse, Error> {
let page = update
.update_props
@ -67,6 +70,7 @@ fn handle_update(update: &Update, conn: &PgConnection) -> Result<HttpResponse, E
Ok(HttpResponse::Ok().finish())
}
/// Handle accepted follows.
fn handle_accept(_accept: &Accept, _conn: &PgConnection) -> Result<HttpResponse, Error> {
// TODO: make sure that we actually requested a follow
// TODO: at this point, indicate to the user that they are following the community