mirror of
https://github.com/LemmyNet/activitypub-federation-rust.git
synced 2024-06-10 17:29:34 +00:00
Merge remote-tracking branch 'origin/main' into raw-sending
This commit is contained in:
commit
da191f48bb
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "activitypub_federation"
|
||||
version = "0.4.6"
|
||||
version = "0.5.0-beta.1"
|
||||
edition = "2021"
|
||||
description = "High-level Activitypub framework"
|
||||
keywords = ["activitypub", "activitystreams", "federation", "fediverse"]
|
||||
|
|
|
@ -65,7 +65,7 @@ Besides we also need a second struct to represent the data which gets stored in
|
|||
|
||||
```rust
|
||||
# use url::Url;
|
||||
# use chrono::NaiveDateTime;
|
||||
# use chrono::{DateTime, Utc};
|
||||
|
||||
pub struct DbUser {
|
||||
pub id: i32,
|
||||
|
@ -79,7 +79,7 @@ pub struct DbUser {
|
|||
pub local: bool,
|
||||
pub public_key: String,
|
||||
pub private_key: Option<String>,
|
||||
pub last_refreshed_at: NaiveDateTime,
|
||||
pub last_refreshed_at: DateTime<Utc>,
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ pub enum SearchableDbObjects {
|
|||
Post(DbPost)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum SearchableObjects {
|
||||
Person(Person),
|
||||
|
|
|
@ -7,7 +7,7 @@ use activitypub_federation::{
|
|||
protocol::{public_key::PublicKey, verification::verify_domains_match},
|
||||
traits::{ActivityHandler, Actor, Object},
|
||||
};
|
||||
use chrono::{Local, NaiveDateTime};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use url::Url;
|
||||
|
@ -21,7 +21,7 @@ pub struct DbUser {
|
|||
pub public_key: String,
|
||||
// exists only for local users
|
||||
pub private_key: Option<String>,
|
||||
last_refreshed_at: NaiveDateTime,
|
||||
last_refreshed_at: DateTime<Utc>,
|
||||
pub followers: Vec<Url>,
|
||||
pub local: bool,
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ impl DbUser {
|
|||
inbox,
|
||||
public_key: keypair.public_key,
|
||||
private_key: Some(keypair.private_key),
|
||||
last_refreshed_at: Local::now().naive_local(),
|
||||
last_refreshed_at: Utc::now(),
|
||||
followers: vec![],
|
||||
local: true,
|
||||
})
|
||||
|
@ -69,7 +69,7 @@ impl Object for DbUser {
|
|||
type Kind = Person;
|
||||
type Error = Error;
|
||||
|
||||
fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
|
||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||
Some(self.last_refreshed_at)
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,7 @@ impl Object for DbUser {
|
|||
inbox: json.inbox,
|
||||
public_key: json.public_key.public_key_pem,
|
||||
private_key: None,
|
||||
last_refreshed_at: Local::now().naive_local(),
|
||||
last_refreshed_at: Utc::now(),
|
||||
followers: vec![],
|
||||
local: false,
|
||||
})
|
||||
|
|
|
@ -49,9 +49,9 @@ struct MyUrlVerifier();
|
|||
|
||||
#[async_trait]
|
||||
impl UrlVerifier for MyUrlVerifier {
|
||||
async fn verify(&self, url: &Url) -> Result<(), &'static str> {
|
||||
async fn verify(&self, url: &Url) -> Result<(), anyhow::Error> {
|
||||
if url.domain() == Some("malicious.com") {
|
||||
Err("malicious domain")
|
||||
Err(anyhow!("malicious domain"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use activitypub_federation::{
|
|||
protocol::{context::WithContext, public_key::PublicKey, verification::verify_domains_match},
|
||||
traits::{ActivityHandler, Actor, Object},
|
||||
};
|
||||
use chrono::{Local, NaiveDateTime};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use url::Url;
|
||||
|
@ -28,7 +28,7 @@ pub struct DbUser {
|
|||
public_key: String,
|
||||
// exists only for local users
|
||||
private_key: Option<String>,
|
||||
last_refreshed_at: NaiveDateTime,
|
||||
last_refreshed_at: DateTime<Utc>,
|
||||
pub followers: Vec<Url>,
|
||||
pub local: bool,
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ impl DbUser {
|
|||
inbox,
|
||||
public_key: keypair.public_key,
|
||||
private_key: Some(keypair.private_key),
|
||||
last_refreshed_at: Local::now().naive_local(),
|
||||
last_refreshed_at: Utc::now(),
|
||||
followers: vec![],
|
||||
local: true,
|
||||
})
|
||||
|
@ -124,7 +124,7 @@ impl Object for DbUser {
|
|||
type Kind = Person;
|
||||
type Error = Error;
|
||||
|
||||
fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
|
||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||
Some(self.last_refreshed_at)
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ impl Object for DbUser {
|
|||
inbox: json.inbox,
|
||||
public_key: json.public_key.public_key_pem,
|
||||
private_key: None,
|
||||
last_refreshed_at: Local::now().naive_local(),
|
||||
last_refreshed_at: Utc::now(),
|
||||
followers: vec![],
|
||||
local: false,
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ use crate::{
|
|||
protocol::verification::verify_domains_match,
|
||||
traits::{ActivityHandler, Actor},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use derive_builder::Builder;
|
||||
use dyn_clone::{clone_trait_object, DynClone};
|
||||
|
@ -103,9 +104,9 @@ impl<T: Clone> FederationConfig<T> {
|
|||
verify_domains_match(activity.id(), activity.actor())?;
|
||||
self.verify_url_valid(activity.id()).await?;
|
||||
if self.is_local_url(activity.id()) {
|
||||
return Err(Error::UrlVerificationError(
|
||||
"Activity was sent from local instance",
|
||||
));
|
||||
return Err(Error::UrlVerificationError(anyhow!(
|
||||
"Activity was sent from local instance"
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -128,12 +129,12 @@ impl<T: Clone> FederationConfig<T> {
|
|||
"https" => {}
|
||||
"http" => {
|
||||
if !self.allow_http_urls {
|
||||
return Err(Error::UrlVerificationError(
|
||||
"Http urls are only allowed in debug mode",
|
||||
));
|
||||
return Err(Error::UrlVerificationError(anyhow!(
|
||||
"Http urls are only allowed in debug mode"
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::UrlVerificationError("Invalid url scheme")),
|
||||
_ => return Err(Error::UrlVerificationError(anyhow!("Invalid url scheme"))),
|
||||
};
|
||||
|
||||
// Urls which use our local domain are not a security risk, no further verification needed
|
||||
|
@ -142,13 +143,15 @@ impl<T: Clone> FederationConfig<T> {
|
|||
}
|
||||
|
||||
if url.domain().is_none() {
|
||||
return Err(Error::UrlVerificationError("Url must have a domain"));
|
||||
return Err(Error::UrlVerificationError(anyhow!(
|
||||
"Url must have a domain"
|
||||
)));
|
||||
}
|
||||
|
||||
if url.domain() == Some("localhost") && !self.debug {
|
||||
return Err(Error::UrlVerificationError(
|
||||
"Localhost is only allowed in debug mode",
|
||||
));
|
||||
return Err(Error::UrlVerificationError(anyhow!(
|
||||
"Localhost is only allowed in debug mode"
|
||||
)));
|
||||
}
|
||||
|
||||
self.url_verifier
|
||||
|
@ -224,6 +227,7 @@ impl<T: Clone> Deref for FederationConfig<T> {
|
|||
/// # use async_trait::async_trait;
|
||||
/// # use url::Url;
|
||||
/// # use activitypub_federation::config::UrlVerifier;
|
||||
/// # use anyhow::anyhow;
|
||||
/// # #[derive(Clone)]
|
||||
/// # struct DatabaseConnection();
|
||||
/// # async fn get_blocklist(_: &DatabaseConnection) -> Vec<String> {
|
||||
|
@ -236,11 +240,11 @@ impl<T: Clone> Deref for FederationConfig<T> {
|
|||
///
|
||||
/// #[async_trait]
|
||||
/// impl UrlVerifier for Verifier {
|
||||
/// async fn verify(&self, url: &Url) -> Result<(), &'static str> {
|
||||
/// async fn verify(&self, url: &Url) -> Result<(), anyhow::Error> {
|
||||
/// let blocklist = get_blocklist(&self.db_connection).await;
|
||||
/// let domain = url.domain().unwrap().to_string();
|
||||
/// if blocklist.contains(&domain) {
|
||||
/// Err("Domain is blocked")
|
||||
/// Err(anyhow!("Domain is blocked"))
|
||||
/// } else {
|
||||
/// Ok(())
|
||||
/// }
|
||||
|
@ -250,7 +254,7 @@ impl<T: Clone> Deref for FederationConfig<T> {
|
|||
#[async_trait]
|
||||
pub trait UrlVerifier: DynClone + Send {
|
||||
/// Should return Ok iff the given url is valid for processing.
|
||||
async fn verify(&self, url: &Url) -> Result<(), &'static str>;
|
||||
async fn verify(&self, url: &Url) -> Result<(), anyhow::Error>;
|
||||
}
|
||||
|
||||
/// Default URL verifier which does nothing.
|
||||
|
@ -259,7 +263,7 @@ struct DefaultUrlVerifier();
|
|||
|
||||
#[async_trait]
|
||||
impl UrlVerifier for DefaultUrlVerifier {
|
||||
async fn verify(&self, _url: &Url) -> Result<(), &'static str> {
|
||||
async fn verify(&self, _url: &Url) -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ pub enum Error {
|
|||
#[error("Object to be fetched was deleted")]
|
||||
ObjectDeleted,
|
||||
/// url verification error
|
||||
#[error("{0}")]
|
||||
UrlVerificationError(&'static str),
|
||||
#[error("URL failed verification: {0}")]
|
||||
UrlVerificationError(anyhow::Error),
|
||||
/// Incoming activity has invalid digest for body
|
||||
#[error("Incoming activity has invalid digest for body")]
|
||||
ActivityBodyDigestInvalid,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::Object};
|
||||
use anyhow::anyhow;
|
||||
use chrono::{Duration as ChronoDuration, NaiveDateTime, Utc};
|
||||
use chrono::{DateTime, Duration as ChronoDuration, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
|
@ -180,14 +180,14 @@ static ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG: i64 = 20;
|
|||
/// Determines when a remote actor should be refetched from its instance. In release builds, this is
|
||||
/// `ACTOR_REFETCH_INTERVAL_SECONDS` after the last refetch, in debug builds
|
||||
/// `ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG`.
|
||||
fn should_refetch_object(last_refreshed: NaiveDateTime) -> bool {
|
||||
fn should_refetch_object(last_refreshed: DateTime<Utc>) -> bool {
|
||||
let update_interval = if cfg!(debug_assertions) {
|
||||
// avoid infinite loop when fetching community outbox
|
||||
ChronoDuration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG)
|
||||
} else {
|
||||
ChronoDuration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS)
|
||||
};
|
||||
let refresh_limit = Utc::now().naive_utc() - update_interval;
|
||||
let refresh_limit = Utc::now() - update_interval;
|
||||
last_refreshed.lt(&refresh_limit)
|
||||
}
|
||||
|
||||
|
@ -259,10 +259,10 @@ pub mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_should_refetch_object() {
|
||||
let one_second_ago = Utc::now().naive_utc() - ChronoDuration::seconds(1);
|
||||
let one_second_ago = Utc::now() - ChronoDuration::seconds(1);
|
||||
assert!(!should_refetch_object(one_second_ago));
|
||||
|
||||
let two_days_ago = Utc::now().naive_utc() - ChronoDuration::days(2);
|
||||
let two_days_ago = Utc::now() - ChronoDuration::days(2);
|
||||
assert!(should_refetch_object(two_days_ago));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,7 +221,7 @@ mod tests {
|
|||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_webfinger() {
|
||||
async fn test_webfinger() -> Result<(), Error> {
|
||||
let config = FederationConfig::builder()
|
||||
.domain("example.com")
|
||||
.app_data(DbConnection)
|
||||
|
@ -229,12 +229,10 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
let data = config.to_request_data();
|
||||
let res =
|
||||
webfinger_resolve_actor::<DbConnection, DbUser>("LemmyDev@mastodon.social", &data)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
webfinger_resolve_actor::<DbConnection, DbUser>("LemmyDev@mastodon.social", &data).await?;
|
||||
// poa.st is as of 2023-07-14 the largest Pleroma instance
|
||||
let res = webfinger_resolve_actor::<DbConnection, DbUser>("graf@poa.st", &data).await;
|
||||
assert!(res.is_ok());
|
||||
webfinger_resolve_actor::<DbConnection, DbUser>("graf@poa.st", &data).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,3 +115,22 @@ where
|
|||
let inner = T::deserialize(value).unwrap_or_default();
|
||||
Ok(inner)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn deserialize_one_multiple_values() {
|
||||
use crate::protocol::helpers::deserialize_one;
|
||||
use url::Url;
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Note {
|
||||
#[serde(deserialize_with = "deserialize_one")]
|
||||
_to: Url,
|
||||
}
|
||||
|
||||
let note = serde_json::from_str::<Note>(
|
||||
r#"{"_to": ["https://example.com/u/alice", "https://example.com/u/bob"] }"#,
|
||||
);
|
||||
assert!(note.is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Verify that received data is valid
|
||||
|
||||
use crate::error::Error;
|
||||
use anyhow::anyhow;
|
||||
use url::Url;
|
||||
|
||||
/// Check that both urls have the same domain. If not, return UrlVerificationError.
|
||||
|
@ -15,7 +16,7 @@ use url::Url;
|
|||
/// ```
|
||||
pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), Error> {
|
||||
if a.domain() != b.domain() {
|
||||
return Err(Error::UrlVerificationError("Domains do not match"));
|
||||
return Err(Error::UrlVerificationError(anyhow!("Domains do not match")));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -32,7 +33,7 @@ pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), Error> {
|
|||
/// ```
|
||||
pub fn verify_urls_match(a: &Url, b: &Url) -> Result<(), Error> {
|
||||
if a != b {
|
||||
return Err(Error::UrlVerificationError("Urls do not match"));
|
||||
return Err(Error::UrlVerificationError(anyhow!("Urls do not match")));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use crate::{config::Data, protocol::public_key::PublicKey};
|
||||
use async_trait::async_trait;
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Deserialize;
|
||||
use std::{fmt::Debug, ops::Deref};
|
||||
use url::Url;
|
||||
|
@ -11,7 +11,7 @@ use url::Url;
|
|||
///
|
||||
/// ```
|
||||
/// # use activitystreams_kinds::{object::NoteType, public};
|
||||
/// # use chrono::{Local, NaiveDateTime};
|
||||
/// # use chrono::{Local, DateTime, Utc};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use url::Url;
|
||||
/// # use activitypub_federation::protocol::{public_key::PublicKey, helpers::deserialize_one_or_many};
|
||||
|
@ -112,7 +112,7 @@ pub trait Object: Sized + Debug {
|
|||
///
|
||||
/// The object is refetched if `last_refreshed_at` value is more than 24 hours ago. In debug
|
||||
/// mode this is reduced to 20 seconds.
|
||||
fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
|
||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||
None
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue