From 94cc260803f78dfd7e1e4b6c1f5f1b4beffa3fc8 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 22 Nov 2021 23:05:28 +0900 Subject: [PATCH 01/31] Add Instance to users::Role --- plume-models/src/users.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plume-models/src/users.rs b/plume-models/src/users.rs index 0b40d63f..d0325afd 100644 --- a/plume-models/src/users.rs +++ b/plume-models/src/users.rs @@ -52,6 +52,7 @@ pub enum Role { Admin = 0, Moderator = 1, Normal = 2, + Instance = 3, } #[derive(Queryable, Identifiable, Clone, Debug, AsChangeset)] @@ -78,6 +79,7 @@ pub struct User { pub summary_html: SafeString, /// 0 = admin /// 1 = moderator + /// 3 = local instance /// anything else = normal user pub role: i32, pub preferred_theme: Option, From 0da957262728e08ef339cc0b6806e764b17c158b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 22 Nov 2021 23:11:32 +0900 Subject: [PATCH 02/31] Implement User::get_local_instance_user() --- plume-models/src/instance.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/plume-models/src/instance.rs b/plume-models/src/instance.rs index 80babed5..cabc9b9b 100644 --- a/plume-models/src/instance.rs +++ b/plume-models/src/instance.rs @@ -3,11 +3,11 @@ use crate::{ medias::Media, safe_string::SafeString, schema::{instances, users}, - users::{Role, User}, + users::{NewUser, Role, User}, Connection, Error, Result, }; use chrono::NaiveDateTime; -use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; +use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl}; use plume_common::utils::md_to_html; use std::sync::RwLock; @@ -45,6 +45,8 @@ lazy_static! { static ref LOCAL_INSTANCE: RwLock> = RwLock::new(None); } +const LOCAL_INSTANCE_USER: &str = "__instance__"; + impl Instance { pub fn set_local(self) { LOCAL_INSTANCE.write().unwrap().replace(self); @@ -85,6 +87,28 @@ impl Instance { .map_err(Error::from) } + pub fn get_local_instance_user(conn: &Connection) -> Result { + users::table + .filter(users::role.eq(3)) + .first(conn) + .or_else(|err| match err { + NotFound => { + let instance = Instance::get_local().expect("Failed to get local instance"); + let email = format!("{}@{}", LOCAL_INSTANCE_USER, &instance.public_domain); + NewUser::new_local( + conn, + LOCAL_INSTANCE_USER.into(), + instance.public_domain, + Role::Instance, + "Local instance", + email, + None, + ) + } + _ => Err(Error::Db(err)), + }) + } + insert!(instances, NewInstance); get!(instances); find_by!(instances, find_by_domain, public_domain as &str); From 858806149a30f815eb7ff3dce642978195f7644f Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 24 Nov 2021 21:41:44 +0900 Subject: [PATCH 03/31] Use concrete Error for Signer --- plume-common/src/activity_pub/sign.rs | 17 +++++++++++------ plume-models/src/blogs.rs | 12 +++++------- plume-models/src/lib.rs | 8 +++++++- plume-models/src/users.rs | 14 ++++++-------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/plume-common/src/activity_pub/sign.rs b/plume-common/src/activity_pub/sign.rs index c97cb5dd..22aaf9d6 100644 --- a/plume-common/src/activity_pub/sign.rs +++ b/plume-common/src/activity_pub/sign.rs @@ -19,20 +19,25 @@ pub fn gen_keypair() -> (Vec, Vec) { #[derive(Debug)] pub struct Error(); +pub type Result = std::result::Result; + +impl From for Error { + fn from(_: openssl::error::ErrorStack) -> Self { + Self() + } +} pub trait Signer { - type Error; - fn get_key_id(&self) -> String; /// Sign some data with the signer keypair - fn sign(&self, to_sign: &str) -> Result, Self::Error>; + fn sign(&self, to_sign: &str) -> Result>; /// Verify if the signature is valid - fn verify(&self, data: &str, signature: &[u8]) -> Result; + fn verify(&self, data: &str, signature: &[u8]) -> Result; } pub trait Signable { - fn sign(&mut self, creator: &T) -> Result<&mut Self, Error> + fn sign(&mut self, creator: &T) -> Result<&mut Self> where T: Signer; fn verify(self, creator: &T) -> bool @@ -46,7 +51,7 @@ pub trait Signable { } impl Signable for serde_json::Value { - fn sign(&mut self, creator: &T) -> Result<&mut serde_json::Value, Error> { + fn sign(&mut self, creator: &T) -> Result<&mut serde_json::Value> { let creation_date = Utc::now().to_rfc3339(); let mut options = json!({ "type": "RsaSignature2017", diff --git a/plume-models/src/blogs.rs b/plume-models/src/blogs.rs index 4043c85e..da556225 100644 --- a/plume-models/src/blogs.rs +++ b/plume-models/src/blogs.rs @@ -462,24 +462,22 @@ impl AsActor<&PlumeRocket> for Blog { } impl sign::Signer for Blog { - type Error = Error; - fn get_key_id(&self) -> String { format!("{}#main-key", self.ap_url) } - fn sign(&self, to_sign: &str) -> Result> { - let key = self.get_keypair()?; + fn sign(&self, to_sign: &str) -> sign::Result> { + let key = self.get_keypair().map_err(|_| sign::Error())?; let mut signer = Signer::new(MessageDigest::sha256(), &key)?; signer.update(to_sign.as_bytes())?; - signer.sign_to_vec().map_err(Error::from) + signer.sign_to_vec().map_err(sign::Error::from) } - fn verify(&self, data: &str, signature: &[u8]) -> Result { + fn verify(&self, data: &str, signature: &[u8]) -> sign::Result { let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?; let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?; verifier.update(data.as_bytes())?; - verifier.verify(signature).map_err(Error::from) + verifier.verify(signature).map_err(sign::Error::from) } } diff --git a/plume-models/src/lib.rs b/plume-models/src/lib.rs index bd1474ec..33a5ec2b 100755 --- a/plume-models/src/lib.rs +++ b/plume-models/src/lib.rs @@ -17,7 +17,7 @@ extern crate serde_json; extern crate tantivy; use once_cell::sync::Lazy; -use plume_common::activity_pub::inbox::InboxError; +use plume_common::activity_pub::{inbox::InboxError, sign}; use posts::PostEvent; use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder}; use users::UserEvent; @@ -79,6 +79,12 @@ impl From for Error { } } +impl From for Error { + fn from(_: sign::Error) -> Self { + Error::Signature + } +} + impl From for Error { fn from(err: diesel::result::Error) -> Self { Error::Db(err) diff --git a/plume-models/src/users.rs b/plume-models/src/users.rs index d0325afd..04e27d5f 100644 --- a/plume-models/src/users.rs +++ b/plume-models/src/users.rs @@ -24,7 +24,7 @@ use plume_common::{ activity_pub::{ ap_accept_header, inbox::{AsActor, AsObject, FromId}, - sign::{gen_keypair, Signer}, + sign::{gen_keypair, Error as SignError, Result as SignResult, Signer}, ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY, }, utils, @@ -1071,24 +1071,22 @@ impl AsObject for User { } impl Signer for User { - type Error = Error; - fn get_key_id(&self) -> String { format!("{}#main-key", self.ap_url) } - fn sign(&self, to_sign: &str) -> Result> { - let key = self.get_keypair()?; + fn sign(&self, to_sign: &str) -> SignResult> { + let key = self.get_keypair().map_err(|_| SignError())?; let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?; signer.update(to_sign.as_bytes())?; - signer.sign_to_vec().map_err(Error::from) + signer.sign_to_vec().map_err(SignError::from) } - fn verify(&self, data: &str, signature: &[u8]) -> Result { + fn verify(&self, data: &str, signature: &[u8]) -> SignResult { let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?; let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?; verifier.update(data.as_bytes())?; - verifier.verify(signature).map_err(Error::from) + verifier.verify(signature).map_err(SignError::from) } } From 897ea8e11eff91742eb868d791f8eaa9cb3d89f5 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 24 Nov 2021 21:49:09 +0900 Subject: [PATCH 04/31] Change const name: LOCAL_INSTANCE_USER -> LOCAL_INSTANCE_USERNAME --- plume-models/src/instance.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plume-models/src/instance.rs b/plume-models/src/instance.rs index cabc9b9b..f3f05481 100644 --- a/plume-models/src/instance.rs +++ b/plume-models/src/instance.rs @@ -45,7 +45,7 @@ lazy_static! { static ref LOCAL_INSTANCE: RwLock> = RwLock::new(None); } -const LOCAL_INSTANCE_USER: &str = "__instance__"; +const LOCAL_INSTANCE_USERNAME: &str = "__instance__"; impl Instance { pub fn set_local(self) { @@ -94,10 +94,10 @@ impl Instance { .or_else(|err| match err { NotFound => { let instance = Instance::get_local().expect("Failed to get local instance"); - let email = format!("{}@{}", LOCAL_INSTANCE_USER, &instance.public_domain); + let email = format!("{}@{}", LOCAL_INSTANCE_USERNAME, &instance.public_domain); NewUser::new_local( conn, - LOCAL_INSTANCE_USER.into(), + LOCAL_INSTANCE_USERNAME.into(), instance.public_domain, Role::Instance, "Local instance", From 1506802c20dfaf433538ff7bd22c938c8c41b3a5 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 24 Nov 2021 22:04:35 +0900 Subject: [PATCH 05/31] Implement LOCAL_INSTANCE_USER and related methods --- plume-models/src/instance.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/plume-models/src/instance.rs b/plume-models/src/instance.rs index f3f05481..5f4905d7 100644 --- a/plume-models/src/instance.rs +++ b/plume-models/src/instance.rs @@ -8,6 +8,7 @@ use crate::{ }; use chrono::NaiveDateTime; use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl}; +use once_cell::sync::OnceCell; use plume_common::utils::md_to_html; use std::sync::RwLock; @@ -46,6 +47,7 @@ lazy_static! { } const LOCAL_INSTANCE_USERNAME: &str = "__instance__"; +static LOCAL_INSTANCE_USER: OnceCell = OnceCell::new(); impl Instance { pub fn set_local(self) { @@ -78,16 +80,11 @@ impl Instance { .map_err(Error::from) } - pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result> { - instances::table - .order(instances::public_domain.asc()) - .offset(min.into()) - .limit((max - min).into()) - .load::(conn) - .map_err(Error::from) + pub fn get_local_instance_user() -> Option<&'static User> { + LOCAL_INSTANCE_USER.get() } - pub fn get_local_instance_user(conn: &Connection) -> Result { + pub fn get_local_instance_user_uncached(conn: &Connection) -> Result { users::table .filter(users::role.eq(3)) .first(conn) @@ -109,6 +106,23 @@ impl Instance { }) } + pub fn cache_local_instance_user(conn: &Connection) { + let user = Self::get_local_instance_user_uncached(conn) + .expect("Failed to get local instance user"); + LOCAL_INSTANCE_USER + .set(user) + .expect("Failed to set local instance user"); + } + + pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result> { + instances::table + .order(instances::public_domain.asc()) + .offset(min.into()) + .limit((max - min).into()) + .load::(conn) + .map_err(Error::from) + } + insert!(instances, NewInstance); get!(instances); find_by!(instances, find_by_domain, public_domain as &str); From 2f7a5cbf563be603dc49233786769376445320b1 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 24 Nov 2021 22:07:25 +0900 Subject: [PATCH 06/31] Cache local instance user on start --- src/main.rs | 4 +++- src/routes/blogs.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 243a0b91..49eeb812 100755 --- a/src/main.rs +++ b/src/main.rs @@ -57,7 +57,9 @@ fn init_pool() -> Option { builder = builder.max_size(max_size); }; let pool = builder.build(manager).ok()?; - Instance::cache_local(&pool.get().unwrap()); + let conn = pool.get().unwrap(); + Instance::cache_local(&conn); + Instance::cache_local_instance_user(&conn); Some(pool) } diff --git a/src/routes/blogs.rs b/src/routes/blogs.rs index df4da910..b8b00f33 100644 --- a/src/routes/blogs.rs +++ b/src/routes/blogs.rs @@ -442,6 +442,7 @@ mod tests { ) .unwrap(); Instance::cache_local(conn); + Instance::cache_local_instance_user(conn); instance }); let mut user = NewUser::default(); From af5b0b961b6889f4ea749cdbf18f5ddc51b10cc5 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 24 Nov 2021 22:11:56 +0900 Subject: [PATCH 07/31] Extract Instance::create_local_instance_user() from get_local_instance_user_uncached() --- plume-models/src/instance.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/plume-models/src/instance.rs b/plume-models/src/instance.rs index 5f4905d7..e791cb67 100644 --- a/plume-models/src/instance.rs +++ b/plume-models/src/instance.rs @@ -80,6 +80,20 @@ impl Instance { .map_err(Error::from) } + pub fn create_local_instance_user(conn: &Connection) -> Result { + let instance = Instance::get_local().expect("Failed to get local instance"); + let email = format!("{}@{}", LOCAL_INSTANCE_USERNAME, &instance.public_domain); + NewUser::new_local( + conn, + LOCAL_INSTANCE_USERNAME.into(), + instance.public_domain, + Role::Instance, + "Local instance", + email, + None, + ) + } + pub fn get_local_instance_user() -> Option<&'static User> { LOCAL_INSTANCE_USER.get() } @@ -89,19 +103,7 @@ impl Instance { .filter(users::role.eq(3)) .first(conn) .or_else(|err| match err { - NotFound => { - let instance = Instance::get_local().expect("Failed to get local instance"); - let email = format!("{}@{}", LOCAL_INSTANCE_USERNAME, &instance.public_domain); - NewUser::new_local( - conn, - LOCAL_INSTANCE_USERNAME.into(), - instance.public_domain, - Role::Instance, - "Local instance", - email, - None, - ) - } + NotFound => Self::create_local_instance_user(conn), _ => Err(Error::Db(err)), }) } From c5254100623adbcec40530b69cf56c2c3e3a54e7 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 24 Nov 2021 22:13:58 +0900 Subject: [PATCH 08/31] Create local instance user on creating instance --- plume-cli/src/instance.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/plume-cli/src/instance.rs b/plume-cli/src/instance.rs index 8fa48060..f383dcd9 100644 --- a/plume-cli/src/instance.rs +++ b/plume-cli/src/instance.rs @@ -68,4 +68,5 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) { }, ) .expect("Couldn't save instance"); + Instance::create_local_instance_user(conn).expect("Couldn't save local instance user"); } From f4d7dfb2611fb11512f592830895f0541dfdb26b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 24 Nov 2021 22:50:16 +0900 Subject: [PATCH 09/31] Sign GET request to other instances --- plume-common/src/activity_pub/inbox.rs | 29 ++++++++++++++++++------ plume-common/src/activity_pub/request.rs | 4 ++-- plume-models/src/blogs.rs | 4 ++++ plume-models/src/comments.rs | 5 ++++ plume-models/src/follows.rs | 8 +++++-- plume-models/src/likes.rs | 9 ++++++-- plume-models/src/posts.rs | 9 ++++++++ plume-models/src/reshares.rs | 9 ++++++-- plume-models/src/users.rs | 4 ++++ 9 files changed, 66 insertions(+), 15 deletions(-) diff --git a/plume-common/src/activity_pub/inbox.rs b/plume-common/src/activity_pub/inbox.rs index 0f88b5d9..c7625454 100644 --- a/plume-common/src/activity_pub/inbox.rs +++ b/plume-common/src/activity_pub/inbox.rs @@ -1,6 +1,11 @@ -use reqwest::header::{HeaderValue, ACCEPT}; +use reqwest::{ + header::{HeaderValue, HOST}, + Url, +}; use std::fmt::Debug; +use super::{request, sign::Signer}; + /// Represents an ActivityPub inbox. /// /// It routes an incoming Activity through the registered handlers. @@ -311,6 +316,14 @@ pub trait FromId: Sized { id: &str, proxy: Option, ) -> Result, Self::Error)> { + let mut headers = request::headers(); + let url = Url::parse(id).map_err(|_| (None, InboxError::DerefError.into()))?; + if !url.has_host() { + return Err((None, InboxError::DerefError.into())); + } + let host_header_value = HeaderValue::from_str(&url.host_str().expect("Unreachable")) + .map_err(|_| (None, InboxError::DerefError.into()))?; + headers.insert(HOST, host_header_value); if let Some(proxy) = proxy { reqwest::ClientBuilder::new().proxy(proxy) } else { @@ -320,13 +333,13 @@ pub trait FromId: Sized { .build() .map_err(|_| (None, InboxError::DerefError.into()))? .get(id) + .headers(headers.clone()) .header( - ACCEPT, - HeaderValue::from_str( - &super::ap_accept_header() - .into_iter() - .collect::>() - .join(", "), + "Signature", + request::signature( + Self::get_sender(), + &headers, + ("get", url.path(), url.query()), ) .map_err(|_| (None, InboxError::DerefError.into()))?, ) @@ -347,6 +360,8 @@ pub trait FromId: Sized { /// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database) fn from_db(ctx: &C, id: &str) -> Result; + + fn get_sender() -> &'static dyn Signer; } /// Should be implemented by anything representing an ActivityPub actor. diff --git a/plume-common/src/activity_pub/request.rs b/plume-common/src/activity_pub/request.rs index 73e6d6ee..8f20de13 100644 --- a/plume-common/src/activity_pub/request.rs +++ b/plume-common/src/activity_pub/request.rs @@ -118,8 +118,8 @@ type Path<'a> = &'a str; type Query<'a> = &'a str; type RequestTarget<'a> = (Method<'a>, Path<'a>, Option>); -pub fn signature( - signer: &S, +pub fn signature( + signer: &dyn Signer, headers: &HeaderMap, request_target: RequestTarget, ) -> Result { diff --git a/plume-models/src/blogs.rs b/plume-models/src/blogs.rs index da556225..9d0839ae 100644 --- a/plume-models/src/blogs.rs +++ b/plume-models/src/blogs.rs @@ -443,6 +443,10 @@ impl FromId for Blog { }, ) } + + fn get_sender() -> &'static dyn sign::Signer { + Instance::get_local_instance_user().expect("Failed to local instance user") + } } impl AsActor<&PlumeRocket> for Blog { diff --git a/plume-models/src/comments.rs b/plume-models/src/comments.rs index d92ab6c0..0809aab5 100644 --- a/plume-models/src/comments.rs +++ b/plume-models/src/comments.rs @@ -21,6 +21,7 @@ use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use plume_common::{ activity_pub::{ inbox::{AsActor, AsObject, FromId}, + sign::Signer, Id, IntoId, PUBLIC_VISIBILITY, }, utils, @@ -328,6 +329,10 @@ impl FromId for Comment { comm.notify(conn)?; Ok(comm) } + + fn get_sender() -> &'static dyn Signer { + Instance::get_local_instance_user().expect("Failed to local instance user") + } } impl AsObject for Comment { diff --git a/plume-models/src/follows.rs b/plume-models/src/follows.rs index c1669e36..9450b59b 100644 --- a/plume-models/src/follows.rs +++ b/plume-models/src/follows.rs @@ -1,6 +1,6 @@ use crate::{ - ap_url, db_conn::DbConn, notifications::*, schema::follows, users::User, Connection, Error, - Result, CONFIG, + ap_url, db_conn::DbConn, instance::Instance, notifications::*, schema::follows, users::User, + Connection, Error, Result, CONFIG, }; use activitypub::activity::{Accept, Follow as FollowAct, Undo}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; @@ -183,6 +183,10 @@ impl FromId for Follow { .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 AsObject for Follow { diff --git a/plume-models/src/likes.rs b/plume-models/src/likes.rs index b14e7c38..fa092af0 100644 --- a/plume-models/src/likes.rs +++ b/plume-models/src/likes.rs @@ -1,12 +1,13 @@ use crate::{ - db_conn::DbConn, notifications::*, posts::Post, schema::likes, timeline::*, users::User, - Connection, Error, Result, CONFIG, + db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*, + users::User, Connection, Error, Result, CONFIG, }; use activitypub::activity; use chrono::NaiveDateTime; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use plume_common::activity_pub::{ inbox::{AsActor, AsObject, FromId}, + sign::Signer, Id, IntoId, PUBLIC_VISIBILITY, }; @@ -137,6 +138,10 @@ impl FromId for Like { res.notify(conn)?; Ok(res) } + + fn get_sender() -> &'static dyn Signer { + Instance::get_local_instance_user().expect("Failed to local instance user") + } } impl AsObject for Like { diff --git a/plume-models/src/posts.rs b/plume-models/src/posts.rs index c53f20c1..9a385db9 100644 --- a/plume-models/src/posts.rs +++ b/plume-models/src/posts.rs @@ -15,6 +15,7 @@ use once_cell::sync::Lazy; use plume_common::{ activity_pub::{ inbox::{AsActor, AsObject, FromId}, + sign::Signer, Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY, }, utils::{iri_percent_encode_seg, md_to_html}, @@ -759,6 +760,10 @@ impl FromId for Post { Ok(post) } + + fn get_sender() -> &'static dyn Signer { + Instance::get_local_instance_user().expect("Failed to local instance user") + } } impl AsObject for Post { @@ -830,6 +835,10 @@ impl FromId for PostUpdate { tags: updated.object.object_props.tag, }) } + + fn get_sender() -> &'static dyn Signer { + Instance::get_local_instance_user().expect("Failed to local instance user") + } } impl AsObject for PostUpdate { diff --git a/plume-models/src/reshares.rs b/plume-models/src/reshares.rs index eec86a1e..58400196 100644 --- a/plume-models/src/reshares.rs +++ b/plume-models/src/reshares.rs @@ -1,12 +1,13 @@ use crate::{ - db_conn::DbConn, notifications::*, posts::Post, schema::reshares, timeline::*, users::User, - Connection, Error, Result, CONFIG, + db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::reshares, + timeline::*, users::User, Connection, Error, Result, CONFIG, }; use activitypub::activity::{Announce, Undo}; use chrono::NaiveDateTime; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use plume_common::activity_pub::{ inbox::{AsActor, AsObject, FromId}, + sign::Signer, Id, IntoId, PUBLIC_VISIBILITY, }; @@ -162,6 +163,10 @@ impl FromId for Reshare { res.notify(conn)?; Ok(res) } + + fn get_sender() -> &'static dyn Signer { + Instance::get_local_instance_user().expect("Failed to local instance user") + } } impl AsObject for Reshare { diff --git a/plume-models/src/users.rs b/plume-models/src/users.rs index 04e27d5f..b8b6b476 100644 --- a/plume-models/src/users.rs +++ b/plume-models/src/users.rs @@ -1039,6 +1039,10 @@ impl FromId for User { Ok(user) } + + fn get_sender() -> &'static dyn Signer { + Instance::get_local_instance_user().expect("Failed to local instance user") + } } impl AsActor<&DbConn> for User { From 79715ec7c7ec76a6b6989fc5e56f34cadd9ff1a3 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 26 Nov 2021 05:55:16 +0900 Subject: [PATCH 10/31] Add once_cell to dev-dependencies for plume-common --- plume-common/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plume-common/Cargo.toml b/plume-common/Cargo.toml index 06cf22f5..a67f748e 100644 --- a/plume-common/Cargo.toml +++ b/plume-common/Cargo.toml @@ -33,3 +33,6 @@ version = "0.4" default-features = false git = "https://git.joinplu.me/Plume/pulldown-cmark" branch = "bidi-plume" + +[dev-dependencies] +once_cell = "1.5.2" From 7d349c2de6b2df9287a22e6dc37e698efc0a17c1 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 26 Nov 2021 05:55:28 +0900 Subject: [PATCH 11/31] Install once_cell --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 122a9af2..0bd41319 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3038,6 +3038,7 @@ dependencies = [ "heck", "hex", "hyper 0.12.36", + "once_cell", "openssl", "pulldown-cmark", "regex-syntax 0.6.25", From 44f9d36df1689b5ceaef37c84b240a8ad75918c9 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 26 Nov 2021 05:56:13 +0900 Subject: [PATCH 12/31] Make request test follow API change --- plume-common/src/activity_pub/request.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plume-common/src/activity_pub/request.rs b/plume-common/src/activity_pub/request.rs index 8f20de13..e5c879a7 100644 --- a/plume-common/src/activity_pub/request.rs +++ b/plume-common/src/activity_pub/request.rs @@ -166,8 +166,8 @@ pub fn signature( #[cfg(test)] mod tests { - use super::{signature, Error}; - use crate::activity_pub::sign::{gen_keypair, Signer}; + use super::signature; + use crate::activity_pub::sign::{gen_keypair, Error, Result, Signer}; use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa}; use reqwest::header::HeaderMap; @@ -187,13 +187,11 @@ mod tests { } impl Signer for MySigner { - type Error = Error; - fn get_key_id(&self) -> String { "mysigner".into() } - fn sign(&self, to_sign: &str) -> Result, Self::Error> { + fn sign(&self, to_sign: &str) -> Result> { let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap()) .unwrap(); let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap(); @@ -201,7 +199,7 @@ mod tests { signer.sign_to_vec().map_err(|_| Error()) } - fn verify(&self, data: &str, signature: &[u8]) -> Result { + fn verify(&self, data: &str, signature: &[u8]) -> Result { let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap()) .unwrap(); let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap(); From 5a5c8bdac8c7cd5d17795889a4a3ce88cd6cf34c Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 26 Nov 2021 06:10:40 +0900 Subject: [PATCH 13/31] Make inbox test follow API change --- plume-common/src/activity_pub/inbox.rs | 158 +++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/plume-common/src/activity_pub/inbox.rs b/plume-common/src/activity_pub/inbox.rs index c7625454..f35d2060 100644 --- a/plume-common/src/activity_pub/inbox.rs +++ b/plume-common/src/activity_pub/inbox.rs @@ -15,7 +15,50 @@ use super::{request, sign::Signer}; /// ```rust /// # extern crate activitypub; /// # use activitypub::{actor::Person, activity::{Announce, Create}, object::Note}; +/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa}; +/// # use once_cell::sync::Lazy; /// # use plume_common::activity_pub::inbox::*; +/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer}; +/// # +/// # static MY_SIGNER: Lazy = Lazy::new(|| MySigner::new()); +/// # +/// # struct MySigner { +/// # public_key: String, +/// # private_key: String, +/// # } +/// # +/// # impl MySigner { +/// # fn new() -> Self { +/// # let (pub_key, priv_key) = gen_keypair(); +/// # Self { +/// # public_key: String::from_utf8(pub_key).unwrap(), +/// # private_key: String::from_utf8(priv_key).unwrap(), +/// # } +/// # } +/// # } +/// # +/// # impl Signer for MySigner { +/// # fn get_key_id(&self) -> String { +/// # "mysigner".into() +/// # } +/// # +/// # fn sign(&self, to_sign: &str) -> SignResult> { +/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap()) +/// # .unwrap(); +/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap(); +/// # signer.update(to_sign.as_bytes()).unwrap(); +/// # signer.sign_to_vec().map_err(|_| SignError()) +/// # } +/// # +/// # fn verify(&self, data: &str, signature: &[u8]) -> SignResult { +/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap()) +/// # .unwrap(); +/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap(); +/// # verifier.update(data.as_bytes()).unwrap(); +/// # verifier.verify(&signature).map_err(|_| SignError()) +/// # } +/// # } +/// # /// # struct User; /// # impl FromId<()> for User { /// # type Error = (); @@ -28,6 +71,10 @@ use super::{request, sign::Signer}; /// # fn from_activity(_: &(), obj: Person) -> Result { /// # Ok(User) /// # } +/// # +/// # fn get_sender() -> &'static dyn Signer { +/// # &*MY_SIGNER +/// # } /// # } /// # impl AsActor<&()> for User { /// # fn get_inbox_url(&self) -> String { @@ -47,6 +94,10 @@ use super::{request, sign::Signer}; /// # fn from_activity(_: &(), obj: Note) -> Result { /// # Ok(Message) /// # } +/// # +/// # fn get_sender() -> &'static dyn Signer { +/// # &*MY_SIGNER +/// # } /// # } /// # impl AsObject for Message { /// # type Error = (); @@ -400,6 +451,49 @@ pub trait AsActor { /// # extern crate activitypub; /// # use activitypub::{activity::Create, actor::Person, object::Note}; /// # use plume_common::activity_pub::inbox::{AsActor, AsObject, FromId}; +/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer}; +/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa}; +/// # use once_cell::sync::Lazy; +/// # +/// # static MY_SIGNER: Lazy = Lazy::new(|| MySigner::new()); +/// # +/// # struct MySigner { +/// # public_key: String, +/// # private_key: String, +/// # } +/// # +/// # impl MySigner { +/// # fn new() -> Self { +/// # let (pub_key, priv_key) = gen_keypair(); +/// # Self { +/// # public_key: String::from_utf8(pub_key).unwrap(), +/// # private_key: String::from_utf8(priv_key).unwrap(), +/// # } +/// # } +/// # } +/// # +/// # impl Signer for MySigner { +/// # fn get_key_id(&self) -> String { +/// # "mysigner".into() +/// # } +/// # +/// # fn sign(&self, to_sign: &str) -> SignResult> { +/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap()) +/// # .unwrap(); +/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap(); +/// # signer.update(to_sign.as_bytes()).unwrap(); +/// # signer.sign_to_vec().map_err(|_| SignError()) +/// # } +/// # +/// # fn verify(&self, data: &str, signature: &[u8]) -> SignResult { +/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap()) +/// # .unwrap(); +/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap(); +/// # verifier.update(data.as_bytes()).unwrap(); +/// # verifier.verify(&signature).map_err(|_| SignError()) +/// # } +/// # } +/// # /// # struct Account; /// # impl FromId<()> for Account { /// # type Error = (); @@ -412,6 +506,10 @@ pub trait AsActor { /// # fn from_activity(_: &(), obj: Person) -> Result { /// # Ok(Account) /// # } +/// # +/// # fn get_sender() -> &'static dyn Signer { +/// # &*MY_SIGNER +/// # } /// # } /// # impl AsActor<()> for Account { /// # fn get_inbox_url(&self) -> String { @@ -435,6 +533,10 @@ pub trait AsActor { /// fn from_activity(_: &(), obj: Note) -> Result { /// Ok(Message { text: obj.object_props.content_string().map_err(|_| ())? }) /// } +/// +/// fn get_sender() -> &'static dyn Signer { +/// &*MY_SIGNER +/// } /// } /// /// impl AsObject for Message { @@ -474,7 +576,51 @@ where #[cfg(test)] mod tests { use super::*; + use crate::activity_pub::sign::{ + gen_keypair, Error as SignError, Result as SignResult, Signer, + }; use activitypub::{activity::*, actor::Person, object::Note}; + use once_cell::sync::Lazy; + use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa}; + + static MY_SIGNER: Lazy = Lazy::new(|| MySigner::new()); + + struct MySigner { + public_key: String, + private_key: String, + } + + impl MySigner { + fn new() -> Self { + let (pub_key, priv_key) = gen_keypair(); + Self { + public_key: String::from_utf8(pub_key).unwrap(), + private_key: String::from_utf8(priv_key).unwrap(), + } + } + } + + impl Signer for MySigner { + fn get_key_id(&self) -> String { + "mysigner".into() + } + + fn sign(&self, to_sign: &str) -> SignResult> { + let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap()) + .unwrap(); + let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap(); + signer.update(to_sign.as_bytes()).unwrap(); + signer.sign_to_vec().map_err(|_| SignError()) + } + + fn verify(&self, data: &str, signature: &[u8]) -> SignResult { + let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap()) + .unwrap(); + let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap(); + verifier.update(data.as_bytes()).unwrap(); + verifier.verify(&signature).map_err(|_| SignError()) + } + } struct MyActor; impl FromId<()> for MyActor { @@ -488,6 +634,10 @@ mod tests { fn from_activity(_: &(), _obj: Person) -> Result { Ok(MyActor) } + + fn get_sender() -> &'static dyn Signer { + &*MY_SIGNER + } } impl AsActor<&()> for MyActor { @@ -512,6 +662,10 @@ mod tests { fn from_activity(_: &(), _obj: Note) -> Result { Ok(MyObject) } + + fn get_sender() -> &'static dyn Signer { + &*MY_SIGNER + } } impl AsObject for MyObject { type Error = (); @@ -616,6 +770,10 @@ mod tests { fn from_activity(_: &(), _obj: Person) -> Result { Err(()) } + + fn get_sender() -> &'static dyn Signer { + &*MY_SIGNER + } } impl AsActor<&()> for FailingActor { fn get_inbox_url(&self) -> String { From 1e67b3c13c10b9b90088eac596ec5002d236c2ea Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 26 Nov 2021 09:27:57 +0900 Subject: [PATCH 14/31] Create local instance user on caching if it doesn't exist --- plume-models/src/instance.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plume-models/src/instance.rs b/plume-models/src/instance.rs index e791cb67..5f9ef5a7 100644 --- a/plume-models/src/instance.rs +++ b/plume-models/src/instance.rs @@ -109,8 +109,9 @@ impl Instance { } pub fn cache_local_instance_user(conn: &Connection) { - let user = Self::get_local_instance_user_uncached(conn) - .expect("Failed to get local instance user"); + let user = Self::get_local_instance_user_uncached(conn).unwrap_or_else(|_| { + Self::create_local_instance_user(conn).expect("Failed to create local instance user") + }); LOCAL_INSTANCE_USER .set(user) .expect("Failed to set local instance user"); From b9ea06a01a8cef0f705c6f9fb4b32eb106c00ddd Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 26 Nov 2021 09:51:25 +0900 Subject: [PATCH 15/31] Don't stop even when caching local instance user --- plume-models/src/instance.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plume-models/src/instance.rs b/plume-models/src/instance.rs index 5f9ef5a7..e7247d6d 100644 --- a/plume-models/src/instance.rs +++ b/plume-models/src/instance.rs @@ -11,6 +11,7 @@ use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQuer use once_cell::sync::OnceCell; use plume_common::utils::md_to_html; use std::sync::RwLock; +use tracing::error; #[derive(Clone, Identifiable, Queryable)] pub struct Instance { @@ -81,7 +82,7 @@ impl Instance { } pub fn create_local_instance_user(conn: &Connection) -> Result { - let instance = Instance::get_local().expect("Failed to get local instance"); + let instance = Instance::get_local()?; let email = format!("{}@{}", LOCAL_INSTANCE_USERNAME, &instance.public_domain); NewUser::new_local( conn, @@ -109,9 +110,13 @@ impl Instance { } pub fn cache_local_instance_user(conn: &Connection) { - let user = Self::get_local_instance_user_uncached(conn).unwrap_or_else(|_| { - Self::create_local_instance_user(conn).expect("Failed to create local instance user") - }); + let res = Self::get_local_instance_user_uncached(conn) + .or_else(|_| Self::create_local_instance_user(conn)); + if res.is_err() { + error!("Failed to cache local instance user: {:?}", res); + return; + } + let user = res.expect("Unreachable"); LOCAL_INSTANCE_USER .set(user) .expect("Failed to set local instance user"); From 12a8d00f8e5c1ef1d5c160c2af0f9ec3f6f80c1f Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 26 Nov 2021 09:51:51 +0900 Subject: [PATCH 16/31] Cache local instance user for test --- plume-models/src/instance.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/plume-models/src/instance.rs b/plume-models/src/instance.rs index e7247d6d..caf474b2 100644 --- a/plume-models/src/instance.rs +++ b/plume-models/src/instance.rs @@ -350,6 +350,7 @@ pub(crate) mod tests { }) .collect(); Instance::cache_local(conn); + Instance::cache_local_instance_user(conn); res } From d3c035aa394a8fcfd761f4520c5ebc9aaa12faaa Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 26 Nov 2021 09:52:18 +0900 Subject: [PATCH 17/31] Remove needless code --- src/routes/blogs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/blogs.rs b/src/routes/blogs.rs index b8b00f33..df4da910 100644 --- a/src/routes/blogs.rs +++ b/src/routes/blogs.rs @@ -442,7 +442,6 @@ mod tests { ) .unwrap(); Instance::cache_local(conn); - Instance::cache_local_instance_user(conn); instance }); let mut user = NewUser::default(); From 388acd67382f6f6a60dd570a3ab40db7100a5dab Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 29 Nov 2021 09:50:43 +0900 Subject: [PATCH 18/31] Remove needless reference sign --- plume-common/src/activity_pub/inbox.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plume-common/src/activity_pub/inbox.rs b/plume-common/src/activity_pub/inbox.rs index f35d2060..9483a039 100644 --- a/plume-common/src/activity_pub/inbox.rs +++ b/plume-common/src/activity_pub/inbox.rs @@ -372,7 +372,7 @@ pub trait FromId: Sized { if !url.has_host() { return Err((None, InboxError::DerefError.into())); } - let host_header_value = HeaderValue::from_str(&url.host_str().expect("Unreachable")) + let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable")) .map_err(|_| (None, InboxError::DerefError.into()))?; headers.insert(HOST, host_header_value); if let Some(proxy) = proxy { From a7d8d49faffb2b8358c413c26b6fa6a4d3b2325c Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 29 Nov 2021 09:51:06 +0900 Subject: [PATCH 19/31] Define request::get() function --- plume-common/src/activity_pub/request.rs | 32 +++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/plume-common/src/activity_pub/request.rs b/plume-common/src/activity_pub/request.rs index e5c879a7..c2a523e4 100644 --- a/plume-common/src/activity_pub/request.rs +++ b/plume-common/src/activity_pub/request.rs @@ -1,6 +1,9 @@ use chrono::{offset::Utc, DateTime}; use openssl::hash::{Hasher, MessageDigest}; -use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT}; +use reqwest::{ + header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, HOST, USER_AGENT}, + ClientBuilder, Proxy, Response, Url, +}; use std::ops::Deref; use std::time::SystemTime; use tracing::warn; @@ -164,6 +167,33 @@ pub fn signature( )).map_err(|_| Error()) } +pub fn get(url_str: &str, sender: &dyn Signer, proxy: Option) -> Result { + let mut headers = headers(); + let url = Url::parse(url_str).map_err(|_| Error())?; // TODO: Define and use From trait + if !url.has_host() { + return Err(Error()); + } + let host_header_value = + HeaderValue::from_str(url.host_str().expect("Unreachable")).map_err(|_| Error())?; + headers.insert(HOST, host_header_value); + if let Some(proxy) = proxy { + ClientBuilder::new().proxy(proxy) + } else { + ClientBuilder::new() + } + .connect_timeout(Some(std::time::Duration::from_secs(5))) + .build() + .map_err(|_| Error())? + .get(url_str) + .headers(headers.clone()) + .header( + "Signature", + signature(sender, &headers, ("get", url.path(), url.query())).map_err(|_| Error())?, + ) + .send() + .map_err(|_| Error()) +} + #[cfg(test)] mod tests { use super::signature; From 48fab8ad2c27ae377519d4861c20683130f30a6b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 29 Nov 2021 10:01:40 +0900 Subject: [PATCH 20/31] Impl From for request::Error --- plume-common/src/activity_pub/request.rs | 34 ++++++++++++++++++------ 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/plume-common/src/activity_pub/request.rs b/plume-common/src/activity_pub/request.rs index c2a523e4..4258bd7b 100644 --- a/plume-common/src/activity_pub/request.rs +++ b/plume-common/src/activity_pub/request.rs @@ -1,8 +1,10 @@ use chrono::{offset::Utc, DateTime}; use openssl::hash::{Hasher, MessageDigest}; use reqwest::{ - header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, HOST, USER_AGENT}, - ClientBuilder, Proxy, Response, Url, + header::{ + HeaderMap, HeaderValue, InvalidHeaderValue, ACCEPT, CONTENT_TYPE, DATE, HOST, USER_AGENT, + }, + ClientBuilder, Proxy, Response, Url, UrlError, }; use std::ops::Deref; use std::time::SystemTime; @@ -16,6 +18,24 @@ const PLUME_USER_AGENT: &str = concat!("Plume/", env!("CARGO_PKG_VERSION")); #[derive(Debug)] pub struct Error(); +impl From for Error { + fn from(_err: UrlError) -> Self { + Error() + } +} + +impl From for Error { + fn from(_err: InvalidHeaderValue) -> Self { + Error() + } +} + +impl From for Error { + fn from(_err: reqwest::Error) -> Self { + Error() + } +} + pub struct Digest(String); impl Digest { @@ -169,12 +189,11 @@ pub fn signature( pub fn get(url_str: &str, sender: &dyn Signer, proxy: Option) -> Result { let mut headers = headers(); - let url = Url::parse(url_str).map_err(|_| Error())?; // TODO: Define and use From trait + let url = Url::parse(url_str)?; if !url.has_host() { return Err(Error()); } - let host_header_value = - HeaderValue::from_str(url.host_str().expect("Unreachable")).map_err(|_| Error())?; + let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable"))?; headers.insert(HOST, host_header_value); if let Some(proxy) = proxy { ClientBuilder::new().proxy(proxy) @@ -182,13 +201,12 @@ pub fn get(url_str: &str, sender: &dyn Signer, proxy: Option) -> Result Date: Sun, 5 Dec 2021 19:27:57 +0900 Subject: [PATCH 21/31] Use request::get() instead of ClientBuilder --- plume-common/src/activity_pub/inbox.rs | 52 ++++------------- plume-models/src/lib.rs | 8 ++- plume-models/src/medias.rs | 14 ++--- plume-models/src/users.rs | 79 ++++++-------------------- 4 files changed, 41 insertions(+), 112 deletions(-) diff --git a/plume-common/src/activity_pub/inbox.rs b/plume-common/src/activity_pub/inbox.rs index 9483a039..48ba6c5f 100644 --- a/plume-common/src/activity_pub/inbox.rs +++ b/plume-common/src/activity_pub/inbox.rs @@ -1,7 +1,4 @@ -use reqwest::{ - header::{HeaderValue, HOST}, - Url, -}; +use reqwest; use std::fmt::Debug; use super::{request, sign::Signer}; @@ -367,43 +364,16 @@ pub trait FromId: Sized { id: &str, proxy: Option, ) -> Result, Self::Error)> { - let mut headers = request::headers(); - let url = Url::parse(id).map_err(|_| (None, InboxError::DerefError.into()))?; - if !url.has_host() { - return Err((None, InboxError::DerefError.into())); - } - let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable")) - .map_err(|_| (None, InboxError::DerefError.into()))?; - headers.insert(HOST, host_header_value); - if let Some(proxy) = proxy { - reqwest::ClientBuilder::new().proxy(proxy) - } else { - reqwest::ClientBuilder::new() - } - .connect_timeout(Some(std::time::Duration::from_secs(5))) - .build() - .map_err(|_| (None, InboxError::DerefError.into()))? - .get(id) - .headers(headers.clone()) - .header( - "Signature", - request::signature( - Self::get_sender(), - &headers, - ("get", url.path(), url.query()), - ) - .map_err(|_| (None, InboxError::DerefError.into()))?, - ) - .send() - .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())) + 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 diff --git a/plume-models/src/lib.rs b/plume-models/src/lib.rs index 33a5ec2b..16d3a159 100755 --- a/plume-models/src/lib.rs +++ b/plume-models/src/lib.rs @@ -17,7 +17,7 @@ extern crate serde_json; extern crate tantivy; use once_cell::sync::Lazy; -use plume_common::activity_pub::{inbox::InboxError, sign}; +use plume_common::activity_pub::{inbox::InboxError, request, sign}; use posts::PostEvent; use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder}; use users::UserEvent; @@ -157,6 +157,12 @@ impl From> for Error { } } +impl From for Error { + fn from(_err: request::Error) -> Error { + Error::Request + } +} + pub type Result = std::result::Result; /// Adds a function to a model, that returns the first diff --git a/plume-models/src/medias.rs b/plume-models/src/medias.rs index 5be9bc70..68cb52c4 100644 --- a/plume-models/src/medias.rs +++ b/plume-models/src/medias.rs @@ -7,7 +7,7 @@ use askama_escape::escape; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use guid_create::GUID; use plume_common::{ - activity_pub::{inbox::FromId, Id}, + activity_pub::{inbox::FromId, request, Id}, utils::MediaProcessor, }; use std::{ @@ -220,13 +220,11 @@ impl Media { let mut dest = fs::File::create(path.clone())?; // TODO: conditional GET - if let Some(proxy) = CONFIG.proxy() { - reqwest::ClientBuilder::new().proxy(proxy.clone()).build()? - } else { - reqwest::Client::new() - } - .get(remote_url.as_str()) - .send()? + request::get( + remote_url.as_str(), + User::get_sender(), + CONFIG.proxy().cloned(), + )? .copy_to(&mut dest)?; Media::find_by_file_path(conn, path.to_str().ok_or(Error::InvalidValue)?) diff --git a/plume-models/src/users.rs b/plume-models/src/users.rs index b8b6b476..14ea4e64 100644 --- a/plume-models/src/users.rs +++ b/plume-models/src/users.rs @@ -22,17 +22,13 @@ use openssl::{ }; use plume_common::{ activity_pub::{ - ap_accept_header, inbox::{AsActor, AsObject, FromId}, + request::get, sign::{gen_keypair, Error as SignError, Result as SignResult, Signer}, ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY, }, utils, }; -use reqwest::{ - header::{HeaderValue, ACCEPT}, - ClientBuilder, -}; use riker::actors::{Publish, Tell}; use rocket::{ outcome::IntoOutcome, @@ -231,20 +227,7 @@ impl User { } fn fetch(url: &str) -> Result { - let mut res = ClientBuilder::new() - .connect_timeout(Some(std::time::Duration::from_secs(5))) - .build()? - .get(url) - .header( - ACCEPT, - HeaderValue::from_str( - &ap_accept_header() - .into_iter() - .collect::>() - .join(", "), - )?, - ) - .send()?; + let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?; let text = &res.text()?; // without this workaround, publicKey is not correctly deserialized let ap_sign = serde_json::from_str::(text)?; @@ -293,7 +276,10 @@ impl User { )) .execute(conn) .map(|_| ()) - .map_err(Error::from) + .map_err(|err| { + tracing::error!("{:?}", err); + Error::from(err) + }) }) } @@ -471,20 +457,7 @@ impl User { Ok(ActivityStream::new(coll)) } fn fetch_outbox_page(&self, url: &str) -> Result<(Vec, Option)> { - let mut res = ClientBuilder::new() - .connect_timeout(Some(std::time::Duration::from_secs(5))) - .build()? - .get(url) - .header( - ACCEPT, - HeaderValue::from_str( - &ap_accept_header() - .into_iter() - .collect::>() - .join(", "), - )?, - ) - .send()?; + let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?; let text = &res.text()?; let json: serde_json::Value = serde_json::from_str(text)?; let items = json["items"] @@ -498,20 +471,11 @@ impl User { Ok((items, next)) } pub fn fetch_outbox(&self) -> Result> { - let mut res = ClientBuilder::new() - .connect_timeout(Some(std::time::Duration::from_secs(5))) - .build()? - .get(&self.outbox_url[..]) - .header( - ACCEPT, - HeaderValue::from_str( - &ap_accept_header() - .into_iter() - .collect::>() - .join(", "), - )?, - ) - .send()?; + let mut res = get( + &self.outbox_url[..], + Self::get_sender(), + CONFIG.proxy().cloned(), + )?; let text = &res.text()?; let json: serde_json::Value = serde_json::from_str(text)?; if let Some(first) = json.get("first") { @@ -543,20 +507,11 @@ impl User { } pub fn fetch_followers_ids(&self) -> Result> { - let mut res = ClientBuilder::new() - .connect_timeout(Some(std::time::Duration::from_secs(5))) - .build()? - .get(&self.followers_endpoint[..]) - .header( - ACCEPT, - HeaderValue::from_str( - &ap_accept_header() - .into_iter() - .collect::>() - .join(", "), - )?, - ) - .send()?; + let mut res = get( + &self.followers_endpoint[..], + Self::get_sender(), + CONFIG.proxy().cloned(), + )?; let text = &res.text()?; let json: serde_json::Value = serde_json::from_str(text)?; Ok(json["items"] From d98132db80b10dec925abab29104e11288f16cc1 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 5 Dec 2021 19:32:06 +0900 Subject: [PATCH 22/31] Don't log unnecessary error --- plume-models/src/users.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plume-models/src/users.rs b/plume-models/src/users.rs index 14ea4e64..de2cd55d 100644 --- a/plume-models/src/users.rs +++ b/plume-models/src/users.rs @@ -276,10 +276,7 @@ impl User { )) .execute(conn) .map(|_| ()) - .map_err(|err| { - tracing::error!("{:?}", err); - Error::from(err) - }) + .map_err(|err| Error::from(err)) }) } From 5651e11ba15f5904779f65647961c67cc15b5a0a Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 5 Dec 2021 20:22:10 +0900 Subject: [PATCH 23/31] Add FIXME comment --- plume-models/src/users.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plume-models/src/users.rs b/plume-models/src/users.rs index de2cd55d..1b40015f 100644 --- a/plume-models/src/users.rs +++ b/plume-models/src/users.rs @@ -246,7 +246,7 @@ impl User { conn, json.object .object_props - .icon_image()? + .icon_image()? // FIXME: Fails when icon is not set .object_props .url_string()?, self, From de4380fd34846b2be3891c1bfc29f493e0501a46 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 5 Dec 2021 20:22:32 +0900 Subject: [PATCH 24/31] Cache local when creating local instance --- plume-cli/src/instance.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/plume-cli/src/instance.rs b/plume-cli/src/instance.rs index f383dcd9..35793639 100644 --- a/plume-cli/src/instance.rs +++ b/plume-cli/src/instance.rs @@ -68,5 +68,6 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) { }, ) .expect("Couldn't save instance"); + Instance::cache_local(conn); Instance::create_local_instance_user(conn).expect("Couldn't save local instance user"); } From e01539ef1628155241ec5325ba0c20e1684faec4 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 5 Dec 2021 20:24:11 +0900 Subject: [PATCH 25/31] Follow Clippy --- plume-cli/src/main.rs | 2 +- plume-models/src/users.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plume-cli/src/main.rs b/plume-cli/src/main.rs index 35248a32..3615c10d 100644 --- a/plume-cli/src/main.rs +++ b/plume-cli/src/main.rs @@ -25,7 +25,7 @@ fn main() { e => e.map(|_| ()).unwrap(), } let conn = Conn::establish(CONFIG.database_url.as_str()); - let _ = conn.as_ref().map(|conn| Instance::cache_local(conn)); + let _ = conn.as_ref().map(Instance::cache_local); match matches.subcommand() { ("instance", Some(args)) => { diff --git a/plume-models/src/users.rs b/plume-models/src/users.rs index 1b40015f..8c42701b 100644 --- a/plume-models/src/users.rs +++ b/plume-models/src/users.rs @@ -276,7 +276,7 @@ impl User { )) .execute(conn) .map(|_| ()) - .map_err(|err| Error::from(err)) + .map_err(Error::from) }) } From c67b702425906355b0a40d0a37b445c746e0ad0d Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 5 Dec 2021 20:57:10 +0900 Subject: [PATCH 26/31] Cache LOCAL_INSTANCE_USER once --- plume-models/src/instance.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/plume-models/src/instance.rs b/plume-models/src/instance.rs index caf474b2..86cb3d46 100644 --- a/plume-models/src/instance.rs +++ b/plume-models/src/instance.rs @@ -11,7 +11,6 @@ use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQuer use once_cell::sync::OnceCell; use plume_common::utils::md_to_html; use std::sync::RwLock; -use tracing::error; #[derive(Clone, Identifiable, Queryable)] pub struct Instance { @@ -110,16 +109,11 @@ impl Instance { } pub fn cache_local_instance_user(conn: &Connection) { - let res = Self::get_local_instance_user_uncached(conn) - .or_else(|_| Self::create_local_instance_user(conn)); - if res.is_err() { - error!("Failed to cache local instance user: {:?}", res); - return; - } - let user = res.expect("Unreachable"); - LOCAL_INSTANCE_USER - .set(user) - .expect("Failed to set local instance user"); + let _ = LOCAL_INSTANCE_USER.get_or_init(|| { + Self::get_local_instance_user_uncached(conn) + .or_else(|_| Self::create_local_instance_user(conn)) + .expect("Failed to cache local instance user") + }); } pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result> { From 13be46445c819461aa12368c49147456872fe65e Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 5 Dec 2021 21:06:42 +0900 Subject: [PATCH 27/31] Add changelog about signing GET requests --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb180c95..f6a8bfcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Upgrade Tantivy to 0.13.3 and lindera-tantivy to 0.7.1 (#878) - Run searcher on actor system (#870) - Use article title as its slug instead of capitalizing and inserting hyphens (#920) +- Sign GET requests to other instances (#957) ### Fixed From 346a67fe1c8b635c1137d37c1fbf19dbe4c8f767 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 5 Dec 2021 21:34:36 +0900 Subject: [PATCH 28/31] Create local instance user on start --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 49eeb812..b488fc57 100755 --- a/src/main.rs +++ b/src/main.rs @@ -59,6 +59,7 @@ fn init_pool() -> Option { let pool = builder.build(manager).ok()?; let conn = pool.get().unwrap(); Instance::cache_local(&conn); + Instance::create_local_instance_user(&conn); Instance::cache_local_instance_user(&conn); Some(pool) } From 05a98175bfffb4001a06670b7e5308592fdad81c Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 5 Dec 2021 21:56:00 +0900 Subject: [PATCH 29/31] Assign unsed return value to underscore variable --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index b488fc57..58cb3abe 100755 --- a/src/main.rs +++ b/src/main.rs @@ -59,7 +59,7 @@ fn init_pool() -> Option { let pool = builder.build(manager).ok()?; let conn = pool.get().unwrap(); Instance::cache_local(&conn); - Instance::create_local_instance_user(&conn); + let _ = Instance::create_local_instance_user(&conn); Instance::cache_local_instance_user(&conn); Some(pool) } From b7cc2369a79c73f80b6ba1c23fd2ad78dfc403d8 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 6 Dec 2021 00:33:20 +0900 Subject: [PATCH 30/31] Allow clippy::needless_borrow on CI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cd7f2d06..b0f7e651 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -63,7 +63,7 @@ commands: type: boolean default: false steps: - - run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<> --release -p <> -- -D warnings + - run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<> --release -p <> -- -D warnings -A clippy::needless_borrow run_with_coverage: description: run command with environment for coverage From 43656d8e4645362e82e96a3dec78f36d1e59082b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 6 Dec 2021 00:33:48 +0900 Subject: [PATCH 31/31] Create instance for test --- src/routes/blogs.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/routes/blogs.rs b/src/routes/blogs.rs index df4da910..07a8e8f1 100644 --- a/src/routes/blogs.rs +++ b/src/routes/blogs.rs @@ -390,6 +390,7 @@ mod tests { posts::{NewPost, Post}, safe_string::SafeString, users::{NewUser, User, AUTH_COOKIE}, + Connection as Conn, CONFIG, }; use rocket::{ http::{Cookie, Cookies, SameSite}, @@ -398,6 +399,22 @@ mod tests { #[test] fn edit_link_within_post_card() { + let conn = Conn::establish(CONFIG.database_url.as_str()).unwrap(); + Instance::insert( + &conn, + NewInstance { + public_domain: "example.org".to_string(), + name: "Plume".to_string(), + local: true, + long_description: SafeString::new(""), + short_description: SafeString::new(""), + default_license: "CC-BY-SA".to_string(), + open_registrations: true, + short_description_html: String::new(), + long_description_html: String::new(), + }, + ) + .unwrap(); let rocket = init_rocket(); let client = Client::new(rocket).expect("valid rocket instance"); let dbpool = client.rocket().state::().unwrap();