mirror of
https://github.com/LemmyNet/activitypub-federation-rust.git
synced 2024-09-26 13:29:59 +00:00
Compare commits
4 commits
1b46dd6f80
...
779313ac22
Author | SHA1 | Date | |
---|---|---|---|
|
779313ac22 | ||
|
7def01a19a | ||
|
a2ac97db98 | ||
|
5402bc9c19 |
13 changed files with 130 additions and 46 deletions
|
@ -1,54 +1,56 @@
|
|||
pipeline:
|
||||
variables:
|
||||
- &rust_image "rust:1.77-bullseye"
|
||||
|
||||
steps:
|
||||
cargo_fmt:
|
||||
image: rustdocker/rust:nightly
|
||||
commands:
|
||||
- /root/.cargo/bin/cargo fmt -- --check
|
||||
|
||||
cargo_check:
|
||||
image: rust:1.70-bullseye
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
- cargo check --all-features --all-targets
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
cargo_clippy:
|
||||
image: rust:1.70-bullseye
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
- rustup component add clippy
|
||||
- cargo clippy --all-targets --all-features --
|
||||
-D warnings -D deprecated -D clippy::perf -D clippy::complexity
|
||||
-D clippy::dbg_macro -D clippy::inefficient_to_string
|
||||
-D clippy::items-after-statements -D clippy::implicit_clone
|
||||
-D clippy::wildcard_imports -D clippy::cast_lossless
|
||||
-D clippy::manual_string_new -D clippy::redundant_closure_for_method_calls
|
||||
- cargo clippy --all-features -- -D clippy::unwrap_used
|
||||
- cargo clippy --all-targets --all-features
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
cargo_test:
|
||||
image: rust:1.70-bullseye
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
- cargo test --all-features --no-fail-fast
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
cargo_doc:
|
||||
image: rust:1.70-bullseye
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
- cargo doc --all-features
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
cargo_run_actix_example:
|
||||
image: rust:1.70-bullseye
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
- cargo run --example local_federation actix-web
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
cargo_run_axum_example:
|
||||
image: rust:1.70-bullseye
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
- cargo run --example local_federation axum
|
||||
when:
|
||||
- event: pull_request
|
||||
|
|
19
Cargo.toml
19
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "activitypub_federation"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
edition = "2021"
|
||||
description = "High-level Activitypub framework"
|
||||
keywords = ["activitypub", "activitystreams", "federation", "fediverse"]
|
||||
|
@ -14,6 +14,23 @@ actix-web = ["dep:actix-web"]
|
|||
axum = ["dep:axum", "dep:tower", "dep:hyper", "dep:http-body-util"]
|
||||
diesel = ["dep:diesel"]
|
||||
|
||||
[lints.rust]
|
||||
warnings = "deny"
|
||||
deprecated = "deny"
|
||||
|
||||
[lints.clippy]
|
||||
perf = "deny"
|
||||
complexity = "deny"
|
||||
dbg_macro = "deny"
|
||||
inefficient_to_string = "deny"
|
||||
items-after-statements = "deny"
|
||||
implicit_clone = "deny"
|
||||
wildcard_imports = "deny"
|
||||
cast_lossless = "deny"
|
||||
manual_string_new = "deny"
|
||||
redundant_closure_for_method_calls = "deny"
|
||||
unwrap_used = "deny"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.34", features = ["clock"], default-features = false }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
database::Database,
|
||||
http::{http_get_user, http_post_user_inbox, webfinger},
|
||||
|
|
|
@ -28,6 +28,7 @@ pub async fn new_instance(
|
|||
.domain(hostname)
|
||||
.signed_fetch_actor(&system_user)
|
||||
.app_data(database)
|
||||
.url_verifier(Box::new(MyUrlVerifier()))
|
||||
.debug(true)
|
||||
.build()
|
||||
.await?;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
instance::{listen, new_instance, Webserver},
|
||||
objects::post::DbPost,
|
||||
|
|
|
@ -416,6 +416,7 @@ async fn retry<T, E: Display + Debug, F: Future<Output = Result<T, E>>, A: FnMut
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::http_signatures::generate_actor_keypair;
|
||||
|
|
|
@ -12,14 +12,17 @@ use crate::{
|
|||
};
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
use http::StatusCode;
|
||||
use httpdate::fmt_http_date;
|
||||
use itertools::Itertools;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
use reqwest::{
|
||||
header::{HeaderMap, HeaderName, HeaderValue},
|
||||
Response,
|
||||
};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
self,
|
||||
fmt::{Debug, Display},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
@ -90,20 +93,30 @@ impl SendActivityTask {
|
|||
)
|
||||
.await?;
|
||||
let response = client.execute(request).await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
match response {
|
||||
o if o.status().is_success() => {
|
||||
/// Based on the HTTP status code determines if an activity was delivered successfully. In that case
|
||||
/// Ok is returned. Otherwise it returns Err and the activity send should be retried later.
|
||||
///
|
||||
/// Equivalent code in mastodon: https://github.com/mastodon/mastodon/blob/v4.2.8/app/helpers/jsonld_helper.rb#L215-L217
|
||||
async fn handle_response(&self, response: Response) -> Result<(), Error> {
|
||||
match response.status() {
|
||||
status if status.is_success() => {
|
||||
debug!("Activity {self} delivered successfully");
|
||||
Ok(())
|
||||
}
|
||||
o if o.status().is_client_error() => {
|
||||
let text = o.text_limited().await?;
|
||||
status
|
||||
if status.is_client_error()
|
||||
&& status != StatusCode::REQUEST_TIMEOUT
|
||||
&& status != StatusCode::TOO_MANY_REQUESTS =>
|
||||
{
|
||||
let text = response.text_limited().await?;
|
||||
debug!("Activity {self} was rejected, aborting: {text}");
|
||||
Ok(())
|
||||
}
|
||||
o => {
|
||||
let status = o.status();
|
||||
let text = o.text_limited().await?;
|
||||
status => {
|
||||
let text = response.text_limited().await?;
|
||||
|
||||
Err(Error::Other(format!(
|
||||
"Activity {self} failure with status {status}: {text}",
|
||||
|
@ -211,11 +224,10 @@ pub(crate) fn generate_request_headers(inbox_url: &Url) -> HeaderMap {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{config::FederationConfig, http_signatures::generate_actor_keypair};
|
||||
use bytes::Bytes;
|
||||
use http::StatusCode;
|
||||
use std::{
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
time::Instant,
|
||||
|
@ -289,4 +301,48 @@ mod tests {
|
|||
info!("Queue Sent: {:?}", start.elapsed());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_response() {
|
||||
let keypair = generate_actor_keypair().unwrap();
|
||||
let message = SendActivityTask {
|
||||
actor_id: "http://localhost:8001".parse().unwrap(),
|
||||
activity_id: "http://localhost:8001/activity".parse().unwrap(),
|
||||
activity: "{}".into(),
|
||||
inbox: "http://localhost:8001".parse().unwrap(),
|
||||
private_key: keypair.private_key().unwrap(),
|
||||
http_signature_compat: true,
|
||||
};
|
||||
|
||||
let res = |status| {
|
||||
http::Response::builder()
|
||||
.status(status)
|
||||
.body(vec![])
|
||||
.unwrap()
|
||||
.into()
|
||||
};
|
||||
|
||||
assert!(message.handle_response(res(StatusCode::OK)).await.is_ok());
|
||||
assert!(message
|
||||
.handle_response(res(StatusCode::BAD_REQUEST))
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
assert!(message
|
||||
.handle_response(res(StatusCode::MOVED_PERMANENTLY))
|
||||
.await
|
||||
.is_err());
|
||||
assert!(message
|
||||
.handle_response(res(StatusCode::REQUEST_TIMEOUT))
|
||||
.await
|
||||
.is_err());
|
||||
assert!(message
|
||||
.handle_response(res(StatusCode::TOO_MANY_REQUESTS))
|
||||
.await
|
||||
.is_err());
|
||||
assert!(message
|
||||
.handle_response(res(StatusCode::INTERNAL_SERVER_ERROR))
|
||||
.await
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
|
|
|
@ -69,10 +69,17 @@ pub async fn fetch_object_http<T: Clone, Kind: DeserializeOwned>(
|
|||
return Err(Error::FetchInvalidContentType(res.url));
|
||||
}
|
||||
|
||||
// Ensure id field matches final url
|
||||
// Ensure id field matches final url after redirect
|
||||
if res.object_id.as_ref() != Some(&res.url) {
|
||||
return Err(Error::FetchWrongId(res.url));
|
||||
}
|
||||
|
||||
// Dont allow fetching local object. Only check this after the request as a local url
|
||||
// may redirect to a remote object.
|
||||
if data.config.is_local_url(&res.url) {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
|
@ -84,8 +91,6 @@ async fn fetch_object_http_with_accept<T: Clone, Kind: DeserializeOwned>(
|
|||
content_type: &HeaderValue,
|
||||
) -> Result<FetchObjectResponse<Kind>, Error> {
|
||||
let config = &data.config;
|
||||
// dont fetch local objects this way
|
||||
debug_assert!(url.domain() != Some(&config.domain));
|
||||
config.verify_url_valid(url).await?;
|
||||
info!("Fetching remote object {}", url.to_string());
|
||||
|
||||
|
|
|
@ -88,19 +88,13 @@ where
|
|||
<Kind as Object>::Error: From<Error>,
|
||||
{
|
||||
let db_object = self.dereference_from_db(data).await?;
|
||||
// if its a local object, only fetch it from the database and not over http
|
||||
if data.config.is_local_url(&self.0) {
|
||||
return match db_object {
|
||||
None => Err(Error::NotFound.into()),
|
||||
Some(o) => Ok(o),
|
||||
};
|
||||
}
|
||||
|
||||
// object found in database
|
||||
if let Some(object) = db_object {
|
||||
// object is old and should be refetched
|
||||
if let Some(last_refreshed_at) = object.last_refreshed_at() {
|
||||
if should_refetch_object(last_refreshed_at) {
|
||||
let is_local = data.config.is_local_url(&self.0);
|
||||
if !is_local && should_refetch_object(last_refreshed_at) {
|
||||
// object is outdated and should be refetched
|
||||
return self.dereference_from_http(data, Some(object)).await;
|
||||
}
|
||||
}
|
||||
|
@ -345,9 +339,10 @@ const _IMPL_DIESEL_NEW_TYPE_FOR_OBJECT_ID: () = {
|
|||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::{fetch::object_id::should_refetch_object, traits::tests::DbUser};
|
||||
use crate::traits::tests::DbUser;
|
||||
|
||||
#[test]
|
||||
fn test_deserialize() {
|
||||
|
|
|
@ -245,6 +245,7 @@ pub struct WebfingerLink {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
|
|
|
@ -275,6 +275,7 @@ pub(crate) fn verify_body_hash(
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use crate::activity_sending::generate_request_headers;
|
||||
|
|
|
@ -343,7 +343,7 @@ pub mod tests {
|
|||
error::Error,
|
||||
fetch::object_id::ObjectId,
|
||||
http_signatures::{generate_actor_keypair, Keypair},
|
||||
protocol::{public_key::PublicKey, verification::verify_domains_match},
|
||||
protocol::verification::verify_domains_match,
|
||||
};
|
||||
use activitystreams_kinds::{activity::FollowType, actor::PersonType};
|
||||
use once_cell::sync::Lazy;
|
||||
|
|
Loading…
Reference in a new issue