]> Untitled Git - lemmy.git/blob - server/src/apub/mod.rs
Adding back in post fetching.
[lemmy.git] / server / src / apub / mod.rs
1 pub mod activities;
2 pub mod community;
3 pub mod community_inbox;
4 pub mod fetcher;
5 pub mod post;
6 pub mod signatures;
7 pub mod user;
8 pub mod user_inbox;
9
10 use activitystreams::{
11   activity::{Accept, Create, Follow, Update},
12   actor::{properties::ApActorProperties, Actor, Group, Person},
13   collection::UnorderedCollection,
14   context,
15   ext::{Ext, Extensible, Extension},
16   object::{properties::ObjectProperties, Page},
17   public, BaseBox,
18 };
19 use actix_web::body::Body;
20 use actix_web::web::Path;
21 use actix_web::{web, HttpRequest, HttpResponse, Result};
22 use diesel::result::Error::NotFound;
23 use diesel::PgConnection;
24 use failure::Error;
25 use failure::_core::fmt::Debug;
26 use http::request::Builder;
27 use http_signature_normalization::Config;
28 use isahc::prelude::*;
29 use log::debug;
30 use openssl::hash::MessageDigest;
31 use openssl::sign::{Signer, Verifier};
32 use openssl::{pkey::PKey, rsa::Rsa};
33 use serde::{Deserialize, Serialize};
34 use std::collections::BTreeMap;
35 use std::time::Duration;
36 use url::Url;
37
38 use crate::api::site::SearchResponse;
39 use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm};
40 use crate::db::community_view::{CommunityFollowerView, CommunityView};
41 use crate::db::post::{Post, PostForm};
42 use crate::db::post_view::PostView;
43 use crate::db::user::{UserForm, User_};
44 use crate::db::user_view::UserView;
45 use crate::db::{Crud, Followable, SearchType};
46 use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
47 use crate::routes::{ChatServerParam, DbPoolParam};
48 use crate::{convert_datetime, naive_now, Settings};
49
50 use activities::accept_follow;
51 use fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user};
52 use signatures::verify;
53 use signatures::{sign, PublicKey, PublicKeyExtension};
54
55 type GroupExt = Ext<Ext<Group, ApActorProperties>, PublicKeyExtension>;
56 type PersonExt = Ext<Ext<Person, ApActorProperties>, PublicKeyExtension>;
57
58 pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
59
60 pub enum EndpointType {
61   Community,
62   User,
63   Post,
64   Comment,
65 }
66
67 /// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
68 /// headers.
69 fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
70 where
71   T: Serialize,
72 {
73   HttpResponse::Ok()
74     .content_type(APUB_JSON_CONTENT_TYPE)
75     .json(data)
76 }
77
78 /// Generates the ActivityPub ID for a given object type and name.
79 ///
80 /// TODO: we will probably need to change apub endpoint urls so that html and activity+json content
81 ///       types are handled at the same endpoint, so that you can copy the url into mastodon search
82 ///       and have it fetch the object.
83 pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url {
84   let point = match endpoint_type {
85     EndpointType::Community => "c",
86     EndpointType::User => "u",
87     EndpointType::Post => "post",
88     // TODO I have to change this else my update advanced_migrations crashes the
89     // server if a comment exists.
90     EndpointType::Comment => "comment",
91   };
92
93   Url::parse(&format!(
94     "{}://{}/{}/{}",
95     get_apub_protocol_string(),
96     Settings::get().hostname,
97     point,
98     name
99   ))
100   .unwrap()
101 }
102
103 pub fn get_apub_protocol_string() -> &'static str {
104   if Settings::get().federation.tls_enabled {
105     "https"
106   } else {
107     "http"
108   }
109 }
110
111 // Checks if the ID has a valid format, correct scheme, and is in the whitelist.
112 fn is_apub_id_valid(apub_id: &Url) -> bool {
113   if apub_id.scheme() != get_apub_protocol_string() {
114     return false;
115   }
116
117   let whitelist: Vec<String> = Settings::get()
118     .federation
119     .instance_whitelist
120     .split(',')
121     .map(|d| d.to_string())
122     .collect();
123   match apub_id.domain() {
124     Some(d) => whitelist.contains(&d.to_owned()),
125     None => false,
126   }
127 }
128
129 // TODO Not sure good names for these
130 pub trait ToApub {
131   type Response;
132   fn to_apub(&self, conn: &PgConnection) -> Result<Self::Response, Error>;
133 }
134
135 pub trait FromApub {
136   type ApubType;
137   fn from_apub(apub: &Self::ApubType, conn: &PgConnection) -> Result<Self, Error>
138   where
139     Self: Sized;
140 }
141
142 pub trait ActorType {
143   fn actor_id(&self) -> String;
144
145   fn public_key(&self) -> String;
146
147   fn get_inbox_url(&self) -> String {
148     format!("{}/inbox", &self.actor_id())
149   }
150   fn get_outbox_url(&self) -> String {
151     format!("{}/outbox", &self.actor_id())
152   }
153
154   fn get_followers_url(&self) -> String {
155     format!("{}/followers", &self.actor_id())
156   }
157   fn get_following_url(&self) -> String {
158     format!("{}/following", &self.actor_id())
159   }
160   fn get_liked_url(&self) -> String {
161     format!("{}/liked", &self.actor_id())
162   }
163
164   fn get_public_key_ext(&self) -> PublicKeyExtension {
165     PublicKey {
166       id: format!("{}#main-key", self.actor_id()),
167       owner: self.actor_id(),
168       public_key_pem: self.public_key(),
169     }
170     .to_ext()
171   }
172 }