Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2fe705a712 | ||
|
3a448e9e17 | ||
|
6e4def4cc5 | ||
|
34b8fd83c1 | ||
|
d5774078e0 | ||
|
a3623412f9 | ||
|
1ed60537cf | ||
|
037d670fb7 | ||
|
8b817d50c5 | ||
|
fa48060a94 | ||
|
b41e982daf | ||
|
2fcb449ed8 | ||
|
218bc54a5f | ||
|
64f0333497 | ||
|
8aa7a5780d | ||
|
17c398bcee | ||
|
76f1455372 | ||
|
1bcad6d7cd | ||
|
9a58d9bcb7 | ||
|
c2dcac4413 | ||
|
394273e866 | ||
|
1bcc70c174 |
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE instances DROP COLUMN private_key;
|
||||||
|
ALTER TABLE instances DROP COLUMN public_key;
|
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE instances ADD COLUMN private_key TEXT;
|
||||||
|
ALTER TABLE instances ADD COLUMN public_key TEXT;
|
@ -0,0 +1,30 @@
|
|||||||
|
CREATE TABLE instances_old (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
public_domain VARCHAR NOT NULL,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
local BOOLEAN NOT NULL DEFAULT 'f',
|
||||||
|
blocked BOOLEAN NOT NULL DEFAULT 'f',
|
||||||
|
creation_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
open_registrations BOOLEAN NOT NULL DEFAULT 't',
|
||||||
|
short_description TEXT NOT NULL DEFAULT '',
|
||||||
|
long_description TEXT NOT NULL DEFAULT '',
|
||||||
|
default_license TEXT NOT NULL DEFAULT 'CC-0',
|
||||||
|
long_description_html VARCHAR NOT NULL DEFAULT '',
|
||||||
|
short_description_html VARCHAR NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
INSERT INTO instances_old SELECT
|
||||||
|
id,
|
||||||
|
public_domain,
|
||||||
|
name,
|
||||||
|
local,
|
||||||
|
blocked,
|
||||||
|
creation_date,
|
||||||
|
open_registrations,
|
||||||
|
short_description,
|
||||||
|
long_description,
|
||||||
|
default_license,
|
||||||
|
long_description_html,
|
||||||
|
short_description_html
|
||||||
|
FROM instances;
|
||||||
|
DROP TABLE instances;
|
||||||
|
ALTER TABLE instances_old RENAME TO instances;
|
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE instances ADD COLUMN private_key TEXT;
|
||||||
|
ALTER TABLE instances ADD COLUMN public_key TEXT;
|
@ -1,6 +1,6 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
|
||||||
use plume_models::{instance::*, safe_string::SafeString, Connection};
|
use plume_models::{instance::NewInstance, Connection};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
pub fn command<'a, 'b>() -> App<'a, 'b> {
|
pub fn command<'a, 'b>() -> App<'a, 'b> {
|
||||||
@ -53,19 +53,5 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
|||||||
.unwrap_or_else(|| String::from("CC-BY-SA"));
|
.unwrap_or_else(|| String::from("CC-BY-SA"));
|
||||||
let open_reg = !args.is_present("private");
|
let open_reg = !args.is_present("private");
|
||||||
|
|
||||||
Instance::insert(
|
NewInstance::new_local(conn, domain, name, open_reg, license).expect("Couldn't save instance");
|
||||||
conn,
|
|
||||||
NewInstance {
|
|
||||||
public_domain: domain,
|
|
||||||
name,
|
|
||||||
local: true,
|
|
||||||
long_description: SafeString::new(""),
|
|
||||||
short_description: SafeString::new(""),
|
|
||||||
default_license: license,
|
|
||||||
open_registrations: open_reg,
|
|
||||||
short_description_html: String::new(),
|
|
||||||
long_description_html: String::new(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.expect("Couldn't save instance");
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
use reqwest::header::{HeaderValue, ACCEPT};
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use super::{request, sign::Signer};
|
||||||
|
use reqwest::{
|
||||||
|
header::{HeaderValue, HOST},
|
||||||
|
Url,
|
||||||
|
};
|
||||||
|
|
||||||
/// 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 +15,8 @@ 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 plume_common::activity_pub::inbox::*;
|
/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
|
/// # use plume_common::activity_pub::{inbox::*, sign::{gen_keypair, Error as SignatureError, Result as SignatureResult, Signer}};
|
||||||
/// # struct User;
|
/// # struct User;
|
||||||
/// # impl FromId<()> for User {
|
/// # impl FromId<()> for User {
|
||||||
/// # type Error = ();
|
/// # type Error = ();
|
||||||
@ -59,6 +65,42 @@ use std::fmt::Debug;
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
|
/// # 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) -> SignatureResult<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(|_| SignatureError())
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<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(|_| SignatureError())
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
/// #
|
/// #
|
||||||
/// # let mut act = Create::default();
|
/// # let mut act = Create::default();
|
||||||
/// # act.object_props.set_id_string(String::from("https://test.ap/activity")).unwrap();
|
/// # act.object_props.set_id_string(String::from("https://test.ap/activity")).unwrap();
|
||||||
@ -69,8 +111,9 @@ use std::fmt::Debug;
|
|||||||
/// # let activity_json = serde_json::to_value(act).unwrap();
|
/// # let activity_json = serde_json::to_value(act).unwrap();
|
||||||
/// #
|
/// #
|
||||||
/// # let conn = ();
|
/// # let conn = ();
|
||||||
|
/// # let sender = MySigner::new();
|
||||||
/// #
|
/// #
|
||||||
/// let result: Result<(), ()> = Inbox::handle(&conn, activity_json)
|
/// let result: Result<(), ()> = Inbox::handle(&conn, &sender, activity_json)
|
||||||
/// .with::<User, Announce, Message>(None)
|
/// .with::<User, Announce, Message>(None)
|
||||||
/// .with::<User, Create, Message>(None)
|
/// .with::<User, Create, Message>(None)
|
||||||
/// .done();
|
/// .done();
|
||||||
@ -84,9 +127,10 @@ where
|
|||||||
/// # Structure
|
/// # Structure
|
||||||
///
|
///
|
||||||
/// - the context to be passed to each handler.
|
/// - the context to be passed to each handler.
|
||||||
|
/// - the sender actor to sign request
|
||||||
/// - the activity
|
/// - the activity
|
||||||
/// - the reason it has not been handled yet
|
/// - the reason it has not been handled yet
|
||||||
NotHandled(&'a C, serde_json::Value, InboxError<E>),
|
NotHandled(&'a C, &'a dyn Signer, serde_json::Value, InboxError<E>),
|
||||||
|
|
||||||
/// A matching handler have been found but failed
|
/// A matching handler have been found but failed
|
||||||
///
|
///
|
||||||
@ -139,8 +183,12 @@ where
|
|||||||
///
|
///
|
||||||
/// - `ctx`: the context to pass to each handler
|
/// - `ctx`: the context to pass to each handler
|
||||||
/// - `json`: the JSON representation of the incoming activity
|
/// - `json`: the JSON representation of the incoming activity
|
||||||
pub fn handle(ctx: &'a C, json: serde_json::Value) -> Inbox<'a, C, E, R> {
|
pub fn handle(
|
||||||
Inbox::NotHandled(ctx, json, InboxError::NoMatch)
|
ctx: &'a C,
|
||||||
|
sender: &'a dyn Signer,
|
||||||
|
json: serde_json::Value,
|
||||||
|
) -> Inbox<'a, C, E, R> {
|
||||||
|
Inbox::NotHandled(ctx, sender, json, InboxError::NoMatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers an handler on this Inbox.
|
/// Registers an handler on this Inbox.
|
||||||
@ -151,27 +199,30 @@ where
|
|||||||
M: AsObject<A, V, &'a C, Error = E> + FromId<C, Error = E>,
|
M: AsObject<A, V, &'a C, Error = E> + FromId<C, Error = E>,
|
||||||
M::Output: Into<R>,
|
M::Output: Into<R>,
|
||||||
{
|
{
|
||||||
if let Inbox::NotHandled(ctx, mut act, e) = self {
|
if let Inbox::NotHandled(ctx, sender, mut act, e) = self {
|
||||||
if serde_json::from_value::<V>(act.clone()).is_ok() {
|
if serde_json::from_value::<V>(act.clone()).is_ok() {
|
||||||
let act_clone = act.clone();
|
let act_clone = act.clone();
|
||||||
let act_id = match act_clone["id"].as_str() {
|
let act_id = match act_clone["id"].as_str() {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidID),
|
None => return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidID),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the actor ID
|
// Get the actor ID
|
||||||
let actor_id = match get_id(act["actor"].clone()) {
|
let actor_id = match get_id(act["actor"].clone()) {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(None)),
|
None => {
|
||||||
|
return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidActor(None))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if Self::is_spoofed_activity(&actor_id, &act) {
|
if Self::is_spoofed_activity(&actor_id, &act) {
|
||||||
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None));
|
return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidObject(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform this actor to a model (see FromId for details about the from_id function)
|
// Transform this actor to a model (see FromId for details about the from_id function)
|
||||||
let actor = match A::from_id(
|
let actor = match A::from_id(
|
||||||
ctx,
|
ctx,
|
||||||
|
sender,
|
||||||
&actor_id,
|
&actor_id,
|
||||||
serde_json::from_value(act["actor"].clone()).ok(),
|
serde_json::from_value(act["actor"].clone()).ok(),
|
||||||
proxy,
|
proxy,
|
||||||
@ -182,17 +233,25 @@ where
|
|||||||
if let Some(json) = json {
|
if let Some(json) = json {
|
||||||
act["actor"] = json;
|
act["actor"] = json;
|
||||||
}
|
}
|
||||||
return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(Some(e)));
|
return Inbox::NotHandled(
|
||||||
|
ctx,
|
||||||
|
sender,
|
||||||
|
act,
|
||||||
|
InboxError::InvalidActor(Some(e)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Same logic for "object"
|
// Same logic for "object"
|
||||||
let obj_id = match get_id(act["object"].clone()) {
|
let obj_id = match get_id(act["object"].clone()) {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None)),
|
None => {
|
||||||
|
return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidObject(None))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let obj = match M::from_id(
|
let obj = match M::from_id(
|
||||||
ctx,
|
ctx,
|
||||||
|
sender,
|
||||||
&obj_id,
|
&obj_id,
|
||||||
serde_json::from_value(act["object"].clone()).ok(),
|
serde_json::from_value(act["object"].clone()).ok(),
|
||||||
proxy,
|
proxy,
|
||||||
@ -202,7 +261,12 @@ where
|
|||||||
if let Some(json) = json {
|
if let Some(json) = json {
|
||||||
act["object"] = json;
|
act["object"] = json;
|
||||||
}
|
}
|
||||||
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(Some(e)));
|
return Inbox::NotHandled(
|
||||||
|
ctx,
|
||||||
|
sender,
|
||||||
|
act,
|
||||||
|
InboxError::InvalidObject(Some(e)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -214,7 +278,7 @@ where
|
|||||||
} else {
|
} else {
|
||||||
// If the Activity type is not matching the expected one for
|
// If the Activity type is not matching the expected one for
|
||||||
// this handler, try with the next one.
|
// this handler, try with the next one.
|
||||||
Inbox::NotHandled(ctx, act, e)
|
Inbox::NotHandled(ctx, sender, act, e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
@ -225,7 +289,7 @@ where
|
|||||||
pub fn done(self) -> Result<R, E> {
|
pub fn done(self) -> Result<R, E> {
|
||||||
match self {
|
match self {
|
||||||
Inbox::Handled(res) => Ok(res),
|
Inbox::Handled(res) => Ok(res),
|
||||||
Inbox::NotHandled(_, _, err) => Err(E::from(err)),
|
Inbox::NotHandled(_, _, _, err) => Err(E::from(err)),
|
||||||
Inbox::Failed(err) => Err(err),
|
Inbox::Failed(err) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -292,6 +356,7 @@ pub trait FromId<C>: Sized {
|
|||||||
/// If absent, the ID will be dereferenced.
|
/// If absent, the ID will be dereferenced.
|
||||||
fn from_id(
|
fn from_id(
|
||||||
ctx: &C,
|
ctx: &C,
|
||||||
|
sender: &dyn Signer,
|
||||||
id: &str,
|
id: &str,
|
||||||
object: Option<Self::Object>,
|
object: Option<Self::Object>,
|
||||||
proxy: Option<&reqwest::Proxy>,
|
proxy: Option<&reqwest::Proxy>,
|
||||||
@ -300,7 +365,7 @@ pub trait FromId<C>: Sized {
|
|||||||
Ok(x) => Ok(x),
|
Ok(x) => Ok(x),
|
||||||
_ => match object {
|
_ => match object {
|
||||||
Some(o) => Self::from_activity(ctx, o).map_err(|e| (None, e)),
|
Some(o) => Self::from_activity(ctx, o).map_err(|e| (None, e)),
|
||||||
None => Self::from_activity(ctx, Self::deref(id, proxy.cloned())?)
|
None => Self::from_activity(ctx, Self::deref(id, sender, proxy.cloned())?)
|
||||||
.map_err(|e| (None, e)),
|
.map_err(|e| (None, e)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -309,8 +374,17 @@ pub trait FromId<C>: Sized {
|
|||||||
/// Dereferences an ID
|
/// Dereferences an ID
|
||||||
fn deref(
|
fn deref(
|
||||||
id: &str,
|
id: &str,
|
||||||
|
sender: &dyn Signer,
|
||||||
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)> {
|
||||||
|
let mut headers = request::headers();
|
||||||
|
let url = Url::parse(&id).map_err(|_| (None, InboxError::InvalidID.into()))?;
|
||||||
|
if !url.has_host() {
|
||||||
|
return Err((None, InboxError::InvalidID.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 {
|
if let Some(proxy) = proxy {
|
||||||
reqwest::ClientBuilder::new().proxy(proxy)
|
reqwest::ClientBuilder::new().proxy(proxy)
|
||||||
} else {
|
} else {
|
||||||
@ -320,15 +394,11 @@ pub trait FromId<C>: Sized {
|
|||||||
.build()
|
.build()
|
||||||
.map_err(|_| (None, InboxError::DerefError.into()))?
|
.map_err(|_| (None, InboxError::DerefError.into()))?
|
||||||
.get(id)
|
.get(id)
|
||||||
|
.headers(headers.clone())
|
||||||
.header(
|
.header(
|
||||||
ACCEPT,
|
"Signature",
|
||||||
HeaderValue::from_str(
|
request::signature(sender, &headers, ("get", url.path(), url.query()))
|
||||||
&super::ap_accept_header()
|
.map_err(|_| (None, InboxError::DerefError.into()))?,
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)
|
|
||||||
.map_err(|_| (None, InboxError::DerefError.into()))?,
|
|
||||||
)
|
)
|
||||||
.send()
|
.send()
|
||||||
.map_err(|_| (None, InboxError::DerefError))
|
.map_err(|_| (None, InboxError::DerefError))
|
||||||
@ -458,8 +528,10 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::super::sign::{gen_keypair, Error as SignatureError, Result as SignatureResult};
|
||||||
use super::*;
|
use super::*;
|
||||||
use activitypub::{activity::*, actor::Person, object::Note};
|
use activitypub::{activity::*, actor::Person, object::Note};
|
||||||
|
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
|
|
||||||
struct MyActor;
|
struct MyActor;
|
||||||
impl FromId<()> for MyActor {
|
impl FromId<()> for MyActor {
|
||||||
@ -557,10 +629,47 @@ mod tests {
|
|||||||
act
|
act
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) -> SignatureResult<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(|_| SignatureError())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<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(|_| SignatureError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_inbox_basic() {
|
fn test_inbox_basic() {
|
||||||
let act = serde_json::to_value(build_create()).unwrap();
|
let act = serde_json::to_value(build_create()).unwrap();
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act)
|
let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act)
|
||||||
.with::<MyActor, Create, MyObject>(None)
|
.with::<MyActor, Create, MyObject>(None)
|
||||||
.done();
|
.done();
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
@ -569,7 +678,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_inbox_multi_handlers() {
|
fn test_inbox_multi_handlers() {
|
||||||
let act = serde_json::to_value(build_create()).unwrap();
|
let act = serde_json::to_value(build_create()).unwrap();
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act)
|
let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act)
|
||||||
.with::<MyActor, Announce, MyObject>(None)
|
.with::<MyActor, Announce, MyObject>(None)
|
||||||
.with::<MyActor, Delete, MyObject>(None)
|
.with::<MyActor, Delete, MyObject>(None)
|
||||||
.with::<MyActor, Create, MyObject>(None)
|
.with::<MyActor, Create, MyObject>(None)
|
||||||
@ -582,7 +691,7 @@ mod tests {
|
|||||||
fn test_inbox_failure() {
|
fn test_inbox_failure() {
|
||||||
let act = serde_json::to_value(build_create()).unwrap();
|
let act = serde_json::to_value(build_create()).unwrap();
|
||||||
// Create is not handled by this inbox
|
// Create is not handled by this inbox
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act)
|
let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act)
|
||||||
.with::<MyActor, Announce, MyObject>(None)
|
.with::<MyActor, Announce, MyObject>(None)
|
||||||
.with::<MyActor, Like, MyObject>(None)
|
.with::<MyActor, Like, MyObject>(None)
|
||||||
.done();
|
.done();
|
||||||
@ -631,12 +740,12 @@ mod tests {
|
|||||||
fn test_inbox_actor_failure() {
|
fn test_inbox_actor_failure() {
|
||||||
let act = serde_json::to_value(build_create()).unwrap();
|
let act = serde_json::to_value(build_create()).unwrap();
|
||||||
|
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act.clone())
|
let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act.clone())
|
||||||
.with::<FailingActor, Create, MyObject>(None)
|
.with::<FailingActor, Create, MyObject>(None)
|
||||||
.done();
|
.done();
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
|
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act.clone())
|
let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act.clone())
|
||||||
.with::<FailingActor, Create, MyObject>(None)
|
.with::<FailingActor, Create, MyObject>(None)
|
||||||
.with::<MyActor, Create, MyObject>(None)
|
.with::<MyActor, Create, MyObject>(None)
|
||||||
.done();
|
.done();
|
||||||
|
@ -118,8 +118,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> {
|
||||||
@ -166,8 +166,10 @@ pub fn signature<S: Signer>(
|
|||||||
|
|
||||||
#[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 as SignatureError, Result as SignatureResult, 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,26 +189,24 @@ 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) -> SignatureResult<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();
|
||||||
signer.update(to_sign.as_bytes()).unwrap();
|
signer.update(to_sign.as_bytes()).unwrap();
|
||||||
signer.sign_to_vec().map_err(|_| Error())
|
signer.sign_to_vec().map_err(|_| SignatureError())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool, Self::Error> {
|
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<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();
|
||||||
verifier.update(data.as_bytes()).unwrap();
|
verifier.update(data.as_bytes()).unwrap();
|
||||||
verifier.verify(&signature).map_err(|_| Error())
|
verifier.verify(&signature).map_err(|_| SignatureError())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -18,7 +18,8 @@ use openssl::{
|
|||||||
};
|
};
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
inbox::{AsActor, FromId},
|
inbox::{AsActor, FromId},
|
||||||
sign, ActivityStream, ApSignature, Id, IntoId, PublicKey, Source,
|
sign::{self, Error as SignatureError, Result as SignatureResult},
|
||||||
|
ActivityStream, ApSignature, Id, IntoId, PublicKey, Source,
|
||||||
};
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use webfinger::*;
|
use webfinger::*;
|
||||||
@ -149,7 +150,16 @@ impl Blog {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||||
.ok_or(Error::Webfinger)
|
.ok_or(Error::Webfinger)
|
||||||
.and_then(|l| Blog::from_id(conn, &l.href?, None, CONFIG.proxy()).map_err(|(_, e)| e))
|
.and_then(|l| {
|
||||||
|
Blog::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&l.href?,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.map_err(|(_, e)| e)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
|
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
|
||||||
@ -359,6 +369,8 @@ impl FromId<DbConn> for Blog {
|
|||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
short_description_html: String::new(),
|
short_description_html: String::new(),
|
||||||
long_description_html: String::new(),
|
long_description_html: String::new(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
@ -372,7 +384,14 @@ impl FromId<DbConn> for Blog {
|
|||||||
Media::save_remote(
|
Media::save_remote(
|
||||||
conn,
|
conn,
|
||||||
icon.object_props.url_string().ok()?,
|
icon.object_props.url_string().ok()?,
|
||||||
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
|
&User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&owner,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.ok()?,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
@ -388,7 +407,14 @@ impl FromId<DbConn> for Blog {
|
|||||||
Media::save_remote(
|
Media::save_remote(
|
||||||
conn,
|
conn,
|
||||||
banner.object_props.url_string().ok()?,
|
banner.object_props.url_string().ok()?,
|
||||||
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
|
&User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&owner,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.ok()?,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
@ -451,24 +477,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) -> SignatureResult<Vec<u8>> {
|
||||||
let key = self.get_keypair()?;
|
let key = self.get_keypair()?;
|
||||||
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(SignatureError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> {
|
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<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(SignatureError::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,6 +236,7 @@ impl FromId<DbConn> for Comment {
|
|||||||
})?,
|
})?,
|
||||||
author_id: User::from_id(
|
author_id: User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
¬e.object_props.attributed_to_link::<Id>()?,
|
¬e.object_props.attributed_to_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@ -294,7 +295,13 @@ impl FromId<DbConn> for Comment {
|
|||||||
.collect::<HashSet<_>>() // remove duplicates (don't do a query more than once)
|
.collect::<HashSet<_>>() // remove duplicates (don't do a query more than once)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
if let Ok(user) = User::from_id(conn, &v, None, CONFIG.proxy()) {
|
if let Ok(user) = User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&v,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
) {
|
||||||
vec![user]
|
vec![user]
|
||||||
} else {
|
} else {
|
||||||
vec![] // TODO try to fetch collection
|
vec![] // TODO try to fetch collection
|
||||||
|
@ -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};
|
||||||
@ -168,6 +168,7 @@ impl FromId<DbConn> for Follow {
|
|||||||
fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> {
|
fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> {
|
||||||
let actor = User::from_id(
|
let actor = User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&follow.follow_props.actor_link::<Id>()?,
|
&follow.follow_props.actor_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@ -176,6 +177,7 @@ impl FromId<DbConn> for Follow {
|
|||||||
|
|
||||||
let target = User::from_id(
|
let target = User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&follow.follow_props.object_link::<Id>()?,
|
&follow.follow_props.object_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
|
@ -3,7 +3,9 @@ use activitypub::activity::*;
|
|||||||
use crate::{
|
use crate::{
|
||||||
comments::Comment,
|
comments::Comment,
|
||||||
db_conn::DbConn,
|
db_conn::DbConn,
|
||||||
follows, likes,
|
follows,
|
||||||
|
instance::Instance,
|
||||||
|
likes,
|
||||||
posts::{Post, PostUpdate},
|
posts::{Post, PostUpdate},
|
||||||
reshares::Reshare,
|
reshares::Reshare,
|
||||||
users::User,
|
users::User,
|
||||||
@ -47,20 +49,24 @@ impl_into_inbox_result! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> {
|
pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> {
|
||||||
Inbox::handle(conn, act)
|
Inbox::handle(
|
||||||
.with::<User, Announce, Post>(CONFIG.proxy())
|
conn,
|
||||||
.with::<User, Create, Comment>(CONFIG.proxy())
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
.with::<User, Create, Post>(CONFIG.proxy())
|
act,
|
||||||
.with::<User, Delete, Comment>(CONFIG.proxy())
|
)
|
||||||
.with::<User, Delete, Post>(CONFIG.proxy())
|
.with::<User, Announce, Post>(CONFIG.proxy())
|
||||||
.with::<User, Delete, User>(CONFIG.proxy())
|
.with::<User, Create, Comment>(CONFIG.proxy())
|
||||||
.with::<User, Follow, User>(CONFIG.proxy())
|
.with::<User, Create, Post>(CONFIG.proxy())
|
||||||
.with::<User, Like, Post>(CONFIG.proxy())
|
.with::<User, Delete, Comment>(CONFIG.proxy())
|
||||||
.with::<User, Undo, Reshare>(CONFIG.proxy())
|
.with::<User, Delete, Post>(CONFIG.proxy())
|
||||||
.with::<User, Undo, follows::Follow>(CONFIG.proxy())
|
.with::<User, Delete, User>(CONFIG.proxy())
|
||||||
.with::<User, Undo, likes::Like>(CONFIG.proxy())
|
.with::<User, Follow, User>(CONFIG.proxy())
|
||||||
.with::<User, Update, PostUpdate>(CONFIG.proxy())
|
.with::<User, Like, Post>(CONFIG.proxy())
|
||||||
.done()
|
.with::<User, Undo, Reshare>(CONFIG.proxy())
|
||||||
|
.with::<User, Undo, follows::Follow>(CONFIG.proxy())
|
||||||
|
.with::<User, Undo, likes::Like>(CONFIG.proxy())
|
||||||
|
.with::<User, Update, PostUpdate>(CONFIG.proxy())
|
||||||
|
.done()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -6,10 +6,26 @@ use crate::{
|
|||||||
users::{Role, User},
|
users::{Role, User},
|
||||||
Connection, Error, Result,
|
Connection, Error, Result,
|
||||||
};
|
};
|
||||||
|
use activitypub::{actor::Service, CustomObject};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use plume_common::utils::md_to_html;
|
use openssl::{
|
||||||
|
hash::MessageDigest,
|
||||||
|
pkey::{PKey, Private},
|
||||||
|
rsa::Rsa,
|
||||||
|
sign,
|
||||||
|
};
|
||||||
|
use plume_common::{
|
||||||
|
activity_pub::{
|
||||||
|
sign::{gen_keypair, Error as SignatureError, Result as SignatureResult, Signer},
|
||||||
|
ApSignature, PublicKey,
|
||||||
|
},
|
||||||
|
utils::md_to_html,
|
||||||
|
};
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
pub type CustomService = CustomObject<ApSignature, Service>;
|
||||||
|
|
||||||
#[derive(Clone, Identifiable, Queryable)]
|
#[derive(Clone, Identifiable, Queryable)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
@ -25,6 +41,8 @@ pub struct Instance {
|
|||||||
pub default_license: String,
|
pub default_license: String,
|
||||||
pub long_description_html: SafeString,
|
pub long_description_html: SafeString,
|
||||||
pub short_description_html: SafeString,
|
pub short_description_html: SafeString,
|
||||||
|
pub private_key: Option<String>,
|
||||||
|
pub public_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Insertable)]
|
#[derive(Clone, Insertable)]
|
||||||
@ -39,6 +57,8 @@ pub struct NewInstance {
|
|||||||
pub default_license: String,
|
pub default_license: String,
|
||||||
pub long_description_html: String,
|
pub long_description_html: String,
|
||||||
pub short_description_html: String,
|
pub short_description_html: String,
|
||||||
|
pub private_key: Option<String>,
|
||||||
|
pub public_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@ -69,6 +89,13 @@ impl Instance {
|
|||||||
*LOCAL_INSTANCE.write().unwrap() = Instance::get_local_uncached(conn).ok();
|
*LOCAL_INSTANCE.write().unwrap() = Instance::get_local_uncached(conn).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_locals(conn: &Connection) -> Result<Vec<Instance>> {
|
||||||
|
instances::table
|
||||||
|
.filter(instances::local.eq(true))
|
||||||
|
.load::<Instance>(conn)
|
||||||
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_remotes(conn: &Connection) -> Result<Vec<Instance>> {
|
pub fn get_remotes(conn: &Connection) -> Result<Vec<Instance>> {
|
||||||
instances::table
|
instances::table
|
||||||
.filter(instances::local.eq(false))
|
.filter(instances::local.eq(false))
|
||||||
@ -238,6 +265,112 @@ impl Instance {
|
|||||||
})
|
})
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_keypair(&self, conn: &Connection) -> Result<()> {
|
||||||
|
let (pub_key, priv_key) = gen_keypair();
|
||||||
|
let private_key = String::from_utf8(priv_key).or(Err(Error::Signature))?;
|
||||||
|
let public_key = String::from_utf8(pub_key).or(Err(Error::Signature))?;
|
||||||
|
diesel::update(self)
|
||||||
|
.set((
|
||||||
|
instances::private_key.eq(Some(private_key)),
|
||||||
|
instances::public_key.eq(Some(public_key)),
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
.and(Ok(()))
|
||||||
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
||||||
|
PKey::from_rsa(Rsa::private_key_from_pem(
|
||||||
|
self.private_key.clone()?.as_ref(),
|
||||||
|
)?)
|
||||||
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is experimental and might change in the future.
|
||||||
|
/// Currently "!" sign is used but it's not decided.
|
||||||
|
pub fn ap_url(&self) -> String {
|
||||||
|
ap_url(&format!(
|
||||||
|
"{}/!/{}",
|
||||||
|
Self::get_local().unwrap().public_domain,
|
||||||
|
self.public_domain
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_activity(&self) -> Result<CustomService> {
|
||||||
|
let mut actor = Service::default();
|
||||||
|
let id = self.ap_url();
|
||||||
|
actor.object_props.set_id_string(id.clone())?;
|
||||||
|
actor.object_props.set_name_string(self.name.clone())?;
|
||||||
|
|
||||||
|
let mut ap_signature = ApSignature::default();
|
||||||
|
if self.local {
|
||||||
|
if let Some(pub_key) = self.public_key.clone() {
|
||||||
|
let mut public_key = PublicKey::default();
|
||||||
|
public_key.set_id_string(format!("{}#main-key", id))?;
|
||||||
|
public_key.set_owner_string(id)?;
|
||||||
|
public_key.set_public_key_pem_string(pub_key)?;
|
||||||
|
ap_signature.set_public_key_publickey(public_key)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(CustomService::new(actor, ap_signature))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewInstance {
|
||||||
|
pub fn new_local(
|
||||||
|
conn: &Connection,
|
||||||
|
public_domain: String,
|
||||||
|
name: String,
|
||||||
|
open_registrations: bool,
|
||||||
|
default_license: String,
|
||||||
|
) -> Result<Instance> {
|
||||||
|
let (pub_key, priv_key) = gen_keypair();
|
||||||
|
|
||||||
|
Instance::insert(
|
||||||
|
conn,
|
||||||
|
NewInstance {
|
||||||
|
public_domain,
|
||||||
|
name,
|
||||||
|
local: true,
|
||||||
|
open_registrations,
|
||||||
|
short_description: SafeString::new(""),
|
||||||
|
long_description: SafeString::new(""),
|
||||||
|
default_license,
|
||||||
|
long_description_html: String::new(),
|
||||||
|
short_description_html: String::new(),
|
||||||
|
private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?),
|
||||||
|
public_key: Some(String::from_utf8(pub_key).or(Err(Error::Signature))?),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signer for Instance {
|
||||||
|
fn get_key_id(&self) -> String {
|
||||||
|
format!("{}#main-key", self.ap_url())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
|
||||||
|
let key = self.get_keypair()?;
|
||||||
|
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?;
|
||||||
|
signer.update(to_sign.as_bytes())?;
|
||||||
|
signer.sign_to_vec().map_err(SignatureError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
|
||||||
|
if self.public_key.is_none() {
|
||||||
|
warn!("missing public key for {}", self.public_domain);
|
||||||
|
return Err(SignatureError());
|
||||||
|
}
|
||||||
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(
|
||||||
|
self.public_key.clone().unwrap().as_ref(),
|
||||||
|
)?)?;
|
||||||
|
let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?;
|
||||||
|
verifier.update(data.as_bytes())?;
|
||||||
|
verifier.verify(&signature).map_err(SignatureError::from)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -259,6 +392,8 @@ pub(crate) mod tests {
|
|||||||
name: "My instance".to_string(),
|
name: "My instance".to_string(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: "plu.me".to_string(),
|
public_domain: "plu.me".to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
NewInstance {
|
NewInstance {
|
||||||
default_license: "WTFPL".to_string(),
|
default_license: "WTFPL".to_string(),
|
||||||
@ -270,6 +405,8 @@ pub(crate) mod tests {
|
|||||||
name: "An instance".to_string(),
|
name: "An instance".to_string(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: "1plu.me".to_string(),
|
public_domain: "1plu.me".to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
NewInstance {
|
NewInstance {
|
||||||
default_license: "CC-0".to_string(),
|
default_license: "CC-0".to_string(),
|
||||||
@ -281,6 +418,8 @@ pub(crate) mod tests {
|
|||||||
name: "Someone instance".to_string(),
|
name: "Someone instance".to_string(),
|
||||||
open_registrations: false,
|
open_registrations: false,
|
||||||
public_domain: "2plu.me".to_string(),
|
public_domain: "2plu.me".to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
NewInstance {
|
NewInstance {
|
||||||
default_license: "CC-0-BY-SA".to_string(),
|
default_license: "CC-0-BY-SA".to_string(),
|
||||||
@ -292,6 +431,8 @@ pub(crate) mod tests {
|
|||||||
name: "Nice day".to_string(),
|
name: "Nice day".to_string(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: "3plu.me".to_string(),
|
public_domain: "3plu.me".to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -17,8 +17,10 @@ extern crate serde_json;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate tantivy;
|
extern crate tantivy;
|
||||||
|
|
||||||
|
use db_conn::DbPool;
|
||||||
|
use instance::Instance;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use plume_common::activity_pub::inbox::InboxError;
|
use plume_common::activity_pub::{inbox::InboxError, 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;
|
||||||
@ -80,6 +82,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)
|
||||||
@ -160,6 +168,12 @@ impl From<InboxError<Error>> for Error {
|
|||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
impl From<Error> for sign::Error {
|
||||||
|
fn from(_: Error) -> Self {
|
||||||
|
Self()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds a function to a model, that returns the first
|
/// Adds a function to a model, that returns the first
|
||||||
/// matching row for a given list of fields.
|
/// matching row for a given list of fields.
|
||||||
///
|
///
|
||||||
@ -295,6 +309,17 @@ pub fn ap_url(url: &str) -> String {
|
|||||||
format!("https://{}", url)
|
format!("https://{}", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn migrate_data(dbpool: &DbPool) -> Result<()> {
|
||||||
|
ensure_local_instance_keys(&dbpool.get().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_local_instance_keys(conn: &Connection) -> Result<()> {
|
||||||
|
for instance in Instance::get_locals(conn)? {
|
||||||
|
instance.set_keypair(conn)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
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;
|
||||||
@ -117,6 +117,7 @@ impl FromId<DbConn> for Like {
|
|||||||
NewLike {
|
NewLike {
|
||||||
post_id: Post::from_id(
|
post_id: Post::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&act.like_props.object_link::<Id>()?,
|
&act.like_props.object_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@ -125,6 +126,7 @@ impl FromId<DbConn> for Like {
|
|||||||
.id,
|
.id,
|
||||||
user_id: User::from_id(
|
user_id: User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&act.like_props.actor_link::<Id>()?,
|
&act.like_props.actor_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
|
@ -272,6 +272,7 @@ impl Media {
|
|||||||
content_warning: image.object_props.summary_string().ok(),
|
content_warning: image.object_props.summary_string().ok(),
|
||||||
owner_id: User::from_id(
|
owner_id: User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
image
|
image
|
||||||
.object_props
|
.object_props
|
||||||
.attributed_to_link_vec::<Id>()
|
.attributed_to_link_vec::<Id>()
|
||||||
|
@ -630,13 +630,28 @@ impl FromId<DbConn> for Post {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.fold((None, vec![]), |(blog, mut authors), link| {
|
.fold((None, vec![]), |(blog, mut authors), link| {
|
||||||
let url = link;
|
let url = link;
|
||||||
match User::from_id(conn, &url, None, CONFIG.proxy()) {
|
match User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&url,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
) {
|
||||||
Ok(u) => {
|
Ok(u) => {
|
||||||
authors.push(u);
|
authors.push(u);
|
||||||
(blog, authors)
|
(blog, authors)
|
||||||
}
|
}
|
||||||
Err(_) => (
|
Err(_) => (
|
||||||
blog.or_else(|| Blog::from_id(conn, &url, None, CONFIG.proxy()).ok()),
|
blog.or_else(|| {
|
||||||
|
Blog::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&url,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
}),
|
||||||
authors,
|
authors,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -837,8 +852,14 @@ impl AsObject<User, Update, &DbConn> for PostUpdate {
|
|||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
||||||
let mut post =
|
let mut post = Post::from_id(
|
||||||
Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?;
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&self.ap_url,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.map_err(|(_, e)| e)?;
|
||||||
|
|
||||||
if !post.is_author(conn, actor.id)? {
|
if !post.is_author(conn, actor.id)? {
|
||||||
// TODO: maybe the author was added in the meantime
|
// TODO: maybe the author was added in the meantime
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db_conn::{DbConn, DbPool},
|
db_conn::{DbConn, DbPool},
|
||||||
follows,
|
follows,
|
||||||
|
instance::Instance,
|
||||||
posts::{LicensedArticle, Post},
|
posts::{LicensedArticle, Post},
|
||||||
users::{User, UserEvent},
|
users::{User, UserEvent},
|
||||||
ACTOR_SYS, CONFIG, USER_CHAN,
|
ACTOR_SYS, CONFIG, USER_CHAN,
|
||||||
@ -89,7 +90,13 @@ fn fetch_and_cache_followers(user: &Arc<User>, conn: &DbConn) {
|
|||||||
match follower_ids {
|
match follower_ids {
|
||||||
Ok(user_ids) => {
|
Ok(user_ids) => {
|
||||||
for user_id in user_ids {
|
for user_id in user_ids {
|
||||||
let follower = User::from_id(conn, &user_id, None, CONFIG.proxy());
|
let follower = User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&user_id,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
);
|
||||||
match follower {
|
match follower {
|
||||||
Ok(follower) => {
|
Ok(follower) => {
|
||||||
let inserted = follows::Follow::insert(
|
let inserted = follows::Follow::insert(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
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;
|
||||||
@ -142,6 +142,7 @@ impl FromId<DbConn> for Reshare {
|
|||||||
NewReshare {
|
NewReshare {
|
||||||
post_id: Post::from_id(
|
post_id: Post::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&act.announce_props.object_link::<Id>()?,
|
&act.announce_props.object_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@ -150,6 +151,7 @@ impl FromId<DbConn> for Reshare {
|
|||||||
.id,
|
.id,
|
||||||
user_id: User::from_id(
|
user_id: User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&act.announce_props.actor_link::<Id>()?,
|
&act.announce_props.actor_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
|
@ -106,6 +106,8 @@ table! {
|
|||||||
default_license -> Text,
|
default_license -> Text,
|
||||||
long_description_html -> Varchar,
|
long_description_html -> Varchar,
|
||||||
short_description_html -> Varchar,
|
short_description_html -> Varchar,
|
||||||
|
private_key -> Nullable<Text>,
|
||||||
|
public_key -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,6 +164,8 @@ mod tests {
|
|||||||
name: random_hex().to_string(),
|
name: random_hex().to_string(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: random_hex().to_string(),
|
public_domain: random_hex().to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -24,7 +24,7 @@ use plume_common::{
|
|||||||
activity_pub::{
|
activity_pub::{
|
||||||
ap_accept_header,
|
ap_accept_header,
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
sign::{gen_keypair, Signer},
|
sign::{gen_keypair, Error as SignatureError, Result as SignatureResult, Signer},
|
||||||
ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY,
|
ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
utils,
|
utils,
|
||||||
@ -210,7 +210,14 @@ impl User {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||||
.ok_or(Error::Webfinger)?;
|
.ok_or(Error::Webfinger)?;
|
||||||
User::from_id(conn, link.href.as_ref()?, None, CONFIG.proxy()).map_err(|(_, e)| e)
|
User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
link.href.as_ref()?,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.map_err(|(_, e)| e)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_remote_interact_uri(acct: &str) -> Result<String> {
|
pub fn fetch_remote_interact_uri(acct: &str) -> Result<String> {
|
||||||
@ -958,6 +965,8 @@ impl FromId<DbConn> for User {
|
|||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
short_description_html: String::new(),
|
short_description_html: String::new(),
|
||||||
long_description_html: String::new(),
|
long_description_html: String::new(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
@ -1063,24 +1072,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) -> SignatureResult<Vec<u8>> {
|
||||||
let key = self.get_keypair()?;
|
let key = self.get_keypair()?;
|
||||||
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(SignatureError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> {
|
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<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(SignatureError::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,83 +60,83 @@ msgstr ""
|
|||||||
msgid "Optional"
|
msgid "Optional"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/blogs.rs:63
|
# src/routes/blogs.rs:67
|
||||||
msgid "To create a new blog, you need to be logged in"
|
msgid "To create a new blog, you need to be logged in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/blogs.rs:102
|
# src/routes/blogs.rs:109
|
||||||
msgid "A blog with the same name already exists."
|
msgid "A blog with the same name already exists."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/blogs.rs:140
|
# src/routes/blogs.rs:147
|
||||||
msgid "Your blog was successfully created!"
|
msgid "Your blog was successfully created!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/blogs.rs:159
|
# src/routes/blogs.rs:165
|
||||||
msgid "Your blog was deleted."
|
msgid "Your blog was deleted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/blogs.rs:167
|
# src/routes/blogs.rs:173
|
||||||
msgid "You are not allowed to delete this blog."
|
msgid "You are not allowed to delete this blog."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/blogs.rs:218
|
# src/routes/blogs.rs:223
|
||||||
msgid "You are not allowed to edit this blog."
|
msgid "You are not allowed to edit this blog."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/blogs.rs:274
|
# src/routes/blogs.rs:279
|
||||||
msgid "You can't use this media as a blog icon."
|
msgid "You can't use this media as a blog icon."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/blogs.rs:292
|
# src/routes/blogs.rs:297
|
||||||
msgid "You can't use this media as a blog banner."
|
msgid "You can't use this media as a blog banner."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/blogs.rs:326
|
# src/routes/blogs.rs:331
|
||||||
msgid "Your blog information have been updated."
|
msgid "Your blog information have been updated."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/comments.rs:99
|
# src/routes/comments.rs:100
|
||||||
msgid "Your comment has been posted."
|
msgid "Your comment has been posted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/comments.rs:178
|
# src/routes/comments.rs:177
|
||||||
msgid "Your comment has been deleted."
|
msgid "Your comment has been deleted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/instance.rs:118
|
# src/routes/instance.rs:147
|
||||||
msgid "Instance settings have been saved."
|
msgid "Instance settings have been saved."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/instance.rs:150
|
# src/routes/instance.rs:180
|
||||||
msgid "{} has been unblocked."
|
msgid "{} has been unblocked."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/instance.rs:152
|
# src/routes/instance.rs:182
|
||||||
msgid "{} has been blocked."
|
msgid "{} has been blocked."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/instance.rs:201
|
# src/routes/instance.rs:233
|
||||||
msgid "Blocks deleted"
|
msgid "Blocks deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/instance.rs:216
|
# src/routes/instance.rs:249
|
||||||
msgid "Email already blocked"
|
msgid "Email already blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/instance.rs:221
|
# src/routes/instance.rs:254
|
||||||
msgid "Email Blocked"
|
msgid "Email Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/instance.rs:312
|
# src/routes/instance.rs:347
|
||||||
msgid "You can't change your own rights."
|
msgid "You can't change your own rights."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/instance.rs:323
|
# src/routes/instance.rs:358
|
||||||
msgid "You are not allowed to take this action."
|
msgid "You are not allowed to take this action."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/instance.rs:359
|
# src/routes/instance.rs:393
|
||||||
msgid "Done."
|
msgid "Done."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -144,23 +144,23 @@ msgstr ""
|
|||||||
msgid "To like a post, you need to be logged in"
|
msgid "To like a post, you need to be logged in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/medias.rs:145
|
# src/routes/medias.rs:158
|
||||||
msgid "Your media have been deleted."
|
msgid "Your media have been deleted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/medias.rs:150
|
# src/routes/medias.rs:163
|
||||||
msgid "You are not allowed to delete this media."
|
msgid "You are not allowed to delete this media."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/medias.rs:167
|
# src/routes/medias.rs:180
|
||||||
msgid "Your avatar has been updated."
|
msgid "Your avatar has been updated."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/medias.rs:172
|
# src/routes/medias.rs:185
|
||||||
msgid "You are not allowed to use this media."
|
msgid "You are not allowed to use this media."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/notifications.rs:28
|
# src/routes/notifications.rs:29
|
||||||
msgid "To see your notifications, you need to be logged in"
|
msgid "To see your notifications, you need to be logged in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -168,51 +168,51 @@ msgstr ""
|
|||||||
msgid "This post isn't published yet."
|
msgid "This post isn't published yet."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:126
|
# src/routes/posts.rs:125
|
||||||
msgid "To write a new post, you need to be logged in"
|
msgid "To write a new post, you need to be logged in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:143
|
# src/routes/posts.rs:146
|
||||||
msgid "You are not an author of this blog."
|
msgid "You are not an author of this blog."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:150
|
# src/routes/posts.rs:153
|
||||||
msgid "New post"
|
msgid "New post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:195
|
# src/routes/posts.rs:198
|
||||||
msgid "Edit {0}"
|
msgid "Edit {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:264
|
# src/routes/posts.rs:267
|
||||||
msgid "You are not allowed to publish on this blog."
|
msgid "You are not allowed to publish on this blog."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:363
|
# src/routes/posts.rs:367
|
||||||
msgid "Your article has been updated."
|
msgid "Your article has been updated."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:553
|
# src/routes/posts.rs:556
|
||||||
msgid "Your article has been saved."
|
msgid "Your article has been saved."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:560
|
# src/routes/posts.rs:563
|
||||||
msgid "New article"
|
msgid "New article"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:597
|
# src/routes/posts.rs:601
|
||||||
msgid "You are not allowed to delete this article."
|
msgid "You are not allowed to delete this article."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:622
|
# src/routes/posts.rs:625
|
||||||
msgid "Your article has been deleted."
|
msgid "Your article has been deleted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:627
|
# src/routes/posts.rs:630
|
||||||
msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?"
|
msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/posts.rs:667
|
# src/routes/posts.rs:672
|
||||||
msgid "Couldn't obtain enough information about your account. Please make sure your username is correct."
|
msgid "Couldn't obtain enough information about your account. Please make sure your username is correct."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -220,63 +220,63 @@ msgstr ""
|
|||||||
msgid "To reshare a post, you need to be logged in"
|
msgid "To reshare a post, you need to be logged in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/session.rs:88
|
# src/routes/session.rs:95
|
||||||
msgid "You are now connected."
|
msgid "You are now connected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/session.rs:109
|
# src/routes/session.rs:116
|
||||||
msgid "You are now logged off."
|
msgid "You are now logged off."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/session.rs:154
|
# src/routes/session.rs:162
|
||||||
msgid "Password reset"
|
msgid "Password reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/session.rs:155
|
# src/routes/session.rs:163
|
||||||
msgid "Here is the link to reset your password: {0}"
|
msgid "Here is the link to reset your password: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/session.rs:215
|
# src/routes/session.rs:235
|
||||||
msgid "Your password was successfully reset."
|
msgid "Your password was successfully reset."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/user.rs:142
|
# src/routes/user.rs:74
|
||||||
msgid "To access your dashboard, you need to be logged in"
|
msgid "To access your dashboard, you need to be logged in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/user.rs:164
|
# src/routes/user.rs:96
|
||||||
msgid "You are no longer following {}."
|
msgid "You are no longer following {}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/user.rs:181
|
# src/routes/user.rs:113
|
||||||
msgid "You are now following {}."
|
msgid "You are now following {}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/user.rs:261
|
# src/routes/user.rs:190
|
||||||
msgid "To subscribe to someone, you need to be logged in"
|
msgid "To subscribe to someone, you need to be logged in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/user.rs:365
|
# src/routes/user.rs:299
|
||||||
msgid "To edit your profile, you need to be logged in"
|
msgid "To edit your profile, you need to be logged in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/user.rs:411
|
# src/routes/user.rs:345
|
||||||
msgid "Your profile has been updated."
|
msgid "Your profile has been updated."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/user.rs:438
|
# src/routes/user.rs:373
|
||||||
msgid "Your account has been deleted."
|
msgid "Your account has been deleted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/user.rs:444
|
# src/routes/user.rs:379
|
||||||
msgid "You can't delete someone else's account."
|
msgid "You can't delete someone else's account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/user.rs:528
|
# src/routes/user.rs:463
|
||||||
msgid "Registrations are closed on this instance."
|
msgid "Registrations are closed on this instance."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# src/routes/user.rs:551
|
# src/routes/user.rs:486
|
||||||
msgid "Your account has been created. Now you just need to log in, before you can use it."
|
msgid "Your account has been created. Now you just need to log in, before you can use it."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
10
src/inbox.rs
10
src/inbox.rs
@ -26,8 +26,14 @@ pub fn handle_incoming(
|
|||||||
.or_else(|| activity["actor"]["id"].as_str())
|
.or_else(|| activity["actor"]["id"].as_str())
|
||||||
.ok_or(status::BadRequest(Some("Missing actor id for activity")))?;
|
.ok_or(status::BadRequest(Some("Missing actor id for activity")))?;
|
||||||
|
|
||||||
let actor = User::from_id(&conn, actor_id, None, CONFIG.proxy())
|
let actor = User::from_id(
|
||||||
.expect("instance::shared_inbox: user error");
|
&conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
actor_id,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.expect("instance::shared_inbox: user error");
|
||||||
if !verify_http_headers(&actor, &headers.0, &sig).is_secure() && !act.clone().verify(&actor) {
|
if !verify_http_headers(&actor, &headers.0, &sig).is_secure() && !act.clone().verify(&actor) {
|
||||||
// maybe we just know an old key?
|
// maybe we just know an old key?
|
||||||
actor
|
actor
|
||||||
|
@ -15,6 +15,7 @@ use diesel::r2d2::ConnectionManager;
|
|||||||
use plume_models::{
|
use plume_models::{
|
||||||
db_conn::{DbPool, PragmaForeignKey},
|
db_conn::{DbPool, PragmaForeignKey},
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
|
migrate_data,
|
||||||
migrations::IMPORTED_MIGRATIONS,
|
migrations::IMPORTED_MIGRATIONS,
|
||||||
remote_fetch_actor::RemoteFetchActor,
|
remote_fetch_actor::RemoteFetchActor,
|
||||||
search::{actor::SearchActor, Searcher as UnmanagedSearcher},
|
search::{actor::SearchActor, Searcher as UnmanagedSearcher},
|
||||||
@ -99,6 +100,7 @@ Then try to restart Plume.
|
|||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
migrate_data(&dbpool).expect("Failed to migrate data");
|
||||||
let workpool = ScheduledThreadPool::with_name("worker {}", num_cpus::get());
|
let workpool = ScheduledThreadPool::with_name("worker {}", num_cpus::get());
|
||||||
// we want a fast exit here, so
|
// we want a fast exit here, so
|
||||||
let searcher = Arc::new(UnmanagedSearcher::open_or_recreate(
|
let searcher = Arc::new(UnmanagedSearcher::open_or_recreate(
|
||||||
@ -147,6 +149,7 @@ Then try to restart Plume.
|
|||||||
routes::comments::delete,
|
routes::comments::delete,
|
||||||
routes::comments::activity_pub,
|
routes::comments::activity_pub,
|
||||||
routes::instance::index,
|
routes::instance::index,
|
||||||
|
routes::instance::activity_details,
|
||||||
routes::instance::admin,
|
routes::instance::admin,
|
||||||
routes::instance::admin_mod,
|
routes::instance::admin_mod,
|
||||||
routes::instance::admin_instances,
|
routes::instance::admin_instances,
|
||||||
|
@ -438,6 +438,8 @@ mod tests {
|
|||||||
name: random_hex().to_string(),
|
name: random_hex().to_string(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: random_hex().to_string(),
|
public_domain: random_hex().to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -11,7 +11,7 @@ use validator::{Validate, ValidationErrors};
|
|||||||
use crate::inbox;
|
use crate::inbox;
|
||||||
use crate::routes::{errors::ErrorPage, rocket_uri_macro_static_files, Page, RespondOrRedirect};
|
use crate::routes::{errors::ErrorPage, rocket_uri_macro_static_files, Page, RespondOrRedirect};
|
||||||
use crate::template_utils::{IntoContext, Ructe};
|
use crate::template_utils::{IntoContext, Ructe};
|
||||||
use plume_common::activity_pub::{broadcast, inbox::FromId};
|
use plume_common::activity_pub::{broadcast, inbox::FromId, ActivityStream, ApRequest};
|
||||||
use plume_models::{
|
use plume_models::{
|
||||||
admin::*,
|
admin::*,
|
||||||
blocklisted_emails::*,
|
blocklisted_emails::*,
|
||||||
@ -49,6 +49,36 @@ pub fn index(conn: DbConn, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Experimental
|
||||||
|
/// "!" might be change in the future
|
||||||
|
///
|
||||||
|
/// This exists for communication with Mastodon.
|
||||||
|
/// Secure mode Mastodon requires HTTP signature for event GET requests.
|
||||||
|
/// To sign requests, public key of request sender is required. We decided
|
||||||
|
/// the sender is local instance.
|
||||||
|
#[get("/!/<public_domain>")]
|
||||||
|
pub fn activity_details(
|
||||||
|
public_domain: String,
|
||||||
|
conn: DbConn,
|
||||||
|
_ap: ApRequest,
|
||||||
|
) -> Option<ActivityStream<CustomService>> {
|
||||||
|
if let Ok(instance) = Instance::find_by_domain(&conn, &public_domain) {
|
||||||
|
if !instance.local {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match instance.to_activity() {
|
||||||
|
Ok(activity) => Some(ActivityStream::new(activity)),
|
||||||
|
Err(plume_models::Error::NotFound) => None,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("{:?}", err);
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/admin")]
|
#[get("/admin")]
|
||||||
pub fn admin(_admin: Admin, conn: DbConn, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
pub fn admin(_admin: Admin, conn: DbConn, rockets: PlumeRocket) -> Result<Ructe, ErrorPage> {
|
||||||
let local_inst = Instance::get_local()?;
|
let local_inst = Instance::get_local()?;
|
||||||
@ -404,7 +434,13 @@ pub fn interact(conn: DbConn, user: Option<User>, target: String) -> Option<Redi
|
|||||||
return Some(Redirect::to(uri!(super::user::details: name = target)));
|
return Some(Redirect::to(uri!(super::user::details: name = target)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(post) = Post::from_id(&conn, &target, None, CONFIG.proxy()) {
|
if let Ok(post) = Post::from_id(
|
||||||
|
&conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&target,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
) {
|
||||||
return Some(Redirect::to(uri!(
|
return Some(Redirect::to(uri!(
|
||||||
super::posts::details: blog = post.get_blog(&conn).expect("Can't retrieve blog").fqn,
|
super::posts::details: blog = post.get_blog(&conn).expect("Can't retrieve blog").fqn,
|
||||||
slug = &post.slug,
|
slug = &post.slug,
|
||||||
@ -412,7 +448,13 @@ pub fn interact(conn: DbConn, user: Option<User>, target: String) -> Option<Redi
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(comment) = Comment::from_id(&conn, &target, None, CONFIG.proxy()) {
|
if let Ok(comment) = Comment::from_id(
|
||||||
|
&conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&target,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
) {
|
||||||
if comment.can_see(&conn, user.as_ref()) {
|
if comment.can_see(&conn, user.as_ref()) {
|
||||||
let post = comment.get_post(&conn).expect("Can't retrieve post");
|
let post = comment.get_post(&conn).expect("Can't retrieve post");
|
||||||
return Some(Redirect::to(uri!(
|
return Some(Redirect::to(uri!(
|
||||||
|
Loading…
Reference in New Issue
Block a user