2020-04-09 19:04:31 +00:00
|
|
|
pub mod activities;
|
2020-04-27 16:57:00 +00:00
|
|
|
pub mod comment;
|
2019-12-19 21:59:13 +00:00
|
|
|
pub mod community;
|
2020-04-15 18:12:25 +00:00
|
|
|
pub mod community_inbox;
|
2020-05-05 14:30:13 +00:00
|
|
|
pub mod extensions;
|
2020-04-10 11:37:35 +00:00
|
|
|
pub mod fetcher;
|
2019-12-19 21:59:13 +00:00
|
|
|
pub mod post;
|
2020-05-06 02:06:24 +00:00
|
|
|
pub mod private_message;
|
2020-04-26 17:20:42 +00:00
|
|
|
pub mod shared_inbox;
|
2019-12-19 21:59:13 +00:00
|
|
|
pub mod user;
|
2020-04-15 18:12:25 +00:00
|
|
|
pub mod user_inbox;
|
2020-04-24 14:04:36 +00:00
|
|
|
|
2020-05-16 14:04:08 +00:00
|
|
|
use crate::{
|
|
|
|
apub::extensions::{
|
|
|
|
group_extensions::GroupExtension,
|
|
|
|
page_extension::PageExtension,
|
|
|
|
signatures::{PublicKey, PublicKeyExtension},
|
|
|
|
},
|
|
|
|
convert_datetime,
|
|
|
|
db::user::User_,
|
2020-05-17 01:09:26 +00:00
|
|
|
routes::webfinger::WebFingerResponse,
|
|
|
|
MentionData,
|
2020-05-16 14:04:08 +00:00
|
|
|
Settings,
|
|
|
|
};
|
2020-04-24 14:04:36 +00:00
|
|
|
use activitystreams::{
|
2020-05-16 14:04:08 +00:00
|
|
|
activity::Follow,
|
|
|
|
actor::{properties::ApActorProperties, Group, Person},
|
2020-06-03 15:10:16 +00:00
|
|
|
object::Page,
|
2020-04-24 14:04:36 +00:00
|
|
|
};
|
2020-05-18 16:15:26 +00:00
|
|
|
use activitystreams_ext::{Ext1, Ext2, Ext3};
|
2020-06-03 15:10:16 +00:00
|
|
|
use activitystreams_new::{
|
|
|
|
base::BaseExt,
|
|
|
|
object::{Tombstone, TombstoneExt},
|
|
|
|
primitives::XsdString,
|
|
|
|
};
|
2020-05-16 14:04:08 +00:00
|
|
|
use actix_web::{body::Body, HttpResponse, Result};
|
|
|
|
use chrono::NaiveDateTime;
|
2020-04-24 14:04:36 +00:00
|
|
|
use diesel::PgConnection;
|
2020-04-24 19:55:54 +00:00
|
|
|
use failure::Error;
|
|
|
|
use isahc::prelude::*;
|
|
|
|
use log::debug;
|
2020-05-16 14:04:08 +00:00
|
|
|
use serde::Serialize;
|
2020-04-24 19:55:54 +00:00
|
|
|
use url::Url;
|
2020-04-24 14:04:36 +00:00
|
|
|
|
2020-05-18 16:15:26 +00:00
|
|
|
type GroupExt = Ext3<Group, GroupExtension, ApActorProperties, PublicKeyExtension>;
|
|
|
|
type PersonExt = Ext2<Person, ApActorProperties, PublicKeyExtension>;
|
|
|
|
type PageExt = Ext1<Page, PageExtension>;
|
2020-04-03 05:02:43 +00:00
|
|
|
|
2020-04-21 20:45:01 +00:00
|
|
|
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
|
2020-04-07 15:29:23 +00:00
|
|
|
|
2020-04-08 16:22:44 +00:00
|
|
|
pub enum EndpointType {
|
|
|
|
Community,
|
|
|
|
User,
|
|
|
|
Post,
|
|
|
|
Comment,
|
2020-05-06 02:06:24 +00:00
|
|
|
PrivateMessage,
|
2020-04-08 16:22:44 +00:00
|
|
|
}
|
|
|
|
|
2020-04-17 15:33:55 +00:00
|
|
|
/// 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>
|
2020-03-19 01:16:17 +00:00
|
|
|
where
|
2020-04-17 15:33:55 +00:00
|
|
|
T: Serialize,
|
2020-03-19 01:16:17 +00:00
|
|
|
{
|
2020-03-16 17:30:25 +00:00
|
|
|
HttpResponse::Ok()
|
2020-04-07 15:29:23 +00:00
|
|
|
.content_type(APUB_JSON_CONTENT_TYPE)
|
2020-04-17 15:33:55 +00:00
|
|
|
.json(data)
|
2019-12-19 21:59:13 +00:00
|
|
|
}
|
2020-04-29 19:10:50 +00:00
|
|
|
|
2020-04-29 14:51:25 +00:00
|
|
|
fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<Body>
|
|
|
|
where
|
|
|
|
T: Serialize,
|
|
|
|
{
|
|
|
|
HttpResponse::Gone()
|
|
|
|
.content_type(APUB_JSON_CONTENT_TYPE)
|
|
|
|
.json(data)
|
|
|
|
}
|
2019-12-19 21:59:13 +00:00
|
|
|
|
2020-05-14 11:23:56 +00:00
|
|
|
/// Generates the ActivityPub ID for a given object type and ID.
|
2020-04-03 04:12:05 +00:00
|
|
|
pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url {
|
2020-03-16 18:19:04 +00:00
|
|
|
let point = match endpoint_type {
|
2020-04-07 15:34:44 +00:00
|
|
|
EndpointType::Community => "c",
|
|
|
|
EndpointType::User => "u",
|
2020-04-21 20:45:01 +00:00
|
|
|
EndpointType::Post => "post",
|
2020-04-14 19:12:19 +00:00
|
|
|
EndpointType::Comment => "comment",
|
2020-05-06 02:06:24 +00:00
|
|
|
EndpointType::PrivateMessage => "private_message",
|
2020-03-16 18:19:04 +00:00
|
|
|
};
|
|
|
|
|
2020-03-12 00:01:25 +00:00
|
|
|
Url::parse(&format!(
|
2020-04-21 20:45:01 +00:00
|
|
|
"{}://{}/{}/{}",
|
2020-02-29 17:38:47 +00:00
|
|
|
get_apub_protocol_string(),
|
2019-12-19 21:59:13 +00:00
|
|
|
Settings::get().hostname,
|
|
|
|
point,
|
2020-03-16 18:19:04 +00:00
|
|
|
name
|
2020-03-12 00:01:25 +00:00
|
|
|
))
|
|
|
|
.unwrap()
|
2019-12-19 21:59:13 +00:00
|
|
|
}
|
2020-02-29 17:38:47 +00:00
|
|
|
|
2020-03-18 15:08:08 +00:00
|
|
|
pub fn get_apub_protocol_string() -> &'static str {
|
2020-03-18 21:09:00 +00:00
|
|
|
if Settings::get().federation.tls_enabled {
|
|
|
|
"https"
|
|
|
|
} else {
|
|
|
|
"http"
|
|
|
|
}
|
2020-02-29 17:38:47 +00:00
|
|
|
}
|
2020-04-03 04:12:05 +00:00
|
|
|
|
2020-04-17 17:34:18 +00:00
|
|
|
// Checks if the ID has a valid format, correct scheme, and is in the whitelist.
|
2020-04-18 18:54:20 +00:00
|
|
|
fn is_apub_id_valid(apub_id: &Url) -> bool {
|
|
|
|
if apub_id.scheme() != get_apub_protocol_string() {
|
2020-04-17 17:34:18 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
let whitelist: Vec<String> = Settings::get()
|
|
|
|
.federation
|
|
|
|
.instance_whitelist
|
|
|
|
.split(',')
|
|
|
|
.map(|d| d.to_string())
|
|
|
|
.collect();
|
2020-04-18 18:54:20 +00:00
|
|
|
match apub_id.domain() {
|
2020-04-17 17:34:18 +00:00
|
|
|
Some(d) => whitelist.contains(&d.to_owned()),
|
|
|
|
None => false,
|
|
|
|
}
|
|
|
|
}
|
2020-04-24 19:55:54 +00:00
|
|
|
|
2020-04-29 14:51:25 +00:00
|
|
|
pub trait ToApub {
|
|
|
|
type Response;
|
|
|
|
fn to_apub(&self, conn: &PgConnection) -> Result<Self::Response, Error>;
|
2020-05-01 14:07:38 +00:00
|
|
|
fn to_tombstone(&self) -> Result<Tombstone, Error>;
|
2020-04-28 17:46:25 +00:00
|
|
|
}
|
|
|
|
|
2020-05-03 14:22:25 +00:00
|
|
|
/// Updated is actually the deletion time
|
2020-04-29 14:51:25 +00:00
|
|
|
fn create_tombstone(
|
|
|
|
deleted: bool,
|
|
|
|
object_id: &str,
|
|
|
|
updated: Option<NaiveDateTime>,
|
2020-04-29 19:10:50 +00:00
|
|
|
former_type: String,
|
2020-04-29 14:51:25 +00:00
|
|
|
) -> Result<Tombstone, Error> {
|
|
|
|
if deleted {
|
|
|
|
if let Some(updated) = updated {
|
2020-06-03 15:10:16 +00:00
|
|
|
let mut tombstone = Tombstone::new();
|
|
|
|
tombstone.set_id(object_id.parse()?);
|
|
|
|
tombstone.set_former_type(former_type.parse::<XsdString>()?);
|
|
|
|
tombstone.set_deleted(convert_datetime(updated).into());
|
2020-05-01 14:07:38 +00:00
|
|
|
Ok(tombstone)
|
|
|
|
} else {
|
|
|
|
Err(format_err!(
|
|
|
|
"Cant convert to tombstone because updated time was None."
|
|
|
|
))
|
2020-04-28 17:46:25 +00:00
|
|
|
}
|
2020-04-29 14:51:25 +00:00
|
|
|
} else {
|
|
|
|
Err(format_err!(
|
|
|
|
"Cant convert object to tombstone if it wasnt deleted"
|
|
|
|
))
|
2020-04-28 17:46:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 21:30:27 +00:00
|
|
|
pub trait FromApub {
|
|
|
|
type ApubType;
|
|
|
|
fn from_apub(apub: &Self::ApubType, conn: &PgConnection) -> Result<Self, Error>
|
2020-04-24 19:55:54 +00:00
|
|
|
where
|
|
|
|
Self: Sized;
|
|
|
|
}
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
pub trait ApubObjectType {
|
|
|
|
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
|
|
|
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
2020-05-01 14:07:38 +00:00
|
|
|
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
2020-05-01 19:01:29 +00:00
|
|
|
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
2020-05-03 14:00:59 +00:00
|
|
|
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
|
|
|
|
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
|
2020-04-27 16:57:00 +00:00
|
|
|
}
|
|
|
|
|
2020-04-28 02:46:09 +00:00
|
|
|
pub trait ApubLikeableType {
|
|
|
|
fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
|
|
|
fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
2020-05-04 00:34:04 +00:00
|
|
|
fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
2020-04-28 02:46:09 +00:00
|
|
|
}
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
pub fn get_shared_inbox(actor_id: &str) -> String {
|
|
|
|
let url = Url::parse(actor_id).unwrap();
|
|
|
|
format!(
|
|
|
|
"{}://{}{}/inbox",
|
|
|
|
&url.scheme(),
|
|
|
|
&url.host_str().unwrap(),
|
|
|
|
if let Some(port) = url.port() {
|
|
|
|
format!(":{}", port)
|
|
|
|
} else {
|
|
|
|
"".to_string()
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-04-24 19:55:54 +00:00
|
|
|
pub trait ActorType {
|
|
|
|
fn actor_id(&self) -> String;
|
|
|
|
|
2020-04-25 02:34:51 +00:00
|
|
|
fn public_key(&self) -> String;
|
2020-05-14 13:38:07 +00:00
|
|
|
fn private_key(&self) -> String;
|
2020-04-25 02:34:51 +00:00
|
|
|
|
2020-04-26 17:20:42 +00:00
|
|
|
// These two have default impls, since currently a community can't follow anything,
|
|
|
|
// and a user can't be followed (yet)
|
|
|
|
#[allow(unused_variables)]
|
2020-05-04 02:41:45 +00:00
|
|
|
fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>;
|
|
|
|
fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>;
|
2020-04-26 17:20:42 +00:00
|
|
|
|
|
|
|
#[allow(unused_variables)]
|
2020-05-04 02:41:45 +00:00
|
|
|
fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error>;
|
2020-04-26 17:20:42 +00:00
|
|
|
|
2020-05-01 14:07:38 +00:00
|
|
|
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
2020-05-01 19:01:29 +00:00
|
|
|
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
|
2020-04-28 17:46:25 +00:00
|
|
|
|
2020-05-03 14:00:59 +00:00
|
|
|
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
|
|
|
|
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
/// For a given community, returns the inboxes of all followers.
|
2020-05-04 02:41:45 +00:00
|
|
|
fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error>;
|
2020-04-27 16:57:00 +00:00
|
|
|
|
2020-04-26 17:20:42 +00:00
|
|
|
// TODO move these to the db rows
|
2020-04-24 19:55:54 +00:00
|
|
|
fn get_inbox_url(&self) -> String {
|
|
|
|
format!("{}/inbox", &self.actor_id())
|
|
|
|
}
|
2020-04-26 17:20:42 +00:00
|
|
|
|
|
|
|
fn get_shared_inbox_url(&self) -> String {
|
2020-04-27 16:57:00 +00:00
|
|
|
get_shared_inbox(&self.actor_id())
|
2020-04-26 17:20:42 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 19:55:54 +00:00
|
|
|
fn get_outbox_url(&self) -> String {
|
|
|
|
format!("{}/outbox", &self.actor_id())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_followers_url(&self) -> String {
|
|
|
|
format!("{}/followers", &self.actor_id())
|
|
|
|
}
|
2020-04-26 17:20:42 +00:00
|
|
|
|
2020-04-24 19:55:54 +00:00
|
|
|
fn get_following_url(&self) -> String {
|
|
|
|
format!("{}/following", &self.actor_id())
|
|
|
|
}
|
2020-04-26 17:20:42 +00:00
|
|
|
|
2020-04-24 19:55:54 +00:00
|
|
|
fn get_liked_url(&self) -> String {
|
|
|
|
format!("{}/liked", &self.actor_id())
|
|
|
|
}
|
2020-04-25 02:34:51 +00:00
|
|
|
|
|
|
|
fn get_public_key_ext(&self) -> PublicKeyExtension {
|
|
|
|
PublicKey {
|
|
|
|
id: format!("{}#main-key", self.actor_id()),
|
|
|
|
owner: self.actor_id(),
|
|
|
|
public_key_pem: self.public_key(),
|
|
|
|
}
|
|
|
|
.to_ext()
|
|
|
|
}
|
2020-04-24 19:55:54 +00:00
|
|
|
}
|
2020-05-15 16:36:11 +00:00
|
|
|
|
|
|
|
pub fn fetch_webfinger_url(mention: &MentionData) -> Result<String, Error> {
|
|
|
|
let fetch_url = format!(
|
|
|
|
"{}://{}/.well-known/webfinger?resource=acct:{}@{}",
|
|
|
|
get_apub_protocol_string(),
|
|
|
|
mention.domain,
|
|
|
|
mention.name,
|
|
|
|
mention.domain
|
|
|
|
);
|
|
|
|
debug!("Fetching webfinger url: {}", &fetch_url);
|
|
|
|
let text = isahc::get(&fetch_url)?.text()?;
|
|
|
|
let res: WebFingerResponse = serde_json::from_str(&text)?;
|
|
|
|
let link = res
|
|
|
|
.links
|
|
|
|
.iter()
|
|
|
|
.find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
|
|
|
|
.ok_or_else(|| format_err!("No application/activity+json link found."))?;
|
|
|
|
link
|
|
|
|
.href
|
|
|
|
.to_owned()
|
|
|
|
.ok_or_else(|| format_err!("No href found."))
|
|
|
|
}
|