Follow 'works'

This commit is contained in:
asonix 2020-03-15 12:49:27 -05:00
parent 6cb4484a33
commit aa6b5daaf0
12 changed files with 188 additions and 52 deletions

4
Cargo.lock generated
View file

@ -2,9 +2,9 @@
# It is not intended for manual editing. # It is not intended for manual editing.
[[package]] [[package]]
name = "activitystreams" name = "activitystreams"
version = "0.5.0-alpha.5" version = "0.5.0-alpha.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cb2b3d9e17c12a21669b061015ca54c777ace07f824b4f275ef3c3a9c736d9e" checksum = "9057510df06a864f3a4da22c393373ae13850f001655a102acb4a8ada0895d9d"
dependencies = [ dependencies = [
"activitystreams-derive", "activitystreams-derive",
"chrono", "chrono",

View file

@ -11,7 +11,7 @@ anyhow = "1.0"
actix = "0.10.0-alpha.2" actix = "0.10.0-alpha.2"
actix-web = { version = "3.0.0-alpha.1", features = ["openssl"] } actix-web = { version = "3.0.0-alpha.1", features = ["openssl"] }
actix-rt = "1.0.0" actix-rt = "1.0.0"
activitystreams = "0.5.0-alpha.5" activitystreams = "0.5.0-alpha.6"
bb8-postgres = "0.4.0" bb8-postgres = "0.4.0"
dotenv = "0.15.0" dotenv = "0.15.0"
futures = "0.3.4" futures = "0.3.4"

View file

@ -1,3 +1,3 @@
-- This file should undo anything in `up.sql` -- This file should undo anything in `up.sql`
DROP INDEX blocks_actor_id_index; DROP INDEX blocks_domain_name_index;
DROP TABLE blocks; DROP TABLE blocks;

View file

@ -1,11 +1,11 @@
-- Your SQL goes here -- Your SQL goes here
CREATE TABLE blocks ( CREATE TABLE blocks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor_id TEXT UNIQUE NOT NULL, domain_name TEXT UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL updated_at TIMESTAMP NOT NULL
); );
CREATE INDEX blocks_actor_id_index ON blocks(actor_id); CREATE INDEX blocks_domain_name_index ON blocks(domain_name);
SELECT diesel_manage_updated_at('blocks'); SELECT diesel_manage_updated_at('blocks');

View file

@ -1,3 +1,3 @@
-- This file should undo anything in `up.sql` -- This file should undo anything in `up.sql`
DROP INDEX whitelists_actor_id_index; DROP INDEX whitelists_domain_name_index;
DROP TABLE whitelists; DROP TABLE whitelists;

View file

@ -1,11 +1,11 @@
-- Your SQL goes here -- Your SQL goes here
CREATE TABLE whitelists ( CREATE TABLE whitelists (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor_id TEXT UNIQUE NOT NULL, domain_name TEXT UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL updated_at TIMESTAMP NOT NULL
); );
CREATE INDEX whitelists_actor_id_index ON whitelists(actor_id); CREATE INDEX whitelists_domain_name_index ON whitelists(domain_name);
SELECT diesel_manage_updated_at('whitelists'); SELECT diesel_manage_updated_at('whitelists');

View file

@ -24,12 +24,6 @@ pub enum ValidTypes {
Undo, Undo,
} }
impl Default for ValidTypes {
fn default() -> Self {
ValidTypes::Create
}
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(untagged)] #[serde(untagged)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -38,13 +32,7 @@ pub enum ValidObjects {
Object(AnyExistingObject), Object(AnyExistingObject),
} }
impl Default for ValidObjects { #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
fn default() -> Self {
ValidObjects::Id(Default::default())
}
}
#[derive(Clone, Default, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AcceptedObjects { pub struct AcceptedObjects {
pub id: XsdAnyUri, pub id: XsdAnyUri,
@ -84,3 +72,9 @@ impl ValidObjects {
} }
} }
} }
impl AcceptedActors {
pub fn inbox(&self) -> &XsdAnyUri {
self.endpoints.shared_inbox.as_ref().unwrap_or(&self.inbox)
}
}

View file

@ -76,7 +76,7 @@ impl Supervised for DbActor {}
impl<F, Fut, R> Handler<DbQuery<F>> for DbActor impl<F, Fut, R> Handler<DbQuery<F>> for DbActor
where where
F: Fn(Pool) -> Fut + 'static, F: FnOnce(Pool) -> Fut + 'static,
Fut: Future<Output = R>, Fut: Future<Output = R>,
R: Send + 'static, R: Send + 'static,
{ {
@ -107,7 +107,7 @@ where
impl<F, Fut, R> Message for DbQuery<F> impl<F, Fut, R> Message for DbQuery<F>
where where
F: Fn(Pool) -> Fut, F: FnOnce(Pool) -> Fut,
Fut: Future<Output = R>, Fut: Future<Output = R>,
R: Send + 'static, R: Send + 'static,
{ {

View file

@ -1,11 +1,15 @@
use activitystreams::primitives::XsdAnyUri; use activitystreams::{
activity::apub::{Accept, Follow},
primitives::XsdAnyUri,
};
use actix::Addr; use actix::Addr;
use actix_web::{client::Client, web, Responder}; use actix_web::{client::Client, web, Responder};
use log::info; use futures::join;
use log::{error, info};
use crate::{ use crate::{
apub::{AcceptedActors, AcceptedObjects, ValidTypes}, apub::{AcceptedActors, AcceptedObjects, ValidTypes},
db_actor::DbActor, db_actor::{DbActor, DbQuery, Pool},
state::State, state::State,
}; };
@ -21,19 +25,75 @@ pub async fn inbox(
) -> Result<impl Responder, MyError> { ) -> Result<impl Responder, MyError> {
let input = input.into_inner(); let input = input.into_inner();
info!("Relaying {} for {}", input.object.id(), input.actor); let actor = fetch_actor(state.clone(), client, &input.actor).await?;
let actor = fetch_actor(state, client, &input.actor).await?;
info!("Actor, {:#?}", actor);
match input.kind { match input.kind {
ValidTypes::Announce => (), ValidTypes::Announce => (),
ValidTypes::Create => (), ValidTypes::Create => (),
ValidTypes::Delete => (), ValidTypes::Delete => (),
ValidTypes::Follow => (), ValidTypes::Follow => return handle_follow(db_actor, state, input, actor).await,
ValidTypes::Undo => (), ValidTypes::Undo => (),
} }
Ok("{}") Err(MyError)
}
async fn handle_follow(
db_actor: web::Data<Addr<DbActor>>,
state: web::Data<State>,
input: AcceptedObjects,
actor: AcceptedActors,
) -> Result<web::Json<Accept>, MyError> {
let (is_listener, is_blocked, is_whitelisted) = join!(
state.is_listener(&actor.id),
state.is_blocked(&actor.id),
state.is_whitelisted(&actor.id)
);
if is_blocked {
error!("Follow from blocked listener, {}", actor.id);
return Err(MyError);
}
if !is_whitelisted {
error!("Follow from non-whitelisted listener, {}", actor.id);
return Err(MyError);
}
if !is_listener {
let state = state.into_inner();
let actor = actor.clone();
db_actor.do_send(DbQuery(move |pool: Pool| {
let actor_id = actor.id.clone();
let state = state.clone();
async move {
let conn = pool.get().await?;
state.add_listener(&conn, actor_id).await
}
}));
}
let mut accept = Accept::default();
let mut follow = Follow::default();
follow.object_props.set_id(input.id)?;
follow
.follow_props
.set_object_xsd_any_uri(format!("https://{}/actor", "localhost"))?
.set_actor_xsd_any_uri(actor.id.clone())?;
accept
.object_props
.set_id(format!("https://{}/activities/{}", "localhost", "1"))?
.set_many_to_xsd_any_uris(vec![actor.id])?;
accept
.accept_props
.set_object_object_box(follow)?
.set_actor_xsd_any_uri(format!("https://{}/actor", "localhost"))?;
Ok(web::Json(accept))
} }
async fn fetch_actor( async fn fetch_actor(
@ -46,14 +106,20 @@ async fn fetch_actor(
} }
let actor: AcceptedActors = client let actor: AcceptedActors = client
.get(actor_id.as_ref()) .get(actor_id.as_str())
.header("Accept", "application/activity+json") .header("Accept", "application/activity+json")
.send() .send()
.await .await
.map_err(|_| MyError)? .map_err(|e| {
error!("Couldn't send request for actor, {}", e);
MyError
})?
.json() .json()
.await .await
.map_err(|_| MyError)?; .map_err(|e| {
error!("Coudn't fetch actor, {}", e);
MyError
})?;
state.cache_actor(actor_id.to_owned(), actor.clone()).await; state.cache_actor(actor_id.to_owned(), actor.clone()).await;
@ -61,3 +127,23 @@ async fn fetch_actor(
} }
impl actix_web::error::ResponseError for MyError {} impl actix_web::error::ResponseError for MyError {}
impl From<std::convert::Infallible> for MyError {
fn from(_: std::convert::Infallible) -> Self {
MyError
}
}
impl From<activitystreams::primitives::XsdAnyUriError> for MyError {
fn from(_: activitystreams::primitives::XsdAnyUriError) -> Self {
error!("Error parsing URI");
MyError
}
}
impl From<std::io::Error> for MyError {
fn from(e: std::io::Error) -> Self {
error!("JSON Error, {}", e);
MyError
}
}

View file

@ -27,7 +27,7 @@ async fn main() -> Result<(), anyhow::Error> {
arbiter_labeler.clone().set_label(); arbiter_labeler.clone().set_label();
let state: State = db_actor let state: State = db_actor
.send(db_actor::DbQuery(State::hydrate)) .send(db_actor::DbQuery(|pool| State::hydrate(false, pool)))
.await? .await?
.await??; .await??;

View file

@ -1,7 +1,7 @@
table! { table! {
blocks (id) { blocks (id) {
id -> Uuid, id -> Uuid,
actor_id -> Text, domain_name -> Text,
created_at -> Timestamp, created_at -> Timestamp,
updated_at -> Timestamp, updated_at -> Timestamp,
} }
@ -19,7 +19,7 @@ table! {
table! { table! {
whitelists (id) { whitelists (id) {
id -> Uuid, id -> Uuid,
actor_id -> Text, domain_name -> Text,
created_at -> Timestamp, created_at -> Timestamp,
updated_at -> Timestamp, updated_at -> Timestamp,
} }

View file

@ -11,14 +11,52 @@ use crate::{apub::AcceptedActors, db_actor::Pool};
#[derive(Clone)] #[derive(Clone)]
pub struct State { pub struct State {
whitelist_enabled: bool,
actor_cache: Arc<RwLock<TtlCache<XsdAnyUri, AcceptedActors>>>, actor_cache: Arc<RwLock<TtlCache<XsdAnyUri, AcceptedActors>>>,
actor_id_cache: Arc<RwLock<LruCache<XsdAnyUri, XsdAnyUri>>>, actor_id_cache: Arc<RwLock<LruCache<XsdAnyUri, XsdAnyUri>>>,
blocks: Arc<RwLock<HashSet<XsdAnyUri>>>, blocks: Arc<RwLock<HashSet<String>>>,
whitelists: Arc<RwLock<HashSet<XsdAnyUri>>>, whitelists: Arc<RwLock<HashSet<String>>>,
listeners: Arc<RwLock<HashSet<XsdAnyUri>>>, listeners: Arc<RwLock<HashSet<XsdAnyUri>>>,
} }
#[derive(Clone, Debug, thiserror::Error)]
#[error("No host present in URI")]
pub struct HostError;
impl State { impl State {
pub async fn is_whitelisted(&self, actor_id: &XsdAnyUri) -> bool {
if !self.whitelist_enabled {
return true;
}
let hs = self.whitelists.clone();
if let Some(host) = actor_id.as_url().host() {
let read_guard = hs.read().await;
return read_guard.contains(&host.to_string());
}
false
}
pub async fn is_blocked(&self, actor_id: &XsdAnyUri) -> bool {
let hs = self.blocks.clone();
if let Some(host) = actor_id.as_url().host() {
let read_guard = hs.read().await;
return read_guard.contains(&host.to_string());
}
true
}
pub async fn is_listener(&self, actor_id: &XsdAnyUri) -> bool {
let hs = self.listeners.clone();
let read_guard = hs.read().await;
read_guard.contains(actor_id)
}
pub async fn get_actor(&self, actor_id: &XsdAnyUri) -> Option<AcceptedActors> { pub async fn get_actor(&self, actor_id: &XsdAnyUri) -> Option<AcceptedActors> {
let cache = self.actor_cache.clone(); let cache = self.actor_cache.clone();
@ -50,15 +88,21 @@ impl State {
pub async fn add_block(&self, client: &Client, block: XsdAnyUri) -> Result<(), Error> { pub async fn add_block(&self, client: &Client, block: XsdAnyUri) -> Result<(), Error> {
let blocks = self.blocks.clone(); let blocks = self.blocks.clone();
let host = if let Some(host) = block.as_url().host() {
host
} else {
return Err(HostError.into());
};
client client
.execute( .execute(
"INSERT INTO blocks (actor_id, created_at) VALUES ($1::TEXT, now);", "INSERT INTO blocks (actor_id, created_at) VALUES ($1::TEXT, now);",
&[&block.as_ref()], &[&host.to_string()],
) )
.await?; .await?;
let mut write_guard = blocks.write().await; let mut write_guard = blocks.write().await;
write_guard.insert(block); write_guard.insert(host.to_string());
Ok(()) Ok(())
} }
@ -66,15 +110,21 @@ impl State {
pub async fn add_whitelist(&self, client: &Client, whitelist: XsdAnyUri) -> Result<(), Error> { pub async fn add_whitelist(&self, client: &Client, whitelist: XsdAnyUri) -> Result<(), Error> {
let whitelists = self.whitelists.clone(); let whitelists = self.whitelists.clone();
let host = if let Some(host) = whitelist.as_url().host() {
host
} else {
return Err(HostError.into());
};
client client
.execute( .execute(
"INSERT INTO whitelists (actor_id, created_at) VALUES ($1::TEXT, now);", "INSERT INTO whitelists (actor_id, created_at) VALUES ($1::TEXT, now);",
&[&whitelist.as_ref()], &[&host.to_string()],
) )
.await?; .await?;
let mut write_guard = whitelists.write().await; let mut write_guard = whitelists.write().await;
write_guard.insert(whitelist); write_guard.insert(host.to_string());
Ok(()) Ok(())
} }
@ -85,7 +135,7 @@ impl State {
client client
.execute( .execute(
"INSERT INTO listeners (actor_id, created_at) VALUES ($1::TEXT, now);", "INSERT INTO listeners (actor_id, created_at) VALUES ($1::TEXT, now);",
&[&listener.as_ref()], &[&listener.as_str()],
) )
.await?; .await?;
@ -95,7 +145,7 @@ impl State {
Ok(()) Ok(())
} }
pub async fn hydrate(pool: Pool) -> Result<Self, Error> { pub async fn hydrate(whitelist_enabled: bool, pool: Pool) -> Result<Self, Error> {
let pool1 = pool.clone(); let pool1 = pool.clone();
let pool2 = pool.clone(); let pool2 = pool.clone();
@ -120,6 +170,7 @@ impl State {
let (blocks, whitelists, listeners) = try_join!(f1, f2, f3)?; let (blocks, whitelists, listeners) = try_join!(f1, f2, f3)?;
Ok(State { Ok(State {
whitelist_enabled,
actor_cache: Arc::new(RwLock::new(TtlCache::new(1024 * 8))), actor_cache: Arc::new(RwLock::new(TtlCache::new(1024 * 8))),
actor_id_cache: Arc::new(RwLock::new(LruCache::new(1024 * 8))), actor_id_cache: Arc::new(RwLock::new(LruCache::new(1024 * 8))),
blocks: Arc::new(RwLock::new(blocks)), blocks: Arc::new(RwLock::new(blocks)),
@ -129,14 +180,16 @@ impl State {
} }
} }
pub async fn hydrate_blocks(client: &Client) -> Result<HashSet<XsdAnyUri>, Error> { pub async fn hydrate_blocks(client: &Client) -> Result<HashSet<String>, Error> {
let rows = client.query("SELECT actor_id FROM blocks", &[]).await?; let rows = client.query("SELECT domain_name FROM blocks", &[]).await?;
parse_rows(rows) parse_rows(rows)
} }
pub async fn hydrate_whitelists(client: &Client) -> Result<HashSet<XsdAnyUri>, Error> { pub async fn hydrate_whitelists(client: &Client) -> Result<HashSet<String>, Error> {
let rows = client.query("SELECT actor_id FROM whitelists", &[]).await?; let rows = client
.query("SELECT domain_name FROM whitelists", &[])
.await?;
parse_rows(rows) parse_rows(rows)
} }
@ -147,11 +200,14 @@ pub async fn hydrate_listeners(client: &Client) -> Result<HashSet<XsdAnyUri>, Er
parse_rows(rows) parse_rows(rows)
} }
pub fn parse_rows(rows: Vec<Row>) -> Result<HashSet<XsdAnyUri>, Error> { pub fn parse_rows<T>(rows: Vec<Row>) -> Result<HashSet<T>, Error>
where
T: std::str::FromStr + Eq + std::hash::Hash,
{
let hs = rows let hs = rows
.into_iter() .into_iter()
.filter_map(move |row| { .filter_map(move |row| {
let s: String = row.try_get("actor_id").ok()?; let s: String = row.try_get(0).ok()?;
s.parse().ok() s.parse().ok()
}) })
.collect(); .collect();