Merge remote-tracking branch 'origin/main' into v0.7.0
This commit is contained in:
commit
31a1591043
@ -63,7 +63,7 @@ commands:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
steps:
|
steps:
|
||||||
- run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<</parameters.no_feature>> --release -p <<parameters.package>> -- -D warnings
|
- run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<</parameters.no_feature>> --release -p <<parameters.package>> -- -D warnings -A clippy::needless_borrow
|
||||||
|
|
||||||
run_with_coverage:
|
run_with_coverage:
|
||||||
description: run command with environment for coverage
|
description: run command with environment for coverage
|
||||||
|
@ -20,9 +20,9 @@
|
|||||||
- Update Rust version to nightly-2021-11-27 (#961)
|
- Update Rust version to nightly-2021-11-27 (#961)
|
||||||
- Upgrade Tantivy to 0.13.3 and lindera-tantivy to 0.7.1 (#878)
|
- Upgrade Tantivy to 0.13.3 and lindera-tantivy to 0.7.1 (#878)
|
||||||
- Run searcher on actor system (#870)
|
- Run searcher on actor system (#870)
|
||||||
- Use article title as its slug instead of capitalizing and inserting hyphens (#920)
|
|
||||||
- Extract a function to calculate posts' ap_url and share it with some places (#918)
|
- Extract a function to calculate posts' ap_url and share it with some places (#918)
|
||||||
- Keep title in URI (#920)
|
- Use article title as its slug instead of capitalizing and inserting hyphens (#920)
|
||||||
|
- Sign GET requests to other instances (#957)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3038,6 +3038,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"hex",
|
"hex",
|
||||||
"hyper 0.12.36",
|
"hyper 0.12.36",
|
||||||
|
"once_cell",
|
||||||
"openssl",
|
"openssl",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"regex-syntax 0.6.25",
|
"regex-syntax 0.6.25",
|
||||||
|
@ -68,4 +68,6 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("Couldn't save instance");
|
.expect("Couldn't save instance");
|
||||||
|
Instance::cache_local(conn);
|
||||||
|
Instance::create_local_instance_user(conn).expect("Couldn't save local instance user");
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ fn main() {
|
|||||||
e => e.map(|_| ()).unwrap(),
|
e => e.map(|_| ()).unwrap(),
|
||||||
}
|
}
|
||||||
let conn = Conn::establish(CONFIG.database_url.as_str());
|
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() {
|
match matches.subcommand() {
|
||||||
("instance", Some(args)) => {
|
("instance", Some(args)) => {
|
||||||
|
@ -33,3 +33,6 @@ version = "0.4"
|
|||||||
default-features = false
|
default-features = false
|
||||||
git = "https://git.joinplu.me/Plume/pulldown-cmark"
|
git = "https://git.joinplu.me/Plume/pulldown-cmark"
|
||||||
branch = "bidi-plume"
|
branch = "bidi-plume"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
once_cell = "1.5.2"
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use reqwest::header::{HeaderValue, ACCEPT};
|
use reqwest;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use super::{request, sign::Signer};
|
||||||
|
|
||||||
/// Represents an ActivityPub inbox.
|
/// Represents an ActivityPub inbox.
|
||||||
///
|
///
|
||||||
/// It routes an incoming Activity through the registered handlers.
|
/// It routes an incoming Activity through the registered handlers.
|
||||||
@ -10,7 +12,50 @@ use std::fmt::Debug;
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate activitypub;
|
/// # extern crate activitypub;
|
||||||
/// # use activitypub::{actor::Person, activity::{Announce, Create}, object::Note};
|
/// # 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::inbox::*;
|
||||||
|
/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer};
|
||||||
|
/// #
|
||||||
|
/// # static MY_SIGNER: Lazy<MySigner> = 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<Vec<u8>> {
|
||||||
|
/// # 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<bool> {
|
||||||
|
/// # 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;
|
/// # struct User;
|
||||||
/// # impl FromId<()> for User {
|
/// # impl FromId<()> for User {
|
||||||
/// # type Error = ();
|
/// # type Error = ();
|
||||||
@ -23,6 +68,10 @@ use std::fmt::Debug;
|
|||||||
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
||||||
/// # Ok(User)
|
/// # Ok(User)
|
||||||
/// # }
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn get_sender() -> &'static dyn Signer {
|
||||||
|
/// # &*MY_SIGNER
|
||||||
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// # impl AsActor<&()> for User {
|
/// # impl AsActor<&()> for User {
|
||||||
/// # fn get_inbox_url(&self) -> String {
|
/// # fn get_inbox_url(&self) -> String {
|
||||||
@ -42,6 +91,10 @@ use std::fmt::Debug;
|
|||||||
/// # fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
/// # fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
||||||
/// # Ok(Message)
|
/// # Ok(Message)
|
||||||
/// # }
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn get_sender() -> &'static dyn Signer {
|
||||||
|
/// # &*MY_SIGNER
|
||||||
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// # impl AsObject<User, Create, &()> for Message {
|
/// # impl AsObject<User, Create, &()> for Message {
|
||||||
/// # type Error = ();
|
/// # type Error = ();
|
||||||
@ -311,35 +364,16 @@ pub trait FromId<C>: Sized {
|
|||||||
id: &str,
|
id: &str,
|
||||||
proxy: Option<reqwest::Proxy>,
|
proxy: Option<reqwest::Proxy>,
|
||||||
) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
|
) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
|
||||||
if let Some(proxy) = proxy {
|
request::get(id, Self::get_sender(), proxy)
|
||||||
reqwest::ClientBuilder::new().proxy(proxy)
|
.map_err(|_| (None, InboxError::DerefError))
|
||||||
} else {
|
.and_then(|mut r| {
|
||||||
reqwest::ClientBuilder::new()
|
let json: serde_json::Value = r
|
||||||
}
|
.json()
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
|
||||||
.build()
|
serde_json::from_value(json.clone())
|
||||||
.map_err(|_| (None, InboxError::DerefError.into()))?
|
.map_err(|_| (Some(json), InboxError::InvalidObject(None)))
|
||||||
.get(id)
|
})
|
||||||
.header(
|
.map_err(|(json, e)| (json, e.into()))
|
||||||
ACCEPT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&super::ap_accept_header()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)
|
|
||||||
.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()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a `Self` from its ActivityPub representation
|
/// Builds a `Self` from its ActivityPub representation
|
||||||
@ -347,6 +381,8 @@ pub trait FromId<C>: Sized {
|
|||||||
|
|
||||||
/// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database)
|
/// 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 from_db(ctx: &C, id: &str) -> Result<Self, Self::Error>;
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should be implemented by anything representing an ActivityPub actor.
|
/// Should be implemented by anything representing an ActivityPub actor.
|
||||||
@ -385,6 +421,49 @@ pub trait AsActor<C> {
|
|||||||
/// # extern crate activitypub;
|
/// # extern crate activitypub;
|
||||||
/// # use activitypub::{activity::Create, actor::Person, object::Note};
|
/// # use activitypub::{activity::Create, actor::Person, object::Note};
|
||||||
/// # use plume_common::activity_pub::inbox::{AsActor, AsObject, FromId};
|
/// # 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<MySigner> = 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<Vec<u8>> {
|
||||||
|
/// # 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<bool> {
|
||||||
|
/// # 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;
|
/// # struct Account;
|
||||||
/// # impl FromId<()> for Account {
|
/// # impl FromId<()> for Account {
|
||||||
/// # type Error = ();
|
/// # type Error = ();
|
||||||
@ -397,6 +476,10 @@ pub trait AsActor<C> {
|
|||||||
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
||||||
/// # Ok(Account)
|
/// # Ok(Account)
|
||||||
/// # }
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn get_sender() -> &'static dyn Signer {
|
||||||
|
/// # &*MY_SIGNER
|
||||||
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// # impl AsActor<()> for Account {
|
/// # impl AsActor<()> for Account {
|
||||||
/// # fn get_inbox_url(&self) -> String {
|
/// # fn get_inbox_url(&self) -> String {
|
||||||
@ -420,6 +503,10 @@ pub trait AsActor<C> {
|
|||||||
/// fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
/// fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
||||||
/// Ok(Message { text: obj.object_props.content_string().map_err(|_| ())? })
|
/// Ok(Message { text: obj.object_props.content_string().map_err(|_| ())? })
|
||||||
/// }
|
/// }
|
||||||
|
///
|
||||||
|
/// fn get_sender() -> &'static dyn Signer {
|
||||||
|
/// &*MY_SIGNER
|
||||||
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl AsObject<Account, Create, ()> for Message {
|
/// impl AsObject<Account, Create, ()> for Message {
|
||||||
@ -459,7 +546,51 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::activity_pub::sign::{
|
||||||
|
gen_keypair, Error as SignError, Result as SignResult, Signer,
|
||||||
|
};
|
||||||
use activitypub::{activity::*, actor::Person, object::Note};
|
use activitypub::{activity::*, actor::Person, object::Note};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
|
|
||||||
|
static MY_SIGNER: Lazy<MySigner> = 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<Vec<u8>> {
|
||||||
|
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<bool> {
|
||||||
|
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;
|
struct MyActor;
|
||||||
impl FromId<()> for MyActor {
|
impl FromId<()> for MyActor {
|
||||||
@ -473,6 +604,10 @@ mod tests {
|
|||||||
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
||||||
Ok(MyActor)
|
Ok(MyActor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
&*MY_SIGNER
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsActor<&()> for MyActor {
|
impl AsActor<&()> for MyActor {
|
||||||
@ -497,6 +632,10 @@ mod tests {
|
|||||||
fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> {
|
fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> {
|
||||||
Ok(MyObject)
|
Ok(MyObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
&*MY_SIGNER
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl AsObject<MyActor, Create, &()> for MyObject {
|
impl AsObject<MyActor, Create, &()> for MyObject {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
@ -601,6 +740,10 @@ mod tests {
|
|||||||
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
&*MY_SIGNER
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl AsActor<&()> for FailingActor {
|
impl AsActor<&()> for FailingActor {
|
||||||
fn get_inbox_url(&self) -> String {
|
fn get_inbox_url(&self) -> String {
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
use chrono::{offset::Utc, DateTime};
|
use chrono::{offset::Utc, DateTime};
|
||||||
use openssl::hash::{Hasher, MessageDigest};
|
use openssl::hash::{Hasher, MessageDigest};
|
||||||
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT};
|
use reqwest::{
|
||||||
|
header::{
|
||||||
|
HeaderMap, HeaderValue, InvalidHeaderValue, ACCEPT, CONTENT_TYPE, DATE, HOST, USER_AGENT,
|
||||||
|
},
|
||||||
|
ClientBuilder, Proxy, Response, Url, UrlError,
|
||||||
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
@ -13,6 +18,24 @@ const PLUME_USER_AGENT: &str = concat!("Plume/", env!("CARGO_PKG_VERSION"));
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error();
|
pub struct Error();
|
||||||
|
|
||||||
|
impl From<UrlError> for Error {
|
||||||
|
fn from(_err: UrlError) -> Self {
|
||||||
|
Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InvalidHeaderValue> for Error {
|
||||||
|
fn from(_err: InvalidHeaderValue) -> Self {
|
||||||
|
Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for Error {
|
||||||
|
fn from(_err: reqwest::Error) -> Self {
|
||||||
|
Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Digest(String);
|
pub struct Digest(String);
|
||||||
|
|
||||||
impl Digest {
|
impl Digest {
|
||||||
@ -118,8 +141,8 @@ type Path<'a> = &'a str;
|
|||||||
type Query<'a> = &'a str;
|
type Query<'a> = &'a str;
|
||||||
type RequestTarget<'a> = (Method<'a>, Path<'a>, Option<Query<'a>>);
|
type RequestTarget<'a> = (Method<'a>, Path<'a>, Option<Query<'a>>);
|
||||||
|
|
||||||
pub fn signature<S: Signer>(
|
pub fn signature(
|
||||||
signer: &S,
|
signer: &dyn Signer,
|
||||||
headers: &HeaderMap,
|
headers: &HeaderMap,
|
||||||
request_target: RequestTarget,
|
request_target: RequestTarget,
|
||||||
) -> Result<HeaderValue, Error> {
|
) -> Result<HeaderValue, Error> {
|
||||||
@ -164,10 +187,35 @@ pub fn signature<S: Signer>(
|
|||||||
)).map_err(|_| Error())
|
)).map_err(|_| Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get(url_str: &str, sender: &dyn Signer, proxy: Option<Proxy>) -> Result<Response, Error> {
|
||||||
|
let mut headers = headers();
|
||||||
|
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"))?;
|
||||||
|
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()?
|
||||||
|
.get(url_str)
|
||||||
|
.headers(headers.clone())
|
||||||
|
.header(
|
||||||
|
"Signature",
|
||||||
|
signature(sender, &headers, ("get", url.path(), url.query()))?,
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.map_err(|_| Error())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{signature, Error};
|
use super::signature;
|
||||||
use crate::activity_pub::sign::{gen_keypair, Signer};
|
use crate::activity_pub::sign::{gen_keypair, Error, Result, Signer};
|
||||||
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
|
|
||||||
@ -187,13 +235,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Signer for MySigner {
|
impl Signer for MySigner {
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn get_key_id(&self) -> String {
|
fn get_key_id(&self) -> String {
|
||||||
"mysigner".into()
|
"mysigner".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, to_sign: &str) -> Result<Vec<u8>, Self::Error> {
|
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> {
|
||||||
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
@ -201,7 +247,7 @@ mod tests {
|
|||||||
signer.sign_to_vec().map_err(|_| Error())
|
signer.sign_to_vec().map_err(|_| Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool, Self::Error> {
|
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> {
|
||||||
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
@ -19,20 +19,25 @@ pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error();
|
pub struct Error();
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
impl From<openssl::error::ErrorStack> for Error {
|
||||||
|
fn from(_: openssl::error::ErrorStack) -> Self {
|
||||||
|
Self()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Signer {
|
pub trait Signer {
|
||||||
type Error;
|
|
||||||
|
|
||||||
fn get_key_id(&self) -> String;
|
fn get_key_id(&self) -> String;
|
||||||
|
|
||||||
/// Sign some data with the signer keypair
|
/// Sign some data with the signer keypair
|
||||||
fn sign(&self, to_sign: &str) -> Result<Vec<u8>, Self::Error>;
|
fn sign(&self, to_sign: &str) -> Result<Vec<u8>>;
|
||||||
/// Verify if the signature is valid
|
/// Verify if the signature is valid
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool, Self::Error>;
|
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Signable {
|
pub trait Signable {
|
||||||
fn sign<T>(&mut self, creator: &T) -> Result<&mut Self, Error>
|
fn sign<T>(&mut self, creator: &T) -> Result<&mut Self>
|
||||||
where
|
where
|
||||||
T: Signer;
|
T: Signer;
|
||||||
fn verify<T>(self, creator: &T) -> bool
|
fn verify<T>(self, creator: &T) -> bool
|
||||||
@ -46,7 +51,7 @@ pub trait Signable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Signable for serde_json::Value {
|
impl Signable for serde_json::Value {
|
||||||
fn sign<T: Signer>(&mut self, creator: &T) -> Result<&mut serde_json::Value, Error> {
|
fn sign<T: Signer>(&mut self, creator: &T) -> Result<&mut serde_json::Value> {
|
||||||
let creation_date = Utc::now().to_rfc3339();
|
let creation_date = Utc::now().to_rfc3339();
|
||||||
let mut options = json!({
|
let mut options = json!({
|
||||||
"type": "RsaSignature2017",
|
"type": "RsaSignature2017",
|
||||||
|
@ -443,6 +443,10 @@ impl FromId<DbConn> 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 {
|
impl AsActor<&PlumeRocket> for Blog {
|
||||||
@ -462,24 +466,22 @@ impl AsActor<&PlumeRocket> for Blog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl sign::Signer for Blog {
|
impl sign::Signer for Blog {
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn get_key_id(&self) -> String {
|
fn get_key_id(&self) -> String {
|
||||||
format!("{}#main-key", self.ap_url)
|
format!("{}#main-key", self.ap_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> {
|
fn sign(&self, to_sign: &str) -> sign::Result<Vec<u8>> {
|
||||||
let key = self.get_keypair()?;
|
let key = self.get_keypair().map_err(|_| sign::Error())?;
|
||||||
let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
|
let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
|
||||||
signer.update(to_sign.as_bytes())?;
|
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<bool> {
|
fn verify(&self, data: &str, signature: &[u8]) -> sign::Result<bool> {
|
||||||
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
||||||
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?;
|
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?;
|
||||||
verifier.update(data.as_bytes())?;
|
verifier.update(data.as_bytes())?;
|
||||||
verifier.verify(signature).map_err(Error::from)
|
verifier.verify(signature).map_err(sign::Error::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
|||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
|
sign::Signer,
|
||||||
Id, IntoId, PUBLIC_VISIBILITY,
|
Id, IntoId, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
utils,
|
utils,
|
||||||
@ -328,6 +329,10 @@ impl FromId<DbConn> for Comment {
|
|||||||
comm.notify(conn)?;
|
comm.notify(conn)?;
|
||||||
Ok(comm)
|
Ok(comm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Create, &DbConn> for Comment {
|
impl AsObject<User, Create, &DbConn> for Comment {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, db_conn::DbConn, notifications::*, schema::follows, users::User, Connection, Error,
|
ap_url, db_conn::DbConn, instance::Instance, notifications::*, schema::follows, users::User,
|
||||||
Result, CONFIG,
|
Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitypub::activity::{Accept, Follow as FollowAct, Undo};
|
use activitypub::activity::{Accept, Follow as FollowAct, Undo};
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
||||||
@ -183,6 +183,10 @@ impl FromId<DbConn> for Follow {
|
|||||||
.map_err(|(_, e)| e)?;
|
.map_err(|(_, e)| e)?;
|
||||||
Follow::accept_follow(conn, &actor, &target, follow, actor.id, target.id)
|
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<User, Undo, &DbConn> for Follow {
|
impl AsObject<User, Undo, &DbConn> for Follow {
|
||||||
|
@ -3,11 +3,12 @@ use crate::{
|
|||||||
medias::Media,
|
medias::Media,
|
||||||
safe_string::SafeString,
|
safe_string::SafeString,
|
||||||
schema::{instances, users},
|
schema::{instances, users},
|
||||||
users::{Role, User},
|
users::{NewUser, Role, User},
|
||||||
Connection, Error, Result,
|
Connection, Error, Result,
|
||||||
};
|
};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use plume_common::utils::md_to_html;
|
use plume_common::utils::md_to_html;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
@ -45,6 +46,9 @@ lazy_static! {
|
|||||||
static ref LOCAL_INSTANCE: RwLock<Option<Instance>> = RwLock::new(None);
|
static ref LOCAL_INSTANCE: RwLock<Option<Instance>> = RwLock::new(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LOCAL_INSTANCE_USERNAME: &str = "__instance__";
|
||||||
|
static LOCAL_INSTANCE_USER: OnceCell<User> = OnceCell::new();
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
pub fn set_local(self) {
|
pub fn set_local(self) {
|
||||||
LOCAL_INSTANCE.write().unwrap().replace(self);
|
LOCAL_INSTANCE.write().unwrap().replace(self);
|
||||||
@ -76,6 +80,42 @@ impl Instance {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_local_instance_user(conn: &Connection) -> Result<User> {
|
||||||
|
let instance = Instance::get_local()?;
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_local_instance_user_uncached(conn: &Connection) -> Result<User> {
|
||||||
|
users::table
|
||||||
|
.filter(users::role.eq(3))
|
||||||
|
.first(conn)
|
||||||
|
.or_else(|err| match err {
|
||||||
|
NotFound => Self::create_local_instance_user(conn),
|
||||||
|
_ => Err(Error::Db(err)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_local_instance_user(conn: &Connection) {
|
||||||
|
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<Vec<Instance>> {
|
pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<Instance>> {
|
||||||
instances::table
|
instances::table
|
||||||
.order(instances::public_domain.asc())
|
.order(instances::public_domain.asc())
|
||||||
@ -304,6 +344,7 @@ pub(crate) mod tests {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Instance::cache_local(conn);
|
Instance::cache_local(conn);
|
||||||
|
Instance::cache_local_instance_user(conn);
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ extern crate serde_json;
|
|||||||
extern crate tantivy;
|
extern crate tantivy;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use plume_common::activity_pub::inbox::InboxError;
|
use plume_common::activity_pub::{inbox::InboxError, request, sign};
|
||||||
use posts::PostEvent;
|
use posts::PostEvent;
|
||||||
use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder};
|
use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder};
|
||||||
use users::UserEvent;
|
use users::UserEvent;
|
||||||
@ -79,6 +79,12 @@ impl From<openssl::error::ErrorStack> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<sign::Error> for Error {
|
||||||
|
fn from(_: sign::Error) -> Self {
|
||||||
|
Error::Signature
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<diesel::result::Error> for Error {
|
impl From<diesel::result::Error> for Error {
|
||||||
fn from(err: diesel::result::Error) -> Self {
|
fn from(err: diesel::result::Error) -> Self {
|
||||||
Error::Db(err)
|
Error::Db(err)
|
||||||
@ -151,6 +157,12 @@ impl From<InboxError<Error>> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<request::Error> for Error {
|
||||||
|
fn from(_err: request::Error) -> Error {
|
||||||
|
Error::Request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
/// Adds a function to a model, that returns the first
|
/// Adds a function to a model, that returns the first
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db_conn::DbConn, notifications::*, posts::Post, schema::likes, timeline::*, users::User,
|
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*,
|
||||||
Connection, Error, Result, CONFIG,
|
users::User, Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitypub::activity;
|
use activitypub::activity;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
|
sign::Signer,
|
||||||
Id, IntoId, PUBLIC_VISIBILITY,
|
Id, IntoId, PUBLIC_VISIBILITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -137,6 +138,10 @@ impl FromId<DbConn> for Like {
|
|||||||
res.notify(conn)?;
|
res.notify(conn)?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, activity::Undo, &DbConn> for Like {
|
impl AsObject<User, activity::Undo, &DbConn> for Like {
|
||||||
|
@ -7,7 +7,7 @@ use askama_escape::escape;
|
|||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use guid_create::GUID;
|
use guid_create::GUID;
|
||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{inbox::FromId, Id},
|
activity_pub::{inbox::FromId, request, Id},
|
||||||
utils::MediaProcessor,
|
utils::MediaProcessor,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
@ -220,13 +220,11 @@ impl Media {
|
|||||||
|
|
||||||
let mut dest = fs::File::create(path.clone())?;
|
let mut dest = fs::File::create(path.clone())?;
|
||||||
// TODO: conditional GET
|
// TODO: conditional GET
|
||||||
if let Some(proxy) = CONFIG.proxy() {
|
request::get(
|
||||||
reqwest::ClientBuilder::new().proxy(proxy.clone()).build()?
|
remote_url.as_str(),
|
||||||
} else {
|
User::get_sender(),
|
||||||
reqwest::Client::new()
|
CONFIG.proxy().cloned(),
|
||||||
}
|
)?
|
||||||
.get(remote_url.as_str())
|
|
||||||
.send()?
|
|
||||||
.copy_to(&mut dest)?;
|
.copy_to(&mut dest)?;
|
||||||
|
|
||||||
Media::find_by_file_path(conn, path.to_str().ok_or(Error::InvalidValue)?)
|
Media::find_by_file_path(conn, path.to_str().ok_or(Error::InvalidValue)?)
|
||||||
|
@ -15,6 +15,7 @@ use once_cell::sync::Lazy;
|
|||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
|
sign::Signer,
|
||||||
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY,
|
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
utils::{iri_percent_encode_seg, md_to_html},
|
utils::{iri_percent_encode_seg, md_to_html},
|
||||||
@ -759,6 +760,10 @@ impl FromId<DbConn> for Post {
|
|||||||
|
|
||||||
Ok(post)
|
Ok(post)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Create, &DbConn> for Post {
|
impl AsObject<User, Create, &DbConn> for Post {
|
||||||
@ -830,6 +835,10 @@ impl FromId<DbConn> for PostUpdate {
|
|||||||
tags: updated.object.object_props.tag,
|
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<User, Update, &DbConn> for PostUpdate {
|
impl AsObject<User, Update, &DbConn> for PostUpdate {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db_conn::DbConn, notifications::*, posts::Post, schema::reshares, timeline::*, users::User,
|
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::reshares,
|
||||||
Connection, Error, Result, CONFIG,
|
timeline::*, users::User, Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitypub::activity::{Announce, Undo};
|
use activitypub::activity::{Announce, Undo};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
|
sign::Signer,
|
||||||
Id, IntoId, PUBLIC_VISIBILITY,
|
Id, IntoId, PUBLIC_VISIBILITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -162,6 +163,10 @@ impl FromId<DbConn> for Reshare {
|
|||||||
res.notify(conn)?;
|
res.notify(conn)?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &DbConn> for Reshare {
|
impl AsObject<User, Undo, &DbConn> for Reshare {
|
||||||
|
@ -22,17 +22,13 @@ use openssl::{
|
|||||||
};
|
};
|
||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
ap_accept_header,
|
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
sign::{gen_keypair, Signer},
|
request::get,
|
||||||
|
sign::{gen_keypair, Error as SignError, Result as SignResult, Signer},
|
||||||
ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY,
|
ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
use reqwest::{
|
|
||||||
header::{HeaderValue, ACCEPT},
|
|
||||||
ClientBuilder,
|
|
||||||
};
|
|
||||||
use riker::actors::{Publish, Tell};
|
use riker::actors::{Publish, Tell};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
outcome::IntoOutcome,
|
outcome::IntoOutcome,
|
||||||
@ -52,6 +48,7 @@ pub enum Role {
|
|||||||
Admin = 0,
|
Admin = 0,
|
||||||
Moderator = 1,
|
Moderator = 1,
|
||||||
Normal = 2,
|
Normal = 2,
|
||||||
|
Instance = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, Clone, Debug, AsChangeset)]
|
#[derive(Queryable, Identifiable, Clone, Debug, AsChangeset)]
|
||||||
@ -78,6 +75,7 @@ pub struct User {
|
|||||||
pub summary_html: SafeString,
|
pub summary_html: SafeString,
|
||||||
/// 0 = admin
|
/// 0 = admin
|
||||||
/// 1 = moderator
|
/// 1 = moderator
|
||||||
|
/// 3 = local instance
|
||||||
/// anything else = normal user
|
/// anything else = normal user
|
||||||
pub role: i32,
|
pub role: i32,
|
||||||
pub preferred_theme: Option<String>,
|
pub preferred_theme: Option<String>,
|
||||||
@ -229,20 +227,7 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(url: &str) -> Result<CustomPerson> {
|
fn fetch(url: &str) -> Result<CustomPerson> {
|
||||||
let mut res = ClientBuilder::new()
|
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
|
||||||
.build()?
|
|
||||||
.get(url)
|
|
||||||
.header(
|
|
||||||
ACCEPT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&ap_accept_header()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)?,
|
|
||||||
)
|
|
||||||
.send()?;
|
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
// without this workaround, publicKey is not correctly deserialized
|
// without this workaround, publicKey is not correctly deserialized
|
||||||
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
|
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
|
||||||
@ -261,7 +246,7 @@ impl User {
|
|||||||
conn,
|
conn,
|
||||||
json.object
|
json.object
|
||||||
.object_props
|
.object_props
|
||||||
.icon_image()?
|
.icon_image()? // FIXME: Fails when icon is not set
|
||||||
.object_props
|
.object_props
|
||||||
.url_string()?,
|
.url_string()?,
|
||||||
self,
|
self,
|
||||||
@ -469,20 +454,7 @@ impl User {
|
|||||||
Ok(ActivityStream::new(coll))
|
Ok(ActivityStream::new(coll))
|
||||||
}
|
}
|
||||||
fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> {
|
fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> {
|
||||||
let mut res = ClientBuilder::new()
|
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
|
||||||
.build()?
|
|
||||||
.get(url)
|
|
||||||
.header(
|
|
||||||
ACCEPT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&ap_accept_header()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)?,
|
|
||||||
)
|
|
||||||
.send()?;
|
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
let items = json["items"]
|
let items = json["items"]
|
||||||
@ -496,20 +468,11 @@ impl User {
|
|||||||
Ok((items, next))
|
Ok((items, next))
|
||||||
}
|
}
|
||||||
pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
|
pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
|
||||||
let mut res = ClientBuilder::new()
|
let mut res = get(
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
&self.outbox_url[..],
|
||||||
.build()?
|
Self::get_sender(),
|
||||||
.get(&self.outbox_url[..])
|
CONFIG.proxy().cloned(),
|
||||||
.header(
|
)?;
|
||||||
ACCEPT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&ap_accept_header()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)?,
|
|
||||||
)
|
|
||||||
.send()?;
|
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
if let Some(first) = json.get("first") {
|
if let Some(first) = json.get("first") {
|
||||||
@ -541,20 +504,11 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_followers_ids(&self) -> Result<Vec<String>> {
|
pub fn fetch_followers_ids(&self) -> Result<Vec<String>> {
|
||||||
let mut res = ClientBuilder::new()
|
let mut res = get(
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
&self.followers_endpoint[..],
|
||||||
.build()?
|
Self::get_sender(),
|
||||||
.get(&self.followers_endpoint[..])
|
CONFIG.proxy().cloned(),
|
||||||
.header(
|
)?;
|
||||||
ACCEPT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&ap_accept_header()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)?,
|
|
||||||
)
|
|
||||||
.send()?;
|
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
Ok(json["items"]
|
Ok(json["items"]
|
||||||
@ -1037,6 +991,10 @@ impl FromId<DbConn> for User {
|
|||||||
|
|
||||||
Ok(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 {
|
impl AsActor<&DbConn> for User {
|
||||||
@ -1069,24 +1027,22 @@ impl AsObject<User, Delete, &DbConn> for User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Signer for User {
|
impl Signer for User {
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn get_key_id(&self) -> String {
|
fn get_key_id(&self) -> String {
|
||||||
format!("{}#main-key", self.ap_url)
|
format!("{}#main-key", self.ap_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> {
|
fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
||||||
let key = self.get_keypair()?;
|
let key = self.get_keypair().map_err(|_| SignError())?;
|
||||||
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?;
|
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?;
|
||||||
signer.update(to_sign.as_bytes())?;
|
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<bool> {
|
fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
||||||
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
||||||
let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?;
|
let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?;
|
||||||
verifier.update(data.as_bytes())?;
|
verifier.update(data.as_bytes())?;
|
||||||
verifier.verify(signature).map_err(Error::from)
|
verifier.verify(signature).map_err(SignError::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,10 @@ fn init_pool() -> Option<DbPool> {
|
|||||||
builder = builder.max_size(max_size);
|
builder = builder.max_size(max_size);
|
||||||
};
|
};
|
||||||
let pool = builder.build(manager).ok()?;
|
let pool = builder.build(manager).ok()?;
|
||||||
Instance::cache_local(&pool.get().unwrap());
|
let conn = pool.get().unwrap();
|
||||||
|
Instance::cache_local(&conn);
|
||||||
|
let _ = Instance::create_local_instance_user(&conn);
|
||||||
|
Instance::cache_local_instance_user(&conn);
|
||||||
Some(pool)
|
Some(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,6 +390,7 @@ mod tests {
|
|||||||
posts::{NewPost, Post},
|
posts::{NewPost, Post},
|
||||||
safe_string::SafeString,
|
safe_string::SafeString,
|
||||||
users::{NewUser, User, AUTH_COOKIE},
|
users::{NewUser, User, AUTH_COOKIE},
|
||||||
|
Connection as Conn, CONFIG,
|
||||||
};
|
};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
http::{Cookie, Cookies, SameSite},
|
http::{Cookie, Cookies, SameSite},
|
||||||
@ -398,6 +399,22 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn edit_link_within_post_card() {
|
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 rocket = init_rocket();
|
||||||
let client = Client::new(rocket).expect("valid rocket instance");
|
let client = Client::new(rocket).expect("valid rocket instance");
|
||||||
let dbpool = client.rocket().state::<DbPool>().unwrap();
|
let dbpool = client.rocket().state::<DbPool>().unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user