]> Untitled Git - lemmy.git/blob - server/src/apub/mod.rs
9c02d1071fb20a2b18b05221ff02aaec989b7e8c
[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::user::{UserForm, User_};
43 use crate::db::user_view::UserView;
44 use crate::db::{Crud, Followable, SearchType};
45 use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
46 use crate::routes::{ChatServerParam, DbPoolParam};
47 use crate::{convert_datetime, naive_now, Settings};
48
49 use activities::accept_follow;
50 use fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user};
51 use signatures::verify;
52 use signatures::{sign, PublicKey, PublicKeyExtension};
53
54 type GroupExt = Ext<Ext<Group, ApActorProperties>, PublicKeyExtension>;
55 type PersonExt = Ext<Ext<Person, ApActorProperties>, PublicKeyExtension>;
56
57 pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
58
59 pub enum EndpointType {
60   Community,
61   User,
62   Post,
63   Comment,
64 }
65
66 /// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
67 /// headers.
68 fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
69 where
70   T: Serialize,
71 {
72   HttpResponse::Ok()
73     .content_type(APUB_JSON_CONTENT_TYPE)
74     .json(data)
75 }
76
77 /// Generates the ActivityPub ID for a given object type and name.
78 ///
79 /// TODO: we will probably need to change apub endpoint urls so that html and activity+json content
80 ///       types are handled at the same endpoint, so that you can copy the url into mastodon search
81 ///       and have it fetch the object.
82 pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url {
83   let point = match endpoint_type {
84     EndpointType::Community => "c",
85     EndpointType::User => "u",
86     EndpointType::Post => "post",
87     // TODO I have to change this else my update advanced_migrations crashes the
88     // server if a comment exists.
89     EndpointType::Comment => "comment",
90   };
91
92   Url::parse(&format!(
93     "{}://{}/{}/{}",
94     get_apub_protocol_string(),
95     Settings::get().hostname,
96     point,
97     name
98   ))
99   .unwrap()
100 }
101
102 pub fn get_apub_protocol_string() -> &'static str {
103   if Settings::get().federation.tls_enabled {
104     "https"
105   } else {
106     "http"
107   }
108 }
109
110 // Checks if the ID has a valid format, correct scheme, and is in the whitelist.
111 fn is_apub_id_valid(apub_id: &Url) -> bool {
112   if apub_id.scheme() != get_apub_protocol_string() {
113     return false;
114   }
115
116   let whitelist: Vec<String> = Settings::get()
117     .federation
118     .instance_whitelist
119     .split(',')
120     .map(|d| d.to_string())
121     .collect();
122   match apub_id.domain() {
123     Some(d) => whitelist.contains(&d.to_owned()),
124     None => false,
125   }
126 }
127
128 // TODO Not sure good names for these
129 pub trait ToApub {
130   type Response;
131   fn to_apub(&self, conn: &PgConnection) -> Result<Self::Response, Error>;
132 }
133
134 pub trait FromApub {
135   type ApubType;
136   fn from_apub(apub: &Self::ApubType, conn: &PgConnection) -> Result<Self, Error>
137   where
138     Self: Sized;
139 }
140
141 pub trait ActorType {
142   fn actor_id(&self) -> String;
143
144   fn public_key(&self) -> String;
145
146   fn get_inbox_url(&self) -> String {
147     format!("{}/inbox", &self.actor_id())
148   }
149   fn get_outbox_url(&self) -> String {
150     format!("{}/outbox", &self.actor_id())
151   }
152
153   fn get_followers_url(&self) -> String {
154     format!("{}/followers", &self.actor_id())
155   }
156   fn get_following_url(&self) -> String {
157     format!("{}/following", &self.actor_id())
158   }
159   fn get_liked_url(&self) -> String {
160     format!("{}/liked", &self.actor_id())
161   }
162
163   fn get_public_key_ext(&self) -> PublicKeyExtension {
164     PublicKey {
165       id: format!("{}#main-key", self.actor_id()),
166       owner: self.actor_id(),
167       public_key_pem: self.public_key(),
168     }
169     .to_ext()
170   }
171 }