This commit is contained in:
Felix Ableitner 2024-09-11 15:46:19 +02:00
parent d0f37c699f
commit bff0567c27
13 changed files with 56 additions and 35 deletions

View file

@ -12,9 +12,9 @@ use activitypub_federation::{
kinds::activity::CreateType, kinds::activity::CreateType,
protocol::{context::WithContext, helpers::deserialize_one_or_many}, protocol::{context::WithContext, helpers::deserialize_one_or_many},
traits::{ActivityHandler, Object}, traits::{ActivityHandler, Object},
url::Url,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View file

@ -6,11 +6,11 @@ use activitypub_federation::{
kinds::actor::PersonType, kinds::actor::PersonType,
protocol::{public_key::PublicKey, verification::verify_domains_match}, protocol::{public_key::PublicKey, verification::verify_domains_match},
traits::{ActivityHandler, Actor, Object}, traits::{ActivityHandler, Actor, Object},
url::Url,
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::{fmt::Debug, str::FromStr};
use url::Url;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DbUser { pub struct DbUser {
@ -36,8 +36,8 @@ pub enum PersonAcceptedActivities {
impl DbUser { impl DbUser {
pub fn new(hostname: &str, name: &str) -> Result<DbUser, Error> { pub fn new(hostname: &str, name: &str) -> Result<DbUser, Error> {
let ap_id = Url::parse(&format!("https://{}/{}", hostname, &name))?.into(); let ap_id = Url::from_str(&format!("https://{}/{}", hostname, &name))?.into();
let inbox = Url::parse(&format!("https://{}/{}/inbox", hostname, &name))?; let inbox = Url::from_str(&format!("https://{}/{}/inbox", hostname, &name))?;
let keypair = generate_actor_keypair()?; let keypair = generate_actor_keypair()?;
Ok(DbUser { Ok(DbUser {
name: name.to_string(), name: name.to_string(),

View file

@ -11,10 +11,10 @@ use activitypub_federation::{
kinds::{object::NoteType, public}, kinds::{object::NoteType, public},
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match}, protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
traits::{Actor, Object}, traits::{Actor, Object},
url::Url,
}; };
use activitystreams_kinds::link::MentionType; use activitystreams_kinds::link::MentionType;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DbPost { pub struct DbPost {
@ -63,7 +63,7 @@ impl Object for DbPost {
id: self.ap_id, id: self.ap_id,
content: self.text, content: self.text,
attributed_to: self.creator, attributed_to: self.creator,
to: vec![public()], to: vec![public().try_into()?],
tag: vec![], tag: vec![],
in_reply_to: None, in_reply_to: None,
}) })
@ -98,7 +98,7 @@ impl Object for DbPost {
kind: Default::default(), kind: Default::default(),
id: generate_object_id(data.domain())?.into(), id: generate_object_id(data.domain())?.into(),
attributed_to: data.local_user().ap_id, attributed_to: data.local_user().ap_id,
to: vec![public()], to: vec![public().try_into()?],
content: format!("Hello {}", creator.name), content: format!("Hello {}", creator.name),
in_reply_to: Some(json.id.clone()), in_reply_to: Some(json.id.clone()),
tag: vec![mention], tag: vec![mention],

View file

@ -1,5 +1,8 @@
use std::str::FromStr;
use activitypub_federation::url::Url;
use rand::{distributions::Alphanumeric, thread_rng, Rng}; use rand::{distributions::Alphanumeric, thread_rng, Rng};
use url::{ParseError, Url}; use url::ParseError;
/// Just generate random url as object id. In a real project, you probably want to use /// Just generate random url as object id. In a real project, you probably want to use
/// an url which contains the database id for easy retrieval (or store the random id in db). /// an url which contains the database id for easy retrieval (or store the random id in db).
@ -9,5 +12,5 @@ pub fn generate_object_id(domain: &str) -> Result<Url, ParseError> {
.take(7) .take(7)
.map(char::from) .map(char::from)
.collect(); .collect();
Url::parse(&format!("https://{}/objects/{}", domain, id)) Url::from_str(&format!("https://{}/objects/{}", domain, id))
} }

View file

@ -4,9 +4,9 @@ use activitypub_federation::{
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
kinds::activity::AcceptType, kinds::activity::AcceptType,
traits::ActivityHandler, traits::ActivityHandler,
url::Url,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View file

@ -9,9 +9,9 @@ use activitypub_federation::{
kinds::activity::CreateType, kinds::activity::CreateType,
protocol::helpers::deserialize_one_or_many, protocol::helpers::deserialize_one_or_many,
traits::{ActivityHandler, Object}, traits::{ActivityHandler, Object},
url::Url,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View file

@ -9,9 +9,9 @@ use activitypub_federation::{
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
kinds::activity::FollowType, kinds::activity::FollowType,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
url::Url,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Deserialize, Serialize, Clone, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View file

@ -2,21 +2,23 @@ use crate::{
objects::{person::DbUser, post::DbPost}, objects::{person::DbUser, post::DbPost},
Error, Error,
}; };
use activitypub_federation::config::{FederationConfig, UrlVerifier}; use activitypub_federation::{
config::{FederationConfig, UrlVerifier},
url::Url,
};
use anyhow::anyhow; use anyhow::anyhow;
use async_trait::async_trait; use async_trait::async_trait;
use std::{ use std::{
str::FromStr, str::FromStr,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use url::Url;
pub async fn new_instance( pub async fn new_instance(
hostname: &str, hostname: &str,
name: String, name: String,
) -> Result<FederationConfig<DatabaseHandle>, Error> { ) -> Result<FederationConfig<DatabaseHandle>, Error> {
let mut system_user = DbUser::new(hostname, "system".into())?; let mut system_user = DbUser::new(hostname, "system".into())?;
system_user.ap_id = Url::parse(&format!("http://{}/", hostname))?.into(); system_user.ap_id = Url::from_str(&format!("http://{}/", hostname))?.into();
let local_user = DbUser::new(hostname, name)?; let local_user = DbUser::new(hostname, name)?;
let database = Arc::new(Database { let database = Arc::new(Database {
@ -51,7 +53,7 @@ struct MyUrlVerifier();
#[async_trait] #[async_trait]
impl UrlVerifier for MyUrlVerifier { impl UrlVerifier for MyUrlVerifier {
async fn verify(&self, url: &Url) -> Result<(), activitypub_federation::error::Error> { async fn verify(&self, url: &Url) -> Result<(), activitypub_federation::error::Error> {
if url.domain() == Some("malicious.com") { if url.domain() == "malicious.com" {
Err(activitypub_federation::error::Error::Other( Err(activitypub_federation::error::Error::Other(
"malicious domain".into(), "malicious domain".into(),
)) ))

View file

@ -14,11 +14,11 @@ use activitypub_federation::{
kinds::actor::PersonType, kinds::actor::PersonType,
protocol::{context::WithContext, public_key::PublicKey, verification::verify_domains_match}, protocol::{context::WithContext, public_key::PublicKey, verification::verify_domains_match},
traits::{ActivityHandler, Actor, Object}, traits::{ActivityHandler, Actor, Object},
url::Url,
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::{fmt::Debug, str::FromStr};
use url::Url;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DbUser { pub struct DbUser {
@ -46,8 +46,8 @@ pub enum PersonAcceptedActivities {
impl DbUser { impl DbUser {
pub fn new(hostname: &str, name: String) -> Result<DbUser, Error> { pub fn new(hostname: &str, name: String) -> Result<DbUser, Error> {
let ap_id = Url::parse(&format!("http://{}/{}", hostname, &name))?.into(); let ap_id = Url::from_str(&format!("http://{}/{}", hostname, &name))?.into();
let inbox = Url::parse(&format!("http://{}/{}/inbox", hostname, &name))?; let inbox = Url::from_str(&format!("http://{}/{}/inbox", hostname, &name))?;
let keypair = generate_actor_keypair()?; let keypair = generate_actor_keypair()?;
Ok(DbUser { Ok(DbUser {
name, name,
@ -79,7 +79,7 @@ impl DbUser {
} }
pub fn followers_url(&self) -> Result<Url, Error> { pub fn followers_url(&self) -> Result<Url, Error> {
Ok(Url::parse(&format!("{}/followers", self.ap_id.inner()))?) Ok(Url::from_str(&format!("{}/followers", self.ap_id.inner()))?)
} }
pub async fn follow(&self, other: &str, data: &Data<DatabaseHandle>) -> Result<(), Error> { pub async fn follow(&self, other: &str, data: &Data<DatabaseHandle>) -> Result<(), Error> {

View file

@ -5,9 +5,9 @@ use activitypub_federation::{
kinds::{object::NoteType, public}, kinds::{object::NoteType, public},
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match}, protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
traits::Object, traits::Object,
url::Url,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DbPost { pub struct DbPost {
@ -19,7 +19,7 @@ pub struct DbPost {
impl DbPost { impl DbPost {
pub fn new(text: String, creator: ObjectId<DbUser>) -> Result<DbPost, Error> { pub fn new(text: String, creator: ObjectId<DbUser>) -> Result<DbPost, Error> {
let ap_id = generate_object_id(creator.inner().domain().unwrap())?.into(); let ap_id = generate_object_id(creator.inner().domain())?.try_into()?;
Ok(DbPost { Ok(DbPost {
text, text,
ap_id, ap_id,
@ -65,7 +65,7 @@ impl Object for DbPost {
kind: Default::default(), kind: Default::default(),
id: self.ap_id, id: self.ap_id,
attributed_to: self.creator, attributed_to: self.creator,
to: vec![public(), creator.followers_url()?], to: vec![public().try_into()?, creator.followers_url()?],
content: self.text, content: self.text,
}) })
} }

View file

@ -47,6 +47,8 @@ where
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
mod test { mod test {
use std::str::FromStr;
use super::*; use super::*;
use crate::{ use crate::{
activity_sending::generate_request_headers, activity_sending::generate_request_headers,
@ -165,7 +167,7 @@ mod test {
actor: ObjectId::parse("http://localhost:123").unwrap(), actor: ObjectId::parse("http://localhost:123").unwrap(),
object: ObjectId::parse("http://localhost:124").unwrap(), object: ObjectId::parse("http://localhost:124").unwrap(),
kind: Default::default(), kind: Default::default(),
id: "http://localhost:123/1".try_into().unwrap(), id: "http://localhost:123/1".parse().unwrap(),
}; };
let body: Bytes = serde_json::to_vec(&activity).unwrap().into(); let body: Bytes = serde_json::to_vec(&activity).unwrap().into();
let incoming_request = construct_request(&body, activity.actor.inner()).await; let incoming_request = construct_request(&body, activity.actor.inner()).await;

View file

@ -135,13 +135,13 @@ async fn fetch_object_http_with_accept<T: Clone, Kind: DeserializeOwned>(
match serde_json::from_slice(&text) { match serde_json::from_slice(&text) {
Ok(object) => Ok(FetchObjectResponse { Ok(object) => Ok(FetchObjectResponse {
object, object,
url: url.into(), url: url.try_into()?,
content_type, content_type,
object_id, object_id,
}), }),
Err(e) => Err(ParseFetchedObject( Err(e) => Err(ParseFetchedObject(
e, e,
url.into(), url.try_into()?,
String::from_utf8(Vec::from(text))?, String::from_utf8(Vec::from(text))?,
)), )),
} }

View file

@ -1,13 +1,12 @@
//! Wrapper for `url::Url` type. //! Wrapper for `url::Url` type.
use serde::{Deserialize, Serialize};
use std::{ use std::{
fmt::{Display, Formatter}, fmt::{Display, Formatter},
ops::Deref, ops::Deref,
str::FromStr, str::FromStr,
}; };
use serde::{Deserialize, Serialize};
/// Wrapper for `url::Url` type. Has `domain` as mandatory field, and prints plain /// Wrapper for `url::Url` type. Has `domain` as mandatory field, and prints plain
/// string for debugging. /// string for debugging.
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] #[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
@ -22,6 +21,7 @@ impl Deref for Url {
} }
impl Display for Url { impl Display for Url {
#[allow(clippy::to_string_in_format_args)]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.to_string()) write!(f, "{}", self.0.to_string())
} }
@ -30,14 +30,24 @@ impl Display for Url {
impl Url { impl Url {
/// Returns domain of the url /// Returns domain of the url
pub fn domain(&self) -> &str { pub fn domain(&self) -> &str {
// TODO: must have error handling, or ensure at creation that it has domain
self.0.domain().expect("has domain") self.0.domain().expect("has domain")
} }
} }
impl From<url::Url> for Url { impl TryFrom<url::Url> for Url {
fn from(value: url::Url) -> Self { type Error = url::ParseError;
Url(value) fn try_from(value: url::Url) -> Result<Self, Self::Error> {
if value.domain().is_none() {
return Err(url::ParseError::EmptyHost);
}
Ok(Url(value))
}
}
#[allow(clippy::from_over_into)]
impl Into<url::Url> for Url {
fn into(self) -> url::Url {
self.0
} }
} }
@ -45,6 +55,10 @@ impl FromStr for Url {
type Err = url::ParseError; type Err = url::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(url::Url::from_str(s).map(Url).unwrap()) let url = url::Url::from_str(s)?;
if url.domain().is_none() {
return Err(url::ParseError::EmptyHost);
}
Ok(Url(url))
} }
} }