Rmove FromId
This commit is contained in:
parent
2165c286ae
commit
2804f44a06
@ -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.
|
||||
///
|
||||
/// The two functions to implement are `from_activity` to create (and save) a new object
|
||||
@ -808,22 +742,6 @@ mod tests {
|
||||
}
|
||||
|
||||
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 {
|
||||
type Error = ();
|
||||
type Object = Person07;
|
||||
@ -852,22 +770,6 @@ mod tests {
|
||||
}
|
||||
|
||||
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 {
|
||||
type Error = ();
|
||||
type Output = ();
|
||||
@ -1017,15 +919,6 @@ mod tests {
|
||||
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]
|
||||
fn test_inbox_basic07() {
|
||||
let act = serde_json::to_value(build_create07()).unwrap();
|
||||
@ -1035,18 +928,6 @@ mod tests {
|
||||
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]
|
||||
fn test_inbox_multi_handlers07() {
|
||||
let act = serde_json::to_value(build_create()).unwrap();
|
||||
@ -1059,17 +940,6 @@ mod tests {
|
||||
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]
|
||||
fn test_inbox_failure07() {
|
||||
let act = serde_json::to_value(build_create07()).unwrap();
|
||||
@ -1082,22 +952,6 @@ mod tests {
|
||||
}
|
||||
|
||||
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 {
|
||||
fn get_inbox_url(&self) -> String {
|
||||
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]
|
||||
fn test_inbox_actor_failure07() {
|
||||
let act = serde_json::to_value(build_create07()).unwrap();
|
||||
|
@ -27,11 +27,10 @@ use openssl::{
|
||||
sign::{Signer, Verifier},
|
||||
};
|
||||
use plume_common::activity_pub::{
|
||||
inbox::{AsActor, FromId, FromId07},
|
||||
inbox::{AsActor, FromId07},
|
||||
sign, ActivityStream, ApSignature, ApSignature07, CustomGroup as CustomGroup07, Id, IntoId,
|
||||
PublicKey, PublicKey07, Source, SourceProperty, ToAsString, ToAsUri,
|
||||
};
|
||||
use url::Url;
|
||||
use webfinger::*;
|
||||
|
||||
pub type CustomGroup = CustomObject<ApSignature, Group>;
|
||||
@ -161,7 +160,7 @@ impl Blog {
|
||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||
.ok_or(Error::Webfinger)
|
||||
.and_then(|l| {
|
||||
Blog::from_id(
|
||||
Blog::from_id07(
|
||||
conn,
|
||||
&l.href.ok_or(Error::MissingApProperty)?,
|
||||
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 {
|
||||
type Error = Error;
|
||||
type Object = CustomGroup07;
|
||||
@ -648,7 +543,7 @@ impl FromId07<DbConn> for Blog {
|
||||
Media::save_remote(
|
||||
conn,
|
||||
banner.url()?.to_as_uri()?,
|
||||
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
|
||||
&User::from_id07(conn, &owner, None, CONFIG.proxy()).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]
|
||||
fn self_federation07() {
|
||||
let conn = &db();
|
||||
|
@ -30,7 +30,7 @@ use chrono::{self, NaiveDateTime, TimeZone, Utc};
|
||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
||||
use plume_common::{
|
||||
activity_pub::{
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07},
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId07},
|
||||
sign::Signer,
|
||||
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(¬e.object_props.to)
|
||||
|| is_public(¬e.object_props.bto)
|
||||
|| is_public(¬e.object_props.cc)
|
||||
|| is_public(¬e.object_props.bcc);
|
||||
|
||||
let comm = Comment::insert(
|
||||
conn,
|
||||
NewComment {
|
||||
content: SafeString::new(¬e.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,
|
||||
¬e.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 {
|
||||
type Error = Error;
|
||||
type Object = Note07;
|
||||
@ -484,7 +349,7 @@ impl FromId07<DbConn> for Comment {
|
||||
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>
|
||||
})?,
|
||||
author_id: User::from_id(
|
||||
author_id: User::from_id07(
|
||||
conn,
|
||||
¬e
|
||||
.attributed_to()
|
||||
@ -537,7 +402,7 @@ impl FromId07<DbConn> for Comment {
|
||||
let receivers_ap_url = receiver_ids
|
||||
.into_iter()
|
||||
.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]
|
||||
} else {
|
||||
vec![] // TODO try to fetch collection
|
||||
|
@ -12,7 +12,7 @@ use activitystreams::{
|
||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
||||
use plume_common::activity_pub::{
|
||||
broadcast, broadcast07,
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07},
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId07},
|
||||
sign::Signer,
|
||||
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 {
|
||||
type Error = Error;
|
||||
type Object = FollowAct07;
|
||||
|
@ -12,7 +12,7 @@ use activitystreams::{
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
use plume_common::activity_pub::{
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07},
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId07},
|
||||
sign::Signer,
|
||||
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 {
|
||||
type Error = Error;
|
||||
type Object = Like07;
|
||||
@ -210,7 +170,7 @@ impl FromId07<DbConn> for Like {
|
||||
let res = Like::insert(
|
||||
conn,
|
||||
NewLike {
|
||||
post_id: Post::from_id(
|
||||
post_id: Post::from_id07(
|
||||
conn,
|
||||
act.object_field_ref()
|
||||
.as_single_id()
|
||||
|
@ -7,7 +7,7 @@ use activitystreams::{object::Image as Image07, prelude::*};
|
||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
use guid_create::GUID;
|
||||
use plume_common::{
|
||||
activity_pub::{inbox::FromId, request, Id, ToAsString, ToAsUri},
|
||||
activity_pub::{inbox::FromId07, request, Id, ToAsString, ToAsUri},
|
||||
utils::{escape, MediaProcessor},
|
||||
};
|
||||
use std::{
|
||||
@ -222,7 +222,7 @@ impl Media {
|
||||
// TODO: conditional GET
|
||||
request::get(
|
||||
remote_url.as_str(),
|
||||
User::get_sender(),
|
||||
User::get_sender07(),
|
||||
CONFIG.proxy().cloned(),
|
||||
)?
|
||||
.copy_to(&mut dest)?;
|
||||
@ -275,7 +275,7 @@ impl Media {
|
||||
remote_url: None,
|
||||
sensitive: image.object_props.summary_string().is_ok(),
|
||||
content_warning: image.object_props.summary_string().ok(),
|
||||
owner_id: User::from_id(
|
||||
owner_id: User::from_id07(
|
||||
conn,
|
||||
image
|
||||
.object_props
|
||||
@ -311,7 +311,7 @@ impl Media {
|
||||
// TODO: conditional GET
|
||||
request::get(
|
||||
remote_url.as_str(),
|
||||
User::get_sender(),
|
||||
User::get_sender07(),
|
||||
CONFIG.proxy().cloned(),
|
||||
)?
|
||||
.copy_to(&mut dest)?;
|
||||
@ -362,7 +362,7 @@ impl Media {
|
||||
remote_url: None,
|
||||
sensitive: summary.is_some(),
|
||||
content_warning: summary,
|
||||
owner_id: User::from_id(
|
||||
owner_id: User::from_id07(
|
||||
conn,
|
||||
&image
|
||||
.attributed_to()
|
||||
|
@ -26,7 +26,7 @@ use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
use once_cell::sync::Lazy;
|
||||
use plume_common::{
|
||||
activity_pub::{
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07},
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId07},
|
||||
sign::Signer,
|
||||
Hashtag, Hashtag07, HashtagType07, Id, IntoId, Licensed, Licensed07,
|
||||
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 {
|
||||
type Error = Error;
|
||||
type Object = LicensedArticle07;
|
||||
@ -1273,43 +1119,6 @@ pub struct PostUpdate {
|
||||
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 {
|
||||
type Error = Error;
|
||||
type Object = LicensedArticle07;
|
||||
@ -1373,7 +1182,7 @@ impl AsObject<User, Update, &DbConn> for PostUpdate {
|
||||
|
||||
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
||||
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)? {
|
||||
// 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<()> {
|
||||
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)? {
|
||||
// TODO: maybe the author was added in the meantime
|
||||
|
@ -1,12 +1,16 @@
|
||||
use crate::{
|
||||
db_conn::{DbConn, DbPool},
|
||||
follows,
|
||||
posts::{LicensedArticle, Post},
|
||||
posts::Post,
|
||||
users::{User, UserEvent},
|
||||
ACTOR_SYS, CONFIG, USER_CHAN,
|
||||
};
|
||||
use activitypub::activity::Create;
|
||||
use plume_common::activity_pub::inbox::FromId;
|
||||
use activitystreams::{
|
||||
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 std::sync::Arc;
|
||||
use tracing::{error, info, warn};
|
||||
@ -64,17 +68,23 @@ impl ActorFactoryArgs<DbPool> for RemoteFetchActor {
|
||||
}
|
||||
|
||||
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 {
|
||||
Ok(create_acts) => {
|
||||
for create_act in create_acts {
|
||||
match create_act.create_props.object_object::<LicensedArticle>() {
|
||||
Ok(article) => {
|
||||
Post::from_activity(conn, article)
|
||||
match create_act
|
||||
.object_field_ref()
|
||||
.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");
|
||||
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 {
|
||||
Ok(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 {
|
||||
Ok(follower) => {
|
||||
let inserted = follows::Follow::insert(
|
||||
|
@ -12,7 +12,7 @@ use activitystreams::{
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
use plume_common::activity_pub::{
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07},
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId07},
|
||||
sign::Signer,
|
||||
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 {
|
||||
type Error = Error;
|
||||
type Object = Announce07;
|
||||
@ -238,7 +198,7 @@ impl FromId07<DbConn> for Reshare {
|
||||
let res = Reshare::insert(
|
||||
conn,
|
||||
NewReshare {
|
||||
post_id: Post::from_id(
|
||||
post_id: Post::from_id07(
|
||||
conn,
|
||||
act.object_field_ref()
|
||||
.as_single_id()
|
||||
@ -249,7 +209,7 @@ impl FromId07<DbConn> for Reshare {
|
||||
)
|
||||
.map_err(|(_, e)| e)?
|
||||
.id,
|
||||
user_id: User::from_id(
|
||||
user_id: User::from_id07(
|
||||
conn,
|
||||
act.actor_field_ref()
|
||||
.as_single_id()
|
||||
|
@ -13,7 +13,7 @@ use activitypub::{
|
||||
};
|
||||
use activitystreams::{
|
||||
activity::Delete as Delete07,
|
||||
actor::AsApActor,
|
||||
actor::{ApActor, AsApActor},
|
||||
actor::{ApActor as ApActor07, Endpoints as Endpoints07, Person as Person07},
|
||||
base::{AnyBase, Base},
|
||||
collection::{
|
||||
@ -21,7 +21,7 @@ use activitystreams::{
|
||||
},
|
||||
iri_string::types::IriString,
|
||||
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::*,
|
||||
};
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
@ -35,7 +35,7 @@ use openssl::{
|
||||
};
|
||||
use plume_common::{
|
||||
activity_pub::{
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId, FromId07},
|
||||
inbox::{AsActor, AsObject, AsObject07, FromId07},
|
||||
request::get,
|
||||
sign::{gen_keypair, Error as SignError, Result as SignResult, Signer},
|
||||
ActivityStream, ApSignature, ApSignature07, CustomPerson as CustomPerson07, Id, IntoId,
|
||||
@ -53,7 +53,6 @@ use std::{
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
use url::Url;
|
||||
use webfinger::*;
|
||||
|
||||
pub type CustomPerson = CustomObject<ApSignature, Person>;
|
||||
@ -238,7 +237,7 @@ impl User {
|
||||
.into_iter()
|
||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||
.ok_or(Error::Webfinger)?;
|
||||
User::from_id(
|
||||
User::from_id07(
|
||||
conn,
|
||||
link.href.as_ref().ok_or(Error::Webfinger)?,
|
||||
None,
|
||||
@ -256,53 +255,84 @@ impl User {
|
||||
.ok_or(Error::Webfinger)
|
||||
}
|
||||
|
||||
fn fetch(url: &str) -> Result<CustomPerson> {
|
||||
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
|
||||
fn fetch(url: &str) -> Result<CustomPerson07> {
|
||||
let mut res = get(url, Self::get_sender07(), CONFIG.proxy().cloned())?;
|
||||
let text = &res.text()?;
|
||||
// without this workaround, publicKey is not correctly deserialized
|
||||
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
|
||||
let mut json = serde_json::from_str::<CustomPerson>(text)?;
|
||||
json.custom_props = ap_sign;
|
||||
let ap_sign = serde_json::from_str::<ApSignature07>(text)?;
|
||||
let person = serde_json::from_str::<Person07>(text)?;
|
||||
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)
|
||||
}
|
||||
|
||||
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<()> {
|
||||
User::fetch(&self.ap_url.clone()).and_then(|json| {
|
||||
let avatar = Media::save_remote(
|
||||
conn,
|
||||
json.object
|
||||
.object_props
|
||||
.icon_image()? // FIXME: Fails when icon is not set
|
||||
.object_props
|
||||
.url_string()?,
|
||||
json.ap_actor_ref()
|
||||
.icon()
|
||||
.ok_or(Error::MissingApProperty)? // FIXME: Fails when icon is not set
|
||||
.iter()
|
||||
.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,
|
||||
)
|
||||
.ok();
|
||||
|
||||
let pub_key = &json.ext_one.public_key.public_key_pem;
|
||||
diesel::update(self)
|
||||
.set((
|
||||
users::username.eq(json.object.ap_actor_props.preferred_username_string()?),
|
||||
users::display_name.eq(json.object.object_props.name_string()?),
|
||||
users::outbox_url.eq(json.object.ap_actor_props.outbox_string()?),
|
||||
users::inbox_url.eq(json.object.ap_actor_props.inbox_string()?),
|
||||
users::username.eq(json
|
||||
.ap_actor_ref()
|
||||
.preferred_username()
|
||||
.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(
|
||||
&json
|
||||
.object
|
||||
.object_props
|
||||
.summary_string()
|
||||
.ap_actor_ref()
|
||||
.summary()
|
||||
.and_then(|summary| summary.to_as_string())
|
||||
.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::last_fetched_date.eq(Utc::now().naive_utc()),
|
||||
users::public_key.eq(json
|
||||
.custom_props
|
||||
.public_key_publickey()?
|
||||
.public_key_pem_string()?),
|
||||
users::public_key.eq(pub_key),
|
||||
))
|
||||
.execute(conn)
|
||||
.map(|_| ())
|
||||
@ -548,7 +578,7 @@ impl User {
|
||||
Ok(coll)
|
||||
}
|
||||
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 json: serde_json::Value = serde_json::from_str(text)?;
|
||||
let items = json["items"]
|
||||
@ -565,7 +595,7 @@ impl User {
|
||||
&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 json: serde_json::Value = serde_json::from_str(text)?;
|
||||
let items = json["items"]
|
||||
@ -581,7 +611,7 @@ impl User {
|
||||
pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
|
||||
let mut res = get(
|
||||
&self.outbox_url[..],
|
||||
Self::get_sender(),
|
||||
Self::get_sender07(),
|
||||
CONFIG.proxy().cloned(),
|
||||
)?;
|
||||
let text = &res.text()?;
|
||||
@ -617,7 +647,7 @@ impl User {
|
||||
pub fn fetch_outbox07<T: Activity07 + serde::de::DeserializeOwned>(&self) -> Result<Vec<T>> {
|
||||
let mut res = get(
|
||||
&self.outbox_url[..],
|
||||
Self::get_sender(),
|
||||
Self::get_sender07(),
|
||||
CONFIG.proxy().cloned(),
|
||||
)?;
|
||||
let text = &res.text()?;
|
||||
@ -653,7 +683,7 @@ impl User {
|
||||
pub fn fetch_followers_ids(&self) -> Result<Vec<String>> {
|
||||
let mut res = get(
|
||||
&self.followers_endpoint[..],
|
||||
Self::get_sender(),
|
||||
Self::get_sender07(),
|
||||
CONFIG.proxy().cloned(),
|
||||
)?;
|
||||
let text = &res.text()?;
|
||||
@ -1097,110 +1127,6 @@ impl IntoId 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 {
|
||||
type Error = Error;
|
||||
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]
|
||||
fn self_federation07() {
|
||||
let conn = db();
|
||||
|
@ -1,5 +1,5 @@
|
||||
use plume_common::activity_pub::{
|
||||
inbox::FromId,
|
||||
inbox::FromId07,
|
||||
request::Digest,
|
||||
sign::{verify_http_headers, Signable},
|
||||
};
|
||||
@ -26,7 +26,7 @@ pub fn handle_incoming(
|
||||
.or_else(|| activity["actor"]["id"].as_str())
|
||||
.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");
|
||||
if !verify_http_headers(&actor, &headers.0, &sig).is_secure() && !act.clone().verify(&actor) {
|
||||
// maybe we just know an old key?
|
||||
|
@ -11,7 +11,7 @@ use validator::{Validate, ValidationErrors};
|
||||
use crate::inbox;
|
||||
use crate::routes::{errors::ErrorPage, rocket_uri_macro_static_files, Page, RespondOrRedirect};
|
||||
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::{
|
||||
admin::*,
|
||||
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)));
|
||||
}
|
||||
|
||||
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!(
|
||||
super::posts::details: blog = post.get_blog(&conn).expect("Can't retrieve blog").fqn,
|
||||
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()) {
|
||||
let post = comment.get_post(&conn).expect("Can't retrieve post");
|
||||
return Some(Redirect::to(uri!(
|
||||
|
Loading…
Reference in New Issue
Block a user