Rmove FromId

This commit is contained in:
Kitaiti Makoto 2022-05-02 12:58:01 +09:00
parent 2165c286ae
commit 2804f44a06
12 changed files with 107 additions and 929 deletions

View file

@ -318,72 +318,6 @@ fn get_id(json: serde_json::Value) -> Option<String> {
} }
} }
/// A trait for ActivityPub objects that can be retrieved or constructed from ID.
///
/// The two functions to implement are `from_activity` to create (and save) a new object
/// of this type from its AP representation, and `from_db` to try to find it in the database
/// using its ID.
///
/// When dealing with the "object" field of incoming activities, `Inbox` will try to see if it is
/// a full object, and if so, save it with `from_activity`. If it is only an ID, it will try to find
/// it in the database with `from_db`, and otherwise dereference (fetch) the full object and parse it
/// with `from_activity`.
pub trait FromId<C>: Sized {
/// The type representing a failure
type Error: From<InboxError<Self::Error>> + Debug;
/// The ActivityPub object type representing Self
type Object: activitypub::Object;
/// Tries to get an instance of `Self` from an ActivityPub ID.
///
/// # Parameters
///
/// - `ctx`: a context to get this instance (= a database in which to search)
/// - `id`: the ActivityPub ID of the object to find
/// - `object`: optional object that will be used if the object was not found in the database
/// If absent, the ID will be dereferenced.
fn from_id(
ctx: &C,
id: &str,
object: Option<Self::Object>,
proxy: Option<&reqwest::Proxy>,
) -> Result<Self, (Option<serde_json::Value>, Self::Error)> {
match Self::from_db(ctx, id) {
Ok(x) => Ok(x),
_ => match object {
Some(o) => Self::from_activity(ctx, o).map_err(|e| (None, e)),
None => Self::from_activity(ctx, Self::deref(id, proxy.cloned())?)
.map_err(|e| (None, e)),
},
}
}
/// Dereferences an ID
fn deref(
id: &str,
proxy: Option<reqwest::Proxy>,
) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
request::get(id, Self::get_sender(), proxy)
.map_err(|_| (None, InboxError::DerefError))
.and_then(|mut r| {
let json: serde_json::Value = r
.json()
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
serde_json::from_value(json.clone())
.map_err(|_| (Some(json), InboxError::InvalidObject(None)))
})
.map_err(|(json, e)| (json, e.into()))
}
/// Builds a `Self` from its ActivityPub representation
fn from_activity(ctx: &C, activity: Self::Object) -> Result<Self, Self::Error>;
/// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database)
fn from_db(ctx: &C, id: &str) -> Result<Self, Self::Error>;
fn get_sender() -> &'static dyn Signer;
}
/// A trait for ActivityPub objects that can be retrieved or constructed from ID. /// A trait for ActivityPub objects that can be retrieved or constructed from ID.
/// ///
/// The two functions to implement are `from_activity` to create (and save) a new object /// The two functions to implement are `from_activity` to create (and save) a new object
@ -808,22 +742,6 @@ mod tests {
} }
struct MyActor; struct MyActor;
impl FromId<()> for MyActor {
type Error = ();
type Object = Person;
fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
Ok(MyActor)
}
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
Ok(MyActor)
}
fn get_sender() -> &'static dyn Signer {
&*MY_SIGNER
}
}
impl FromId07<()> for MyActor { impl FromId07<()> for MyActor {
type Error = (); type Error = ();
type Object = Person07; type Object = Person07;
@ -852,22 +770,6 @@ mod tests {
} }
struct MyObject; struct MyObject;
impl FromId<()> for MyObject {
type Error = ();
type Object = Note;
fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
Ok(MyObject)
}
fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> {
Ok(MyObject)
}
fn get_sender() -> &'static dyn Signer {
&*MY_SIGNER
}
}
impl AsObject<MyActor, Create, &()> for MyObject { impl AsObject<MyActor, Create, &()> for MyObject {
type Error = (); type Error = ();
type Output = (); type Output = ();
@ -1017,15 +919,6 @@ mod tests {
act act
} }
#[test]
fn test_inbox_basic() {
let act = serde_json::to_value(build_create()).unwrap();
let res: Result<(), ()> = Inbox::handle(&(), act)
.with::<MyActor, Create, MyObject>(None)
.done();
assert!(res.is_ok());
}
#[test] #[test]
fn test_inbox_basic07() { fn test_inbox_basic07() {
let act = serde_json::to_value(build_create07()).unwrap(); let act = serde_json::to_value(build_create07()).unwrap();
@ -1035,18 +928,6 @@ mod tests {
assert!(res.is_ok()); assert!(res.is_ok());
} }
#[test]
fn test_inbox_multi_handlers() {
let act = serde_json::to_value(build_create()).unwrap();
let res: Result<(), ()> = Inbox::handle(&(), act)
.with::<MyActor, Announce, MyObject>(None)
.with::<MyActor, Delete, MyObject>(None)
.with::<MyActor, Create, MyObject>(None)
.with::<MyActor, Like, MyObject>(None)
.done();
assert!(res.is_ok());
}
#[test] #[test]
fn test_inbox_multi_handlers07() { fn test_inbox_multi_handlers07() {
let act = serde_json::to_value(build_create()).unwrap(); let act = serde_json::to_value(build_create()).unwrap();
@ -1059,17 +940,6 @@ mod tests {
assert!(res.is_ok()); assert!(res.is_ok());
} }
#[test]
fn test_inbox_failure() {
let act = serde_json::to_value(build_create()).unwrap();
// Create is not handled by this inbox
let res: Result<(), ()> = Inbox::handle(&(), act)
.with::<MyActor, Announce, MyObject>(None)
.with::<MyActor, Like, MyObject>(None)
.done();
assert!(res.is_err());
}
#[test] #[test]
fn test_inbox_failure07() { fn test_inbox_failure07() {
let act = serde_json::to_value(build_create07()).unwrap(); let act = serde_json::to_value(build_create07()).unwrap();
@ -1082,22 +952,6 @@ mod tests {
} }
struct FailingActor; struct FailingActor;
impl FromId<()> for FailingActor {
type Error = ();
type Object = Person;
fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
Err(())
}
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
Err(())
}
fn get_sender() -> &'static dyn Signer {
&*MY_SIGNER
}
}
impl AsActor<&()> for FailingActor { impl AsActor<&()> for FailingActor {
fn get_inbox_url(&self) -> String { fn get_inbox_url(&self) -> String {
String::from("https://test.ap/failing-actor/inbox") String::from("https://test.ap/failing-actor/inbox")
@ -1155,22 +1009,6 @@ mod tests {
} }
} }
#[test]
fn test_inbox_actor_failure() {
let act = serde_json::to_value(build_create()).unwrap();
let res: Result<(), ()> = Inbox::handle(&(), act.clone())
.with::<FailingActor, Create, MyObject>(None)
.done();
assert!(res.is_err());
let res: Result<(), ()> = Inbox::handle(&(), act.clone())
.with::<FailingActor, Create, MyObject>(None)
.with::<MyActor, Create, MyObject>(None)
.done();
assert!(res.is_ok());
}
#[test] #[test]
fn test_inbox_actor_failure07() { fn test_inbox_actor_failure07() {
let act = serde_json::to_value(build_create07()).unwrap(); let act = serde_json::to_value(build_create07()).unwrap();

View file

@ -27,11 +27,10 @@ use openssl::{
sign::{Signer, Verifier}, sign::{Signer, Verifier},
}; };
use plume_common::activity_pub::{ use plume_common::activity_pub::{
inbox::{AsActor, FromId, FromId07}, inbox::{AsActor, FromId07},
sign, ActivityStream, ApSignature, ApSignature07, CustomGroup as CustomGroup07, Id, IntoId, sign, ActivityStream, ApSignature, ApSignature07, CustomGroup as CustomGroup07, Id, IntoId,
PublicKey, PublicKey07, Source, SourceProperty, ToAsString, ToAsUri, PublicKey, PublicKey07, Source, SourceProperty, ToAsString, ToAsUri,
}; };
use url::Url;
use webfinger::*; use webfinger::*;
pub type CustomGroup = CustomObject<ApSignature, Group>; pub type CustomGroup = CustomObject<ApSignature, Group>;
@ -161,7 +160,7 @@ impl Blog {
.find(|l| l.mime_type == Some(String::from("application/activity+json"))) .find(|l| l.mime_type == Some(String::from("application/activity+json")))
.ok_or(Error::Webfinger) .ok_or(Error::Webfinger)
.and_then(|l| { .and_then(|l| {
Blog::from_id( Blog::from_id07(
conn, conn,
&l.href.ok_or(Error::MissingApProperty)?, &l.href.ok_or(Error::MissingApProperty)?,
None, None,
@ -471,110 +470,6 @@ impl IntoId for Blog {
} }
} }
impl FromId<DbConn> for Blog {
type Error = Error;
type Object = CustomGroup;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id)
}
fn from_activity(conn: &DbConn, acct: CustomGroup) -> Result<Self> {
let url = Url::parse(&acct.object.object_props.id_string()?)?;
let inst = url.host_str().ok_or(Error::Url)?;
let instance = Instance::find_by_domain(conn, inst).or_else(|_| {
Instance::insert(
conn,
NewInstance {
public_domain: inst.to_owned(),
name: inst.to_owned(),
local: false,
// We don't really care about all the following for remote instances
long_description: SafeString::new(""),
short_description: SafeString::new(""),
default_license: String::new(),
open_registrations: true,
short_description_html: String::new(),
long_description_html: String::new(),
},
)
})?;
let icon_id = acct
.object
.object_props
.icon_image()
.ok()
.and_then(|icon| {
let owner = icon.object_props.attributed_to_link::<Id>().ok()?;
Media::save_remote(
conn,
icon.object_props.url_string().ok()?,
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
)
.ok()
})
.map(|m| m.id);
let banner_id = acct
.object
.object_props
.image_image()
.ok()
.and_then(|banner| {
let owner = banner.object_props.attributed_to_link::<Id>().ok()?;
Media::save_remote(
conn,
banner.object_props.url_string().ok()?,
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
)
.ok()
})
.map(|m| m.id);
let name = acct.object.ap_actor_props.preferred_username_string()?;
if name.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
return Err(Error::InvalidValue);
}
Blog::insert(
conn,
NewBlog {
actor_id: name.clone(),
title: acct.object.object_props.name_string().unwrap_or(name),
outbox_url: acct.object.ap_actor_props.outbox_string()?,
inbox_url: acct.object.ap_actor_props.inbox_string()?,
summary: acct
.object
.ap_object_props
.source_object::<Source>()
.map(|s| s.content)
.unwrap_or_default(),
instance_id: instance.id,
ap_url: acct.object.object_props.id_string()?,
public_key: acct
.custom_props
.public_key_publickey()?
.public_key_pem_string()?,
private_key: None,
banner_id,
icon_id,
summary_html: SafeString::new(
&acct
.object
.object_props
.summary_string()
.unwrap_or_default(),
),
theme: None,
},
)
}
fn get_sender() -> &'static dyn sign::Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
}
impl FromId07<DbConn> for Blog { impl FromId07<DbConn> for Blog {
type Error = Error; type Error = Error;
type Object = CustomGroup07; type Object = CustomGroup07;
@ -648,7 +543,7 @@ impl FromId07<DbConn> for Blog {
Media::save_remote( Media::save_remote(
conn, conn,
banner.url()?.to_as_uri()?, banner.url()?.to_as_uri()?,
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?, &User::from_id07(conn, &owner, None, CONFIG.proxy()).ok()?,
) )
.ok() .ok()
}) })
@ -1130,33 +1025,6 @@ pub(crate) mod tests {
}) })
} }
#[test]
fn self_federation() {
let conn = &db();
conn.test_transaction::<_, (), _>(|| {
let (_users, blogs) = fill_database(&conn);
let ap_repr = blogs[0].to_activity(&conn).unwrap();
blogs[0].delete(&conn).unwrap();
let blog = Blog::from_activity(&conn, ap_repr).unwrap();
assert_eq!(blog.actor_id, blogs[0].actor_id);
assert_eq!(blog.title, blogs[0].title);
assert_eq!(blog.summary, blogs[0].summary);
assert_eq!(blog.outbox_url, blogs[0].outbox_url);
assert_eq!(blog.inbox_url, blogs[0].inbox_url);
assert_eq!(blog.instance_id, blogs[0].instance_id);
assert_eq!(blog.ap_url, blogs[0].ap_url);
assert_eq!(blog.public_key, blogs[0].public_key);
assert_eq!(blog.fqn, blogs[0].fqn);
assert_eq!(blog.summary_html, blogs[0].summary_html);
assert_eq!(blog.icon_url(&conn), blogs[0].icon_url(&conn));
assert_eq!(blog.banner_url(&conn), blogs[0].banner_url(&conn));
Ok(())
})
}
#[test] #[test]
fn self_federation07() { fn self_federation07() {
let conn = &db(); let conn = &db();

View file

@ -30,7 +30,7 @@ use chrono::{self, NaiveDateTime, TimeZone, Utc};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use plume_common::{ use plume_common::{
activity_pub::{ activity_pub::{
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07}, inbox::{AsActor, AsObject, AsObject07, FromId07},
sign::Signer, sign::Signer,
Id, IntoId, ToAsString, ToAsUri, PUBLIC_VISIBILITY, Id, IntoId, ToAsString, ToAsUri, PUBLIC_VISIBILITY,
}, },
@ -294,141 +294,6 @@ impl Comment {
} }
} }
impl FromId<DbConn> for Comment {
type Error = Error;
type Object = Note;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id)
}
fn from_activity(conn: &DbConn, note: Note) -> Result<Self> {
let comm = {
let previous_url = note
.object_props
.in_reply_to
.as_ref()
.ok_or(Error::MissingApProperty)?
.as_str()
.ok_or(Error::MissingApProperty)?;
let previous_comment = Comment::find_by_ap_url(conn, previous_url);
let is_public = |v: &Option<serde_json::Value>| match v
.as_ref()
.unwrap_or(&serde_json::Value::Null)
{
serde_json::Value::Array(v) => v
.iter()
.filter_map(serde_json::Value::as_str)
.any(|s| s == PUBLIC_VISIBILITY),
serde_json::Value::String(s) => s == PUBLIC_VISIBILITY,
_ => false,
};
let public_visibility = is_public(&note.object_props.to)
|| is_public(&note.object_props.bto)
|| is_public(&note.object_props.cc)
|| is_public(&note.object_props.bcc);
let comm = Comment::insert(
conn,
NewComment {
content: SafeString::new(&note.object_props.content_string()?),
spoiler_text: note.object_props.summary_string().unwrap_or_default(),
ap_url: note.object_props.id_string().ok(),
in_response_to_id: previous_comment.iter().map(|c| c.id).next(),
post_id: previous_comment.map(|c| c.post_id).or_else(|_| {
Ok(Post::find_by_ap_url(conn, previous_url)?.id) as Result<i32>
})?,
author_id: User::from_id(
conn,
&note.object_props.attributed_to_link::<Id>()?,
None,
CONFIG.proxy(),
)
.map_err(|(_, e)| e)?
.id,
sensitive: note.object_props.summary_string().is_ok(),
public_visibility,
},
)?;
// save mentions
if let Some(serde_json::Value::Array(tags)) = note.object_props.tag.clone() {
for tag in tags {
serde_json::from_value::<link::Mention>(tag)
.map_err(Error::from)
.and_then(|m| {
let author = &Post::get(conn, comm.post_id)?.get_authors(conn)?[0];
let not_author = m.link_props.href_string()? != author.ap_url.clone();
Mention::from_activity(conn, &m, comm.id, false, not_author)
})
.ok();
}
}
comm
};
if !comm.public_visibility {
let receivers_ap_url = |v: Option<serde_json::Value>| {
let filter = |e: serde_json::Value| {
if let serde_json::Value::String(s) = e {
Some(s)
} else {
None
}
};
match v.unwrap_or(serde_json::Value::Null) {
serde_json::Value::Array(v) => v,
v => vec![v],
}
.into_iter()
.filter_map(filter)
};
let mut note = note;
let to = receivers_ap_url(note.object_props.to.take());
let cc = receivers_ap_url(note.object_props.cc.take());
let bto = receivers_ap_url(note.object_props.bto.take());
let bcc = receivers_ap_url(note.object_props.bcc.take());
let receivers_ap_url = to
.chain(cc)
.chain(bto)
.chain(bcc)
.collect::<HashSet<_>>() // remove duplicates (don't do a query more than once)
.into_iter()
.flat_map(|v| {
if let Ok(user) = User::from_id(conn, &v, None, CONFIG.proxy()) {
vec![user]
} else {
vec![] // TODO try to fetch collection
}
})
.filter(|u| u.get_instance(conn).map(|i| i.local).unwrap_or(false))
.collect::<HashSet<User>>(); //remove duplicates (prevent db error)
for user in &receivers_ap_url {
CommentSeers::insert(
conn,
NewCommentSeers {
comment_id: comm.id,
user_id: user.id,
},
)?;
}
}
comm.notify(conn)?;
Ok(comm)
}
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
}
impl FromId07<DbConn> for Comment { impl FromId07<DbConn> for Comment {
type Error = Error; type Error = Error;
type Object = Note07; type Object = Note07;
@ -484,7 +349,7 @@ impl FromId07<DbConn> for Comment {
post_id: previous_comment.map(|c| c.post_id).or_else(|_| { post_id: previous_comment.map(|c| c.post_id).or_else(|_| {
Ok(Post::find_by_ap_url(conn, previous_url.as_str())?.id) as Result<i32> Ok(Post::find_by_ap_url(conn, previous_url.as_str())?.id) as Result<i32>
})?, })?,
author_id: User::from_id( author_id: User::from_id07(
conn, conn,
&note &note
.attributed_to() .attributed_to()
@ -537,7 +402,7 @@ impl FromId07<DbConn> for Comment {
let receivers_ap_url = receiver_ids let receivers_ap_url = receiver_ids
.into_iter() .into_iter()
.flat_map(|v| { .flat_map(|v| {
if let Ok(user) = User::from_id(conn, v.as_ref(), None, CONFIG.proxy()) { if let Ok(user) = User::from_id07(conn, v.as_ref(), None, CONFIG.proxy()) {
vec![user] vec![user]
} else { } else {
vec![] // TODO try to fetch collection vec![] // TODO try to fetch collection

View file

@ -12,7 +12,7 @@ use activitystreams::{
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use plume_common::activity_pub::{ use plume_common::activity_pub::{
broadcast, broadcast07, broadcast, broadcast07,
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07}, inbox::{AsActor, AsObject, AsObject07, FromId07},
sign::Signer, sign::Signer,
Id, IntoId, PUBLIC_VISIBILITY, Id, IntoId, PUBLIC_VISIBILITY,
}; };
@ -272,38 +272,6 @@ impl AsObject07<User, FollowAct07, &DbConn> for User {
} }
} }
impl FromId<DbConn> for Follow {
type Error = Error;
type Object = FollowAct;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
Follow::find_by_ap_url(conn, id)
}
fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> {
let actor = User::from_id(
conn,
&follow.follow_props.actor_link::<Id>()?,
None,
CONFIG.proxy(),
)
.map_err(|(_, e)| e)?;
let target = User::from_id(
conn,
&follow.follow_props.object_link::<Id>()?,
None,
CONFIG.proxy(),
)
.map_err(|(_, e)| e)?;
Follow::accept_follow(conn, &actor, &target, follow, actor.id, target.id)
}
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
}
impl FromId07<DbConn> for Follow { impl FromId07<DbConn> for Follow {
type Error = Error; type Error = Error;
type Object = FollowAct07; type Object = FollowAct07;

View file

@ -12,7 +12,7 @@ use activitystreams::{
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use plume_common::activity_pub::{ use plume_common::activity_pub::{
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07}, inbox::{AsActor, AsObject, AsObject07, FromId07},
sign::Signer, sign::Signer,
Id, IntoId, PUBLIC_VISIBILITY, Id, IntoId, PUBLIC_VISIBILITY,
}; };
@ -158,46 +158,6 @@ impl AsObject07<User, Like07, &DbConn> for Post {
} }
} }
impl FromId<DbConn> for Like {
type Error = Error;
type Object = activity::Like;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
Like::find_by_ap_url(conn, id)
}
fn from_activity(conn: &DbConn, act: activity::Like) -> Result<Self> {
let res = Like::insert(
conn,
NewLike {
post_id: Post::from_id(
conn,
&act.like_props.object_link::<Id>()?,
None,
CONFIG.proxy(),
)
.map_err(|(_, e)| e)?
.id,
user_id: User::from_id(
conn,
&act.like_props.actor_link::<Id>()?,
None,
CONFIG.proxy(),
)
.map_err(|(_, e)| e)?
.id,
ap_url: act.object_props.id_string()?,
},
)?;
res.notify(conn)?;
Ok(res)
}
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
}
impl FromId07<DbConn> for Like { impl FromId07<DbConn> for Like {
type Error = Error; type Error = Error;
type Object = Like07; type Object = Like07;
@ -210,7 +170,7 @@ impl FromId07<DbConn> for Like {
let res = Like::insert( let res = Like::insert(
conn, conn,
NewLike { NewLike {
post_id: Post::from_id( post_id: Post::from_id07(
conn, conn,
act.object_field_ref() act.object_field_ref()
.as_single_id() .as_single_id()

View file

@ -7,7 +7,7 @@ use activitystreams::{object::Image as Image07, prelude::*};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use guid_create::GUID; use guid_create::GUID;
use plume_common::{ use plume_common::{
activity_pub::{inbox::FromId, request, Id, ToAsString, ToAsUri}, activity_pub::{inbox::FromId07, request, Id, ToAsString, ToAsUri},
utils::{escape, MediaProcessor}, utils::{escape, MediaProcessor},
}; };
use std::{ use std::{
@ -222,7 +222,7 @@ impl Media {
// TODO: conditional GET // TODO: conditional GET
request::get( request::get(
remote_url.as_str(), remote_url.as_str(),
User::get_sender(), User::get_sender07(),
CONFIG.proxy().cloned(), CONFIG.proxy().cloned(),
)? )?
.copy_to(&mut dest)?; .copy_to(&mut dest)?;
@ -275,7 +275,7 @@ impl Media {
remote_url: None, remote_url: None,
sensitive: image.object_props.summary_string().is_ok(), sensitive: image.object_props.summary_string().is_ok(),
content_warning: image.object_props.summary_string().ok(), content_warning: image.object_props.summary_string().ok(),
owner_id: User::from_id( owner_id: User::from_id07(
conn, conn,
image image
.object_props .object_props
@ -311,7 +311,7 @@ impl Media {
// TODO: conditional GET // TODO: conditional GET
request::get( request::get(
remote_url.as_str(), remote_url.as_str(),
User::get_sender(), User::get_sender07(),
CONFIG.proxy().cloned(), CONFIG.proxy().cloned(),
)? )?
.copy_to(&mut dest)?; .copy_to(&mut dest)?;
@ -362,7 +362,7 @@ impl Media {
remote_url: None, remote_url: None,
sensitive: summary.is_some(), sensitive: summary.is_some(),
content_warning: summary, content_warning: summary,
owner_id: User::from_id( owner_id: User::from_id07(
conn, conn,
&image &image
.attributed_to() .attributed_to()

View file

@ -26,7 +26,7 @@ use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use plume_common::{ use plume_common::{
activity_pub::{ activity_pub::{
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07}, inbox::{AsActor, AsObject, AsObject07, FromId07},
sign::Signer, sign::Signer,
Hashtag, Hashtag07, HashtagType07, Id, IntoId, Licensed, Licensed07, Hashtag, Hashtag07, HashtagType07, Id, IntoId, Licensed, Licensed07,
LicensedArticle as LicensedArticle07, Source, SourceProperty, ToAsString, ToAsUri, LicensedArticle as LicensedArticle07, Source, SourceProperty, ToAsString, ToAsUri,
@ -863,160 +863,6 @@ impl Post {
} }
} }
impl FromId<DbConn> for Post {
type Error = Error;
type Object = LicensedArticle;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id)
}
fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> {
let conn = conn;
let license = article.custom_props.license_string().unwrap_or_default();
let article = article.object;
let (blog, authors) = article
.object_props
.attributed_to_link_vec::<Id>()?
.into_iter()
.fold((None, vec![]), |(blog, mut authors), link| {
let url = link;
match User::from_id(conn, &url, None, CONFIG.proxy()) {
Ok(u) => {
authors.push(u);
(blog, authors)
}
Err(_) => (
blog.or_else(|| Blog::from_id(conn, &url, None, CONFIG.proxy()).ok()),
authors,
),
}
});
let cover = article
.object_props
.icon_object::<Image>()
.ok()
.and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id));
let title = article.object_props.name_string()?;
let ap_url = article
.object_props
.url_string()
.or_else(|_| article.object_props.id_string())?;
let post = Post::from_db(conn, &ap_url)
.and_then(|mut post| {
let mut updated = false;
let slug = Self::slug(&title);
let content = SafeString::new(&article.object_props.content_string()?);
let subtitle = article.object_props.summary_string()?;
let source = article.ap_object_props.source_object::<Source>()?.content;
if post.slug != slug {
post.slug = slug.to_string();
updated = true;
}
if post.title != title {
post.title = title.clone();
updated = true;
}
if post.content != content {
post.content = content;
updated = true;
}
if post.license != license {
post.license = license.clone();
updated = true;
}
if post.subtitle != subtitle {
post.subtitle = subtitle;
updated = true;
}
if post.source != source {
post.source = source;
updated = true;
}
if post.cover_id != cover {
post.cover_id = cover;
updated = true;
}
if updated {
post.update(conn)?;
}
Ok(post)
})
.or_else(|_| {
Post::insert(
conn,
NewPost {
blog_id: blog.ok_or(Error::NotFound)?.id,
slug: Self::slug(&title).to_string(),
title,
content: SafeString::new(&article.object_props.content_string()?),
published: true,
license,
// FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields
ap_url,
creation_date: Some(article.object_props.published_utctime()?.naive_utc()),
subtitle: article.object_props.summary_string()?,
source: article.ap_object_props.source_object::<Source>()?.content,
cover_id: cover,
},
)
.and_then(|post| {
for author in authors {
PostAuthor::insert(
conn,
NewPostAuthor {
post_id: post.id,
author_id: author.id,
},
)?;
}
Ok(post)
})
})?;
// save mentions and tags
let mut hashtags = md_to_html(&post.source, None, false, None)
.2
.into_iter()
.collect::<HashSet<_>>();
if let Some(serde_json::Value::Array(tags)) = article.object_props.tag {
for tag in tags {
serde_json::from_value::<link::Mention>(tag.clone())
.map(|m| Mention::from_activity(conn, &m, post.id, true, true))
.ok();
serde_json::from_value::<Hashtag>(tag.clone())
.map_err(Error::from)
.and_then(|t| {
let tag_name = t.name_string()?;
Ok(Tag::from_activity(
conn,
&t,
post.id,
hashtags.remove(&tag_name),
))
})
.ok();
}
}
Timeline::add_to_all_timelines(conn, &post, Kind::Original)?;
Ok(post)
}
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
}
impl FromId07<DbConn> for Post { impl FromId07<DbConn> for Post {
type Error = Error; type Error = Error;
type Object = LicensedArticle07; type Object = LicensedArticle07;
@ -1273,43 +1119,6 @@ pub struct PostUpdate {
pub tags: Option<serde_json::Value>, pub tags: Option<serde_json::Value>,
} }
impl FromId<DbConn> for PostUpdate {
type Error = Error;
type Object = LicensedArticle;
fn from_db(_: &DbConn, _: &str) -> Result<Self> {
// Always fail because we always want to deserialize the AP object
Err(Error::NotFound)
}
fn from_activity(conn: &DbConn, updated: LicensedArticle) -> Result<Self> {
Ok(PostUpdate {
ap_url: updated.object.object_props.id_string()?,
title: updated.object.object_props.name_string().ok(),
subtitle: updated.object.object_props.summary_string().ok(),
content: updated.object.object_props.content_string().ok(),
cover: updated
.object
.object_props
.icon_object::<Image>()
.ok()
.and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id)),
source: updated
.object
.ap_object_props
.source_object::<Source>()
.ok()
.map(|x| x.content),
license: updated.custom_props.license_string().ok(),
tags: updated.object.object_props.tag,
})
}
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
}
impl FromId07<DbConn> for PostUpdate { impl FromId07<DbConn> for PostUpdate {
type Error = Error; type Error = Error;
type Object = LicensedArticle07; type Object = LicensedArticle07;
@ -1373,7 +1182,7 @@ impl AsObject<User, Update, &DbConn> for PostUpdate {
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
let mut post = let mut post =
Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?; Post::from_id07(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?;
if !post.is_author(conn, actor.id)? { if !post.is_author(conn, actor.id)? {
// TODO: maybe the author was added in the meantime // TODO: maybe the author was added in the meantime
@ -1445,7 +1254,7 @@ impl AsObject07<User, Update07, &DbConn> for PostUpdate {
fn activity07(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity07(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
let mut post = let mut post =
Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?; Post::from_id07(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?;
if !post.is_author(conn, actor.id)? { if !post.is_author(conn, actor.id)? {
// TODO: maybe the author was added in the meantime // TODO: maybe the author was added in the meantime

View file

@ -1,12 +1,16 @@
use crate::{ use crate::{
db_conn::{DbConn, DbPool}, db_conn::{DbConn, DbPool},
follows, follows,
posts::{LicensedArticle, Post}, posts::Post,
users::{User, UserEvent}, users::{User, UserEvent},
ACTOR_SYS, CONFIG, USER_CHAN, ACTOR_SYS, CONFIG, USER_CHAN,
}; };
use activitypub::activity::Create; use activitystreams::{
use plume_common::activity_pub::inbox::FromId; activity::{ActorAndObjectRef, Create as Create07},
base::AnyBase,
object::kind::ArticleType,
};
use plume_common::activity_pub::{inbox::FromId07, LicensedArticle as LicensedArticle07};
use riker::actors::{Actor, ActorFactoryArgs, ActorRefFactory, Context, Sender, Subscribe, Tell}; use riker::actors::{Actor, ActorFactoryArgs, ActorRefFactory, Context, Sender, Subscribe, Tell};
use std::sync::Arc; use std::sync::Arc;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
@ -64,17 +68,23 @@ impl ActorFactoryArgs<DbPool> for RemoteFetchActor {
} }
fn fetch_and_cache_articles(user: &Arc<User>, conn: &DbConn) { fn fetch_and_cache_articles(user: &Arc<User>, conn: &DbConn) {
let create_acts = user.fetch_outbox::<Create>(); let create_acts = user.fetch_outbox07::<Create07>();
match create_acts { match create_acts {
Ok(create_acts) => { Ok(create_acts) => {
for create_act in create_acts { for create_act in create_acts {
match create_act.create_props.object_object::<LicensedArticle>() { match create_act
Ok(article) => { .object_field_ref()
Post::from_activity(conn, article) .as_single_base()
.and_then(|base| {
let any_base = AnyBase::from_base(base.clone()); // FIXME: Don't clone()
any_base.extend::<LicensedArticle07, ArticleType>().ok()
}) {
Some(Some(article)) => {
Post::from_activity07(conn, article)
.expect("Article from remote user couldn't be saved"); .expect("Article from remote user couldn't be saved");
info!("Fetched article from remote user"); info!("Fetched article from remote user");
} }
Err(e) => warn!("Error while fetching articles in background: {:?}", e), _ => warn!("Error while fetching articles in background"),
} }
} }
} }
@ -89,7 +99,7 @@ fn fetch_and_cache_followers(user: &Arc<User>, conn: &DbConn) {
match follower_ids { match follower_ids {
Ok(user_ids) => { Ok(user_ids) => {
for user_id in user_ids { for user_id in user_ids {
let follower = User::from_id(conn, &user_id, None, CONFIG.proxy()); let follower = User::from_id07(conn, &user_id, None, CONFIG.proxy());
match follower { match follower {
Ok(follower) => { Ok(follower) => {
let inserted = follows::Follow::insert( let inserted = follows::Follow::insert(

View file

@ -12,7 +12,7 @@ use activitystreams::{
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use plume_common::activity_pub::{ use plume_common::activity_pub::{
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07}, inbox::{AsActor, AsObject, AsObject07, FromId07},
sign::Signer, sign::Signer,
Id, IntoId, PUBLIC_VISIBILITY, Id, IntoId, PUBLIC_VISIBILITY,
}; };
@ -186,46 +186,6 @@ impl AsObject07<User, Announce07, &DbConn> for Post {
} }
} }
impl FromId<DbConn> for Reshare {
type Error = Error;
type Object = Announce;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
Reshare::find_by_ap_url(conn, id)
}
fn from_activity(conn: &DbConn, act: Announce) -> Result<Self> {
let res = Reshare::insert(
conn,
NewReshare {
post_id: Post::from_id(
conn,
&act.announce_props.object_link::<Id>()?,
None,
CONFIG.proxy(),
)
.map_err(|(_, e)| e)?
.id,
user_id: User::from_id(
conn,
&act.announce_props.actor_link::<Id>()?,
None,
CONFIG.proxy(),
)
.map_err(|(_, e)| e)?
.id,
ap_url: act.object_props.id_string()?,
},
)?;
res.notify(conn)?;
Ok(res)
}
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
}
impl FromId07<DbConn> for Reshare { impl FromId07<DbConn> for Reshare {
type Error = Error; type Error = Error;
type Object = Announce07; type Object = Announce07;
@ -238,7 +198,7 @@ impl FromId07<DbConn> for Reshare {
let res = Reshare::insert( let res = Reshare::insert(
conn, conn,
NewReshare { NewReshare {
post_id: Post::from_id( post_id: Post::from_id07(
conn, conn,
act.object_field_ref() act.object_field_ref()
.as_single_id() .as_single_id()
@ -249,7 +209,7 @@ impl FromId07<DbConn> for Reshare {
) )
.map_err(|(_, e)| e)? .map_err(|(_, e)| e)?
.id, .id,
user_id: User::from_id( user_id: User::from_id07(
conn, conn,
act.actor_field_ref() act.actor_field_ref()
.as_single_id() .as_single_id()

View file

@ -13,7 +13,7 @@ use activitypub::{
}; };
use activitystreams::{ use activitystreams::{
activity::Delete as Delete07, activity::Delete as Delete07,
actor::AsApActor, actor::{ApActor, AsApActor},
actor::{ApActor as ApActor07, Endpoints as Endpoints07, Person as Person07}, actor::{ApActor as ApActor07, Endpoints as Endpoints07, Person as Person07},
base::{AnyBase, Base}, base::{AnyBase, Base},
collection::{ collection::{
@ -21,7 +21,7 @@ use activitystreams::{
}, },
iri_string::types::IriString, iri_string::types::IriString,
markers::Activity as Activity07, markers::Activity as Activity07,
object::{AsObject as _, Image as Image07, Tombstone as Tombstone07}, object::{kind::ImageType, AsObject as _, Image as Image07, Tombstone as Tombstone07},
prelude::*, prelude::*,
}; };
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
@ -35,7 +35,7 @@ use openssl::{
}; };
use plume_common::{ use plume_common::{
activity_pub::{ activity_pub::{
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07}, inbox::{AsActor, AsObject, AsObject07, FromId07},
request::get, request::get,
sign::{gen_keypair, Error as SignError, Result as SignResult, Signer}, sign::{gen_keypair, Error as SignError, Result as SignResult, Signer},
ActivityStream, ApSignature, ApSignature07, CustomPerson as CustomPerson07, Id, IntoId, ActivityStream, ApSignature, ApSignature07, CustomPerson as CustomPerson07, Id, IntoId,
@ -53,7 +53,6 @@ use std::{
hash::{Hash, Hasher}, hash::{Hash, Hasher},
sync::Arc, sync::Arc,
}; };
use url::Url;
use webfinger::*; use webfinger::*;
pub type CustomPerson = CustomObject<ApSignature, Person>; pub type CustomPerson = CustomObject<ApSignature, Person>;
@ -238,7 +237,7 @@ impl User {
.into_iter() .into_iter()
.find(|l| l.mime_type == Some(String::from("application/activity+json"))) .find(|l| l.mime_type == Some(String::from("application/activity+json")))
.ok_or(Error::Webfinger)?; .ok_or(Error::Webfinger)?;
User::from_id( User::from_id07(
conn, conn,
link.href.as_ref().ok_or(Error::Webfinger)?, link.href.as_ref().ok_or(Error::Webfinger)?,
None, None,
@ -256,53 +255,84 @@ impl User {
.ok_or(Error::Webfinger) .ok_or(Error::Webfinger)
} }
fn fetch(url: &str) -> Result<CustomPerson> { fn fetch(url: &str) -> Result<CustomPerson07> {
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?; let mut res = get(url, Self::get_sender07(), CONFIG.proxy().cloned())?;
let text = &res.text()?; let text = &res.text()?;
// without this workaround, publicKey is not correctly deserialized // without this workaround, publicKey is not correctly deserialized
let ap_sign = serde_json::from_str::<ApSignature>(text)?; let ap_sign = serde_json::from_str::<ApSignature07>(text)?;
let mut json = serde_json::from_str::<CustomPerson>(text)?; let person = serde_json::from_str::<Person07>(text)?;
json.custom_props = ap_sign; let json = CustomPerson07::new(
ApActor::new(
person
.clone()
.id_unchecked()
.ok_or(Error::MissingApProperty)?
.to_owned(),
person,
),
ap_sign,
); // FIXME: Don't clone()
Ok(json) Ok(json)
} }
pub fn fetch_from_url(conn: &DbConn, url: &str) -> Result<User> { pub fn fetch_from_url(conn: &DbConn, url: &str) -> Result<User> {
User::fetch(url).and_then(|json| User::from_activity(conn, json)) User::fetch(url).and_then(|json| User::from_activity07(conn, json))
} }
pub fn refetch(&self, conn: &Connection) -> Result<()> { pub fn refetch(&self, conn: &Connection) -> Result<()> {
User::fetch(&self.ap_url.clone()).and_then(|json| { User::fetch(&self.ap_url.clone()).and_then(|json| {
let avatar = Media::save_remote( let avatar = Media::save_remote(
conn, conn,
json.object json.ap_actor_ref()
.object_props .icon()
.icon_image()? // FIXME: Fails when icon is not set .ok_or(Error::MissingApProperty)? // FIXME: Fails when icon is not set
.object_props .iter()
.url_string()?, .next()
.and_then(|i| {
i.clone()
.extend::<Image07, ImageType>() // FIXME: Don't clone()
.ok()?
.and_then(|url| Some(url.id_unchecked()?.to_string()))
})
.ok_or(Error::MissingApProperty)?,
self, self,
) )
.ok(); .ok();
let pub_key = &json.ext_one.public_key.public_key_pem;
diesel::update(self) diesel::update(self)
.set(( .set((
users::username.eq(json.object.ap_actor_props.preferred_username_string()?), users::username.eq(json
users::display_name.eq(json.object.object_props.name_string()?), .ap_actor_ref()
users::outbox_url.eq(json.object.ap_actor_props.outbox_string()?), .preferred_username()
users::inbox_url.eq(json.object.ap_actor_props.inbox_string()?), .ok_or(Error::MissingApProperty)?),
users::display_name.eq(json
.ap_actor_ref()
.name()
.ok_or(Error::MissingApProperty)?
.to_as_string()
.ok_or(Error::MissingApProperty)?),
users::outbox_url.eq(json
.ap_actor_ref()
.outbox()?
.ok_or(Error::MissingApProperty)?
.as_str()),
users::inbox_url.eq(json.ap_actor_ref().inbox()?.as_str()),
users::summary.eq(SafeString::new( users::summary.eq(SafeString::new(
&json &json
.object .ap_actor_ref()
.object_props .summary()
.summary_string() .and_then(|summary| summary.to_as_string())
.unwrap_or_default(), .unwrap_or_default(),
)), )),
users::followers_endpoint.eq(json.object.ap_actor_props.followers_string()?), users::followers_endpoint.eq(json
.ap_actor_ref()
.followers()?
.ok_or(Error::MissingApProperty)?
.as_str()),
users::avatar_id.eq(avatar.map(|a| a.id)), users::avatar_id.eq(avatar.map(|a| a.id)),
users::last_fetched_date.eq(Utc::now().naive_utc()), users::last_fetched_date.eq(Utc::now().naive_utc()),
users::public_key.eq(json users::public_key.eq(pub_key),
.custom_props
.public_key_publickey()?
.public_key_pem_string()?),
)) ))
.execute(conn) .execute(conn)
.map(|_| ()) .map(|_| ())
@ -548,7 +578,7 @@ impl User {
Ok(coll) Ok(coll)
} }
fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> { fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> {
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?; let mut res = get(url, Self::get_sender07(), CONFIG.proxy().cloned())?;
let text = &res.text()?; let text = &res.text()?;
let json: serde_json::Value = serde_json::from_str(text)?; let json: serde_json::Value = serde_json::from_str(text)?;
let items = json["items"] let items = json["items"]
@ -565,7 +595,7 @@ impl User {
&self, &self,
url: &str, url: &str,
) -> Result<(Vec<T>, Option<String>)> { ) -> Result<(Vec<T>, Option<String>)> {
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?; let mut res = get(url, Self::get_sender07(), CONFIG.proxy().cloned())?;
let text = &res.text()?; let text = &res.text()?;
let json: serde_json::Value = serde_json::from_str(text)?; let json: serde_json::Value = serde_json::from_str(text)?;
let items = json["items"] let items = json["items"]
@ -581,7 +611,7 @@ impl User {
pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> { pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
let mut res = get( let mut res = get(
&self.outbox_url[..], &self.outbox_url[..],
Self::get_sender(), Self::get_sender07(),
CONFIG.proxy().cloned(), CONFIG.proxy().cloned(),
)?; )?;
let text = &res.text()?; let text = &res.text()?;
@ -617,7 +647,7 @@ impl User {
pub fn fetch_outbox07<T: Activity07 + serde::de::DeserializeOwned>(&self) -> Result<Vec<T>> { pub fn fetch_outbox07<T: Activity07 + serde::de::DeserializeOwned>(&self) -> Result<Vec<T>> {
let mut res = get( let mut res = get(
&self.outbox_url[..], &self.outbox_url[..],
Self::get_sender(), Self::get_sender07(),
CONFIG.proxy().cloned(), CONFIG.proxy().cloned(),
)?; )?;
let text = &res.text()?; let text = &res.text()?;
@ -653,7 +683,7 @@ impl User {
pub fn fetch_followers_ids(&self) -> Result<Vec<String>> { pub fn fetch_followers_ids(&self) -> Result<Vec<String>> {
let mut res = get( let mut res = get(
&self.followers_endpoint[..], &self.followers_endpoint[..],
Self::get_sender(), Self::get_sender07(),
CONFIG.proxy().cloned(), CONFIG.proxy().cloned(),
)?; )?;
let text = &res.text()?; let text = &res.text()?;
@ -1097,110 +1127,6 @@ impl IntoId for User {
impl Eq for User {} impl Eq for User {}
impl FromId<DbConn> for User {
type Error = Error;
type Object = CustomPerson;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id)
}
fn from_activity(conn: &DbConn, acct: CustomPerson) -> Result<Self> {
let url = Url::parse(&acct.object.object_props.id_string()?)?;
let inst = url.host_str().ok_or(Error::Url)?;
let instance = Instance::find_by_domain(conn, inst).or_else(|_| {
Instance::insert(
conn,
NewInstance {
name: inst.to_owned(),
public_domain: inst.to_owned(),
local: false,
// We don't really care about all the following for remote instances
long_description: SafeString::new(""),
short_description: SafeString::new(""),
default_license: String::new(),
open_registrations: true,
short_description_html: String::new(),
long_description_html: String::new(),
},
)
})?;
let username = acct.object.ap_actor_props.preferred_username_string()?;
if username.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
return Err(Error::InvalidValue);
}
let fqn = if instance.local {
username.clone()
} else {
format!("{}@{}", username, instance.public_domain)
};
let user = User::insert(
conn,
NewUser {
display_name: acct
.object
.object_props
.name_string()
.unwrap_or_else(|_| username.clone()),
username,
outbox_url: acct.object.ap_actor_props.outbox_string()?,
inbox_url: acct.object.ap_actor_props.inbox_string()?,
role: 2,
summary: acct
.object
.object_props
.summary_string()
.unwrap_or_default(),
summary_html: SafeString::new(
&acct
.object
.object_props
.summary_string()
.unwrap_or_default(),
),
email: None,
hashed_password: None,
instance_id: instance.id,
ap_url: acct.object.object_props.id_string()?,
public_key: acct
.custom_props
.public_key_publickey()?
.public_key_pem_string()?,
private_key: None,
shared_inbox_url: acct
.object
.ap_actor_props
.endpoints_endpoint()
.and_then(|e| e.shared_inbox_string())
.ok(),
followers_endpoint: acct.object.ap_actor_props.followers_string()?,
fqn,
avatar_id: None,
},
)?;
if let Ok(icon) = acct.object.object_props.icon_image() {
if let Ok(url) = icon.object_props.url_string() {
let avatar = Media::save_remote(conn, url, &user);
if let Ok(avatar) = avatar {
user.set_avatar(conn, avatar.id)?;
}
}
}
Ok(user)
}
fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user")
}
}
impl FromId07<DbConn> for User { impl FromId07<DbConn> for User {
type Error = Error; type Error = Error;
type Object = CustomPerson07; type Object = CustomPerson07;
@ -1690,32 +1616,6 @@ pub(crate) mod tests {
}); });
} }
#[test]
fn self_federation() {
let conn = db();
conn.test_transaction::<_, (), _>(|| {
let users = fill_database(&conn);
let ap_repr = users[0].to_activity(&conn).unwrap();
users[0].delete(&conn).unwrap();
let user = User::from_activity(&conn, ap_repr).unwrap();
assert_eq!(user.username, users[0].username);
assert_eq!(user.display_name, users[0].display_name);
assert_eq!(user.outbox_url, users[0].outbox_url);
assert_eq!(user.inbox_url, users[0].inbox_url);
assert_eq!(user.instance_id, users[0].instance_id);
assert_eq!(user.ap_url, users[0].ap_url);
assert_eq!(user.public_key, users[0].public_key);
assert_eq!(user.shared_inbox_url, users[0].shared_inbox_url);
assert_eq!(user.followers_endpoint, users[0].followers_endpoint);
assert_eq!(user.avatar_url(&conn), users[0].avatar_url(&conn));
assert_eq!(user.fqn, users[0].fqn);
assert_eq!(user.summary_html, users[0].summary_html);
Ok(())
});
}
#[test] #[test]
fn self_federation07() { fn self_federation07() {
let conn = db(); let conn = db();

View file

@ -1,5 +1,5 @@
use plume_common::activity_pub::{ use plume_common::activity_pub::{
inbox::FromId, inbox::FromId07,
request::Digest, request::Digest,
sign::{verify_http_headers, Signable}, sign::{verify_http_headers, Signable},
}; };
@ -26,7 +26,7 @@ pub fn handle_incoming(
.or_else(|| activity["actor"]["id"].as_str()) .or_else(|| activity["actor"]["id"].as_str())
.ok_or(status::BadRequest(Some("Missing actor id for activity")))?; .ok_or(status::BadRequest(Some("Missing actor id for activity")))?;
let actor = User::from_id(&conn, actor_id, None, CONFIG.proxy()) let actor = User::from_id07(&conn, actor_id, None, CONFIG.proxy())
.expect("instance::shared_inbox: user error"); .expect("instance::shared_inbox: user error");
if !verify_http_headers(&actor, &headers.0, &sig).is_secure() && !act.clone().verify(&actor) { if !verify_http_headers(&actor, &headers.0, &sig).is_secure() && !act.clone().verify(&actor) {
// maybe we just know an old key? // maybe we just know an old key?

View file

@ -11,7 +11,7 @@ use validator::{Validate, ValidationErrors};
use crate::inbox; use crate::inbox;
use crate::routes::{errors::ErrorPage, rocket_uri_macro_static_files, Page, RespondOrRedirect}; use crate::routes::{errors::ErrorPage, rocket_uri_macro_static_files, Page, RespondOrRedirect};
use crate::template_utils::{IntoContext, Ructe}; use crate::template_utils::{IntoContext, Ructe};
use plume_common::activity_pub::{broadcast, inbox::FromId}; use plume_common::activity_pub::{broadcast, inbox::FromId07};
use plume_models::{ use plume_models::{
admin::*, admin::*,
blocklisted_emails::*, blocklisted_emails::*,
@ -404,7 +404,7 @@ pub fn interact(conn: DbConn, user: Option<User>, target: String) -> Option<Redi
return Some(Redirect::to(uri!(super::user::details: name = target))); return Some(Redirect::to(uri!(super::user::details: name = target)));
} }
if let Ok(post) = Post::from_id(&conn, &target, None, CONFIG.proxy()) { if let Ok(post) = Post::from_id07(&conn, &target, None, CONFIG.proxy()) {
return Some(Redirect::to(uri!( return Some(Redirect::to(uri!(
super::posts::details: blog = post.get_blog(&conn).expect("Can't retrieve blog").fqn, super::posts::details: blog = post.get_blog(&conn).expect("Can't retrieve blog").fqn,
slug = &post.slug, slug = &post.slug,
@ -412,7 +412,7 @@ pub fn interact(conn: DbConn, user: Option<User>, target: String) -> Option<Redi
))); )));
} }
if let Ok(comment) = Comment::from_id(&conn, &target, None, CONFIG.proxy()) { if let Ok(comment) = Comment::from_id07(&conn, &target, None, CONFIG.proxy()) {
if comment.can_see(&conn, user.as_ref()) { if comment.can_see(&conn, user.as_ref()) {
let post = comment.get_post(&conn).expect("Can't retrieve post"); let post = comment.get_post(&conn).expect("Can't retrieve post");
return Some(Redirect::to(uri!( return Some(Redirect::to(uri!(