1 use crate::fetcher::post_or_comment::PostOrComment;
2 use activitypub_federation::config::{Data, UrlVerifier};
3 use async_trait::async_trait;
4 use lemmy_api_common::context::LemmyContext;
7 activity::{Activity, ActivityInsertForm},
14 use lemmy_utils::{error::LemmyError, settings::structs::Settings};
15 use once_cell::sync::Lazy;
20 pub(crate) mod activity_lists;
22 pub(crate) mod collections;
25 pub(crate) mod mentions;
29 pub const FEDERATION_HTTP_FETCH_LIMIT: u32 = 50;
31 static CONTEXT: Lazy<Vec<serde_json::Value>> = Lazy::new(|| {
32 serde_json::from_str(include_str!("../assets/lemmy/context.json")).expect("parse context")
36 pub struct VerifyUrlData(pub DbPool);
39 impl UrlVerifier for VerifyUrlData {
40 async fn verify(&self, url: &Url) -> Result<(), &'static str> {
41 let local_site_data = fetch_local_site_data(&self.0)
43 .expect("read local site data");
44 check_apub_id_valid(url, &local_site_data)?;
49 /// Checks if the ID is allowed for sending or receiving.
51 /// In particular, it checks for:
52 /// - federation being enabled (if its disabled, only local URLs are allowed)
53 /// - the correct scheme (either http or https)
54 /// - URL being in the allowlist (if it is active)
55 /// - URL not being in the blocklist (if it is active)
57 /// `use_strict_allowlist` should be true only when parsing a remote community, or when parsing a
58 /// post/comment in a local community.
59 #[tracing::instrument(skip(local_site_data))]
60 fn check_apub_id_valid(apub_id: &Url, local_site_data: &LocalSiteData) -> Result<(), &'static str> {
61 let domain = apub_id.domain().expect("apud id has domain").to_string();
66 .map(|l| l.federation_enabled)
69 return Err("Federation disabled");
75 .any(|i| domain.eq(&i.domain))
77 return Err("Domain is blocked");
80 // Only check this if there are instances in the allowlist
81 if !local_site_data.allowed_instances.is_empty()
85 .any(|i| domain.eq(&i.domain))
87 return Err("Domain is not in allowlist");
94 pub(crate) struct LocalSiteData {
95 local_site: Option<LocalSite>,
96 allowed_instances: Vec<Instance>,
97 blocked_instances: Vec<Instance>,
100 pub(crate) async fn fetch_local_site_data(
102 ) -> Result<LocalSiteData, diesel::result::Error> {
103 // LocalSite may be missing
104 let local_site = LocalSite::read(pool).await.ok();
105 let allowed_instances = Instance::allowlist(pool).await?;
106 let blocked_instances = Instance::blocklist(pool).await?;
115 #[tracing::instrument(skip(settings, local_site_data))]
116 pub(crate) fn check_apub_id_valid_with_strictness(
119 local_site_data: &LocalSiteData,
121 ) -> Result<(), LemmyError> {
122 let domain = apub_id.domain().expect("apud id has domain").to_string();
123 let local_instance = settings
124 .get_hostname_without_port()
125 .expect("local hostname is valid");
126 if domain == local_instance {
129 check_apub_id_valid(apub_id, local_site_data).map_err(LemmyError::from_message)?;
131 // Only check allowlist if this is a community, and there are instances in the allowlist
132 if is_strict && !local_site_data.allowed_instances.is_empty() {
133 // need to allow this explicitly because apub receive might contain objects from our local
135 let mut allowed_and_local = local_site_data
138 .map(|i| i.domain.clone())
139 .collect::<Vec<String>>();
140 let local_instance = settings
141 .get_hostname_without_port()
142 .expect("local hostname is valid");
143 allowed_and_local.push(local_instance);
145 let domain = apub_id.domain().expect("apud id has domain").to_string();
146 if !allowed_and_local.contains(&domain) {
147 return Err(LemmyError::from_message(
148 "Federation forbidden by strict allowlist",
155 /// Store a sent or received activity in the database.
157 /// Stored activities are served over the HTTP endpoint `GET /activities/{type_}/{id}`. This also
158 /// ensures that the same activity cannot be received more than once.
159 #[tracing::instrument(skip(data, activity))]
160 async fn insert_activity<T>(
165 data: &Data<LemmyContext>,
166 ) -> Result<(), LemmyError>
170 let ap_id = ap_id.clone().into();
171 let form = ActivityInsertForm {
173 data: serde_json::to_value(activity)?,
175 sensitive: Some(sensitive),
178 Activity::create(data.pool(), &form).await?;
182 #[async_trait::async_trait]
183 pub trait SendActivity: Sync {
184 type Response: Sync + Send;
186 async fn send_activity(
188 _response: &Self::Response,
189 _context: &Data<LemmyContext>,
190 ) -> Result<(), LemmyError> {