diff --git a/.woodpecker.yml b/.woodpecker.yml index f9d5cf8..423a85d 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,5 +1,6 @@ variables: - &rust_image "rust:1.84-bullseye" + - &install_binstall "wget -O- https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xvz -C /usr/local/cargo/bin" steps: cargo_fmt: @@ -10,7 +11,7 @@ steps: - event: pull_request cargo_shear: - image: *rust_nightly_image + image: *rust_image commands: - *install_binstall - cargo binstall -y cargo-shear diff --git a/src/protocol/verification.rs b/src/protocol/verification.rs index 18595b9..c6c342a 100644 --- a/src/protocol/verification.rs +++ b/src/protocol/verification.rs @@ -1,6 +1,7 @@ //! Verify that received data is valid -use crate::error::Error; +use crate::{config::Data, error::Error, fetch::object_id::ObjectId, traits::Object}; +use serde::Deserialize; use url::Url; /// Check that both urls have the same domain. If not, return UrlVerificationError. @@ -36,3 +37,38 @@ pub fn verify_urls_match(a: &Url, b: &Url) -> Result<(), Error> { } Ok(()) } + +/// Check that the given ID doesn't match the local domain. +/// +/// It is important to verify this to avoid local objects from being overwritten. In general +/// locally created objects should be considered authorative, while incoming federated data +/// is untrusted. Lack of such a check could allow an attacker to rewrite local posts. It could +/// also result in an `object.local` field being overwritten with `false` for local objects, resulting in invalid data. +/// +/// ``` +/// # use activitypub_federation::fetch::object_id::ObjectId; +/// # use activitypub_federation::config::FederationConfig; +/// # use activitypub_federation::protocol::verification::verify_is_remote_object; +/// # use activitypub_federation::traits::tests::{DbConnection, DbUser}; +/// # tokio::runtime::Runtime::new().unwrap().block_on(async { +/// # let config = FederationConfig::builder().domain("example.com").app_data(DbConnection).build().await?; +/// # let data = config.to_request_data(); +/// let id = ObjectId::::parse("https://remote.com/u/name")?; +/// assert!(verify_is_remote_object(&id, &data).is_ok()); +/// # Ok::<(), anyhow::Error>(()) +/// # }).unwrap(); +/// ``` +pub fn verify_is_remote_object( + id: &ObjectId, + data: &Data<::DataType>, +) -> Result<(), Error> +where + Kind: Object + Send + 'static, + for<'de2> ::Kind: Deserialize<'de2>, +{ + if id.is_local(data) { + Err(Error::UrlVerificationError("Object is not remote")) + } else { + Ok(()) + } +}