2018-06-21 22:39:44 +02:00
use activitypub ::{ Actor , Object , CustomObject , actor ::Group , collection ::OrderedCollection } ;
2018-05-19 09:39:59 +02:00
use reqwest ::{
Client ,
header ::{ Accept , qitem } ,
mime ::Mime
} ;
2018-05-13 19:00:47 +02:00
use serde_json ;
use url ::Url ;
2018-04-30 19:46:27 +02:00
use chrono ::NaiveDateTime ;
2018-06-10 19:55:08 +02:00
use diesel ::{ self , QueryDsl , RunQueryDsl , ExpressionMethods , PgConnection , dsl ::any } ;
2018-05-19 09:39:59 +02:00
use openssl ::{
hash ::MessageDigest ,
pkey ::{ PKey , Private } ,
rsa ::Rsa ,
sign ::Signer
} ;
2018-06-18 23:50:40 +02:00
use webfinger ::* ;
2018-04-24 11:21:39 +02:00
2018-06-26 16:16:59 +02:00
use { BASE_URL , USE_HTTPS } ;
2018-06-23 18:36:11 +02:00
use plume_common ::activity_pub ::{
2018-07-18 16:58:28 +02:00
ap_accept_header , ApSignature , ActivityStream , Id , IntoId , PublicKey ,
2018-05-19 09:39:59 +02:00
inbox ::WithInbox ,
2018-06-18 23:50:40 +02:00
sign
2018-05-19 09:39:59 +02:00
} ;
2018-06-23 18:36:11 +02:00
use instance ::* ;
2018-07-18 23:08:49 +02:00
use users ::User ;
2018-04-24 11:21:39 +02:00
use schema ::blogs ;
2018-06-21 22:39:44 +02:00
pub type CustomGroup = CustomObject < ApSignature , Group > ;
2018-04-23 12:29:27 +02:00
2018-05-18 10:04:40 +02:00
#[ derive(Queryable, Identifiable, Serialize, Deserialize, Clone) ]
2018-04-23 12:29:27 +02:00
pub struct Blog {
pub id : i32 ,
pub actor_id : String ,
pub title : String ,
pub summary : String ,
pub outbox_url : String ,
pub inbox_url : String ,
2018-04-30 19:46:27 +02:00
pub instance_id : i32 ,
2018-05-01 20:02:29 +02:00
pub creation_date : NaiveDateTime ,
2018-05-03 21:11:04 +02:00
pub ap_url : String ,
pub private_key : Option < String > ,
pub public_key : String
2018-04-23 12:29:27 +02:00
}
#[ derive(Insertable) ]
#[ table_name = " blogs " ]
pub struct NewBlog {
pub actor_id : String ,
pub title : String ,
pub summary : String ,
pub outbox_url : String ,
pub inbox_url : String ,
2018-05-01 20:02:29 +02:00
pub instance_id : i32 ,
2018-05-03 21:11:04 +02:00
pub ap_url : String ,
pub private_key : Option < String > ,
pub public_key : String
2018-04-23 12:29:27 +02:00
}
2018-06-21 19:42:17 +02:00
const BLOG_PREFIX : & 'static str = " ~ " ;
2018-04-23 12:29:27 +02:00
impl Blog {
2018-06-18 15:57:38 +02:00
insert! ( blogs , NewBlog ) ;
2018-06-18 15:44:23 +02:00
get! ( blogs ) ;
2018-06-21 23:07:04 +02:00
find_by! ( blogs , find_by_ap_url , ap_url as String ) ;
find_by! ( blogs , find_by_name , actor_id as String , instance_id as i32 ) ;
2018-04-23 12:29:27 +02:00
2018-06-21 19:53:57 +02:00
pub fn get_instance ( & self , conn : & PgConnection ) -> Instance {
Instance ::get ( conn , self . instance_id ) . expect ( " Couldn't find instance " )
}
2018-07-18 23:08:49 +02:00
pub fn list_authors ( & self , conn : & PgConnection ) -> Vec < User > {
use schema ::blog_authors ;
use schema ::users ;
let authors_ids = blog_authors ::table . filter ( blog_authors ::blog_id . eq ( self . id ) ) . select ( blog_authors ::author_id ) ;
users ::table . filter ( users ::id . eq ( any ( authors_ids ) ) )
. load ::< User > ( conn )
. expect ( " Couldn't load authors of a blog " )
}
2018-06-10 19:55:08 +02:00
pub fn find_for_author ( conn : & PgConnection , author_id : i32 ) -> Vec < Blog > {
use schema ::blog_authors ;
let author_ids = blog_authors ::table . filter ( blog_authors ::author_id . eq ( author_id ) ) . select ( blog_authors ::blog_id ) ;
blogs ::table . filter ( blogs ::id . eq ( any ( author_ids ) ) )
. load ::< Blog > ( conn )
. expect ( " Couldn't load blogs " )
}
2018-05-13 19:00:47 +02:00
pub fn find_local ( conn : & PgConnection , name : String ) -> Option < Blog > {
Blog ::find_by_name ( conn , name , Instance ::local_id ( conn ) )
}
pub fn find_by_fqn ( conn : & PgConnection , fqn : String ) -> Option < Blog > {
if fqn . contains ( " @ " ) { // remote blog
match Instance ::find_by_domain ( conn , String ::from ( fqn . split ( " @ " ) . last ( ) . unwrap ( ) ) ) {
Some ( instance ) = > {
match Blog ::find_by_name ( conn , String ::from ( fqn . split ( " @ " ) . nth ( 0 ) . unwrap ( ) ) , instance . id ) {
Some ( u ) = > Some ( u ) ,
None = > Blog ::fetch_from_webfinger ( conn , fqn )
}
} ,
None = > Blog ::fetch_from_webfinger ( conn , fqn )
}
} else { // local blog
Blog ::find_local ( conn , fqn )
}
}
fn fetch_from_webfinger ( conn : & PgConnection , acct : String ) -> Option < Blog > {
2018-06-26 16:16:59 +02:00
match resolve ( acct . clone ( ) , * USE_HTTPS ) {
2018-07-26 21:35:35 +02:00
Ok ( wf ) = > wf . links . into_iter ( ) . find ( | l | l . mime_type = = Some ( String ::from ( " application/activity+json " ) ) ) . and_then ( | l | Blog ::fetch_from_url ( conn , l . href . expect ( " No href for AP WF link " ) ) ) ,
2018-05-13 19:00:47 +02:00
Err ( details ) = > {
2018-06-18 23:50:40 +02:00
println! ( " {:?} " , details ) ;
2018-05-13 19:00:47 +02:00
None
}
}
}
fn fetch_from_url ( conn : & PgConnection , url : String ) -> Option < Blog > {
let req = Client ::new ( )
. get ( & url [ .. ] )
2018-07-18 16:58:28 +02:00
. header ( Accept ( ap_accept_header ( ) . into_iter ( ) . map ( | h | qitem ( h . parse ::< Mime > ( ) . expect ( " Invalid Content-Type " ) ) ) . collect ( ) ) )
2018-05-13 19:00:47 +02:00
. send ( ) ;
match req {
Ok ( mut res ) = > {
2018-06-23 16:00:35 +02:00
let text = & res . text ( ) . unwrap ( ) ;
let ap_sign : ApSignature = serde_json ::from_str ( text ) . unwrap ( ) ;
let mut json : CustomGroup = serde_json ::from_str ( text ) . unwrap ( ) ;
json . custom_props = ap_sign ; // without this workaround, publicKey is not correctly deserialized
2018-05-13 19:00:47 +02:00
Some ( Blog ::from_activity ( conn , json , Url ::parse ( url . as_ref ( ) ) . unwrap ( ) . host_str ( ) . unwrap ( ) . to_string ( ) ) )
} ,
Err ( _ ) = > None
}
}
2018-06-21 22:39:44 +02:00
fn from_activity ( conn : & PgConnection , acct : CustomGroup , inst : String ) -> Blog {
2018-05-13 19:00:47 +02:00
let instance = match Instance ::find_by_domain ( conn , inst . clone ( ) ) {
Some ( instance ) = > instance ,
None = > {
2018-06-18 15:57:38 +02:00
Instance ::insert ( conn , NewInstance {
public_domain : inst . clone ( ) ,
name : inst . clone ( ) ,
2018-07-27 19:05:36 +02:00
local : false ,
// We don't really care about all the following for remote instances
long_description : String ::new ( ) ,
short_description : String ::new ( ) ,
default_license : String ::new ( ) ,
2018-07-27 22:16:17 +02:00
open_registrations : true ,
short_description_html : String ::new ( ) ,
long_description_html : String ::new ( )
2018-06-18 15:57:38 +02:00
} )
2018-05-13 19:00:47 +02:00
}
} ;
Blog ::insert ( conn , NewBlog {
2018-06-21 22:39:44 +02:00
actor_id : acct . object . ap_actor_props . preferred_username_string ( ) . expect ( " Blog::from_activity: preferredUsername error " ) ,
title : acct . object . object_props . name_string ( ) . expect ( " Blog::from_activity: name error " ) ,
outbox_url : acct . object . ap_actor_props . outbox_string ( ) . expect ( " Blog::from_activity: outbox error " ) ,
inbox_url : acct . object . ap_actor_props . inbox_string ( ) . expect ( " Blog::from_activity: inbox error " ) ,
summary : acct . object . object_props . summary_string ( ) . expect ( " Blog::from_activity: summary error " ) ,
2018-05-13 19:00:47 +02:00
instance_id : instance . id ,
2018-06-21 22:39:44 +02:00
ap_url : acct . object . object_props . id_string ( ) . expect ( " Blog::from_activity: id error " ) ,
public_key : acct . custom_props . public_key_publickey ( ) . expect ( " Blog::from_activity: publicKey error " )
. public_key_pem_string ( ) . expect ( " Blog::from_activity: publicKey.publicKeyPem error " ) ,
2018-05-13 19:00:47 +02:00
private_key : None
} )
}
2018-06-21 23:07:04 +02:00
pub fn into_activity ( & self , _conn : & PgConnection ) -> CustomGroup {
let mut blog = Group ::default ( ) ;
blog . ap_actor_props . set_preferred_username_string ( self . actor_id . clone ( ) ) . expect ( " Blog::into_activity: preferredUsername error " ) ;
blog . object_props . set_name_string ( self . title . clone ( ) ) . expect ( " Blog::into_activity: name error " ) ;
blog . ap_actor_props . set_outbox_string ( self . outbox_url . clone ( ) ) . expect ( " Blog::into_activity: outbox error " ) ;
blog . ap_actor_props . set_inbox_string ( self . inbox_url . clone ( ) ) . expect ( " Blog::into_activity: inbox error " ) ;
blog . object_props . set_summary_string ( self . summary . clone ( ) ) . expect ( " Blog::into_activity: summary error " ) ;
blog . object_props . set_id_string ( self . ap_url . clone ( ) ) . expect ( " Blog::into_activity: id error " ) ;
let mut public_key = PublicKey ::default ( ) ;
public_key . set_id_string ( format! ( " {} #main-key " , self . ap_url ) ) . expect ( " Blog::into_activity: publicKey.id error " ) ;
public_key . set_owner_string ( self . ap_url . clone ( ) ) . expect ( " Blog::into_activity: publicKey.owner error " ) ;
public_key . set_public_key_pem_string ( self . public_key . clone ( ) ) . expect ( " Blog::into_activity: publicKey.publicKeyPem error " ) ;
let mut ap_signature = ApSignature ::default ( ) ;
ap_signature . set_public_key_publickey ( public_key ) . expect ( " Blog::into_activity: publicKey error " ) ;
CustomGroup ::new ( blog , ap_signature )
2018-06-21 19:09:18 +02:00
}
2018-04-23 15:12:59 +02:00
pub fn update_boxes ( & self , conn : & PgConnection ) {
2018-06-21 19:42:17 +02:00
let instance = self . get_instance ( conn ) ;
2018-04-23 15:12:59 +02:00
if self . outbox_url . len ( ) = = 0 {
diesel ::update ( self )
2018-06-21 19:42:17 +02:00
. set ( blogs ::outbox_url . eq ( instance . compute_box ( BLOG_PREFIX , self . actor_id . clone ( ) , " outbox " ) ) )
2018-04-23 15:12:59 +02:00
. get_result ::< Blog > ( conn ) . expect ( " Couldn't update outbox URL " ) ;
}
if self . inbox_url . len ( ) = = 0 {
diesel ::update ( self )
2018-06-21 19:42:17 +02:00
. set ( blogs ::inbox_url . eq ( instance . compute_box ( BLOG_PREFIX , self . actor_id . clone ( ) , " inbox " ) ) )
2018-04-23 15:12:59 +02:00
. get_result ::< Blog > ( conn ) . expect ( " Couldn't update inbox URL " ) ;
}
2018-05-01 20:02:29 +02:00
if self . ap_url . len ( ) = = 0 {
diesel ::update ( self )
2018-06-21 19:42:17 +02:00
. set ( blogs ::ap_url . eq ( instance . compute_box ( BLOG_PREFIX , self . actor_id . clone ( ) , " " ) ) )
2018-05-01 20:02:29 +02:00
. get_result ::< Blog > ( conn ) . expect ( " Couldn't update AP URL " ) ;
}
2018-04-23 15:12:59 +02:00
}
2018-04-29 19:49:56 +02:00
2018-05-16 20:20:44 +02:00
pub fn outbox ( & self , conn : & PgConnection ) -> ActivityStream < OrderedCollection > {
let mut coll = OrderedCollection ::default ( ) ;
coll . collection_props . items = serde_json ::to_value ( self . get_activities ( conn ) ) . unwrap ( ) ;
coll . collection_props . set_total_items_u64 ( self . get_activities ( conn ) . len ( ) as u64 ) . unwrap ( ) ;
ActivityStream ::new ( coll )
2018-04-29 19:49:56 +02:00
}
2018-05-16 20:20:44 +02:00
fn get_activities ( & self , _conn : & PgConnection ) -> Vec < serde_json ::Value > {
2018-04-29 19:49:56 +02:00
vec! [ ]
}
2018-05-03 21:11:04 +02:00
pub fn get_keypair ( & self ) -> PKey < Private > {
PKey ::from_rsa ( Rsa ::private_key_from_pem ( self . private_key . clone ( ) . unwrap ( ) . as_ref ( ) ) . unwrap ( ) ) . unwrap ( )
}
2018-06-18 23:50:40 +02:00
pub fn webfinger ( & self , conn : & PgConnection ) -> Webfinger {
Webfinger {
subject : format ! ( " acct:{}@{} " , self . actor_id , self . get_instance ( conn ) . public_domain ) ,
2018-06-21 16:48:54 +02:00
aliases : vec ! [ self . ap_url . clone ( ) ] ,
2018-06-18 23:50:40 +02:00
links : vec ! [
Link {
rel : String ::from ( " http://webfinger.net/rel/profile-page " ) ,
mime_type : None ,
2018-07-26 21:35:35 +02:00
href : Some ( self . ap_url . clone ( ) ) ,
template : None
2018-06-18 23:50:40 +02:00
} ,
Link {
rel : String ::from ( " http://schemas.google.com/g/2010#updates-from " ) ,
mime_type : Some ( String ::from ( " application/atom+xml " ) ) ,
2018-07-26 21:35:35 +02:00
href : Some ( self . get_instance ( conn ) . compute_box ( BLOG_PREFIX , self . actor_id . clone ( ) , " feed.atom " ) ) ,
template : None
2018-06-18 23:50:40 +02:00
} ,
Link {
rel : String ::from ( " self " ) ,
mime_type : Some ( String ::from ( " application/activity+json " ) ) ,
2018-07-26 21:35:35 +02:00
href : Some ( self . ap_url . clone ( ) ) ,
template : None
2018-06-18 23:50:40 +02:00
}
]
}
}
2018-06-21 19:23:01 +02:00
pub fn from_url ( conn : & PgConnection , url : String ) -> Option < Blog > {
2018-06-21 23:07:04 +02:00
Blog ::find_by_ap_url ( conn , url . clone ( ) ) . or_else ( | | {
// The requested user was not in the DB
// We try to fetch it if it is remote
if Url ::parse ( url . as_ref ( ) ) . unwrap ( ) . host_str ( ) . unwrap ( ) ! = BASE_URL . as_str ( ) {
2018-06-23 13:14:03 +02:00
Blog ::fetch_from_url ( conn , url )
2018-06-21 23:07:04 +02:00
} else {
None
}
} )
2018-06-21 19:23:01 +02:00
}
2018-07-25 21:22:42 +02:00
pub fn get_fqn ( & self , conn : & PgConnection ) -> String {
if self . instance_id = = Instance ::local_id ( conn ) {
self . actor_id . clone ( )
} else {
format! ( " {} @ {} " , self . actor_id , self . get_instance ( conn ) . public_domain )
}
}
pub fn to_json ( & self , conn : & PgConnection ) -> serde_json ::Value {
let mut json = serde_json ::to_value ( self ) . unwrap ( ) ;
2018-07-26 19:00:23 +02:00
json [ " fqn " ] = json! ( self . get_fqn ( conn ) ) ;
2018-07-25 21:22:42 +02:00
json
}
2018-04-23 12:29:27 +02:00
}
2018-04-23 14:00:11 +02:00
2018-05-19 00:04:30 +02:00
impl IntoId for Blog {
fn into_id ( self ) -> Id {
2018-05-18 10:04:40 +02:00
Id ::new ( self . ap_url )
}
}
impl Object for Blog { }
impl Actor for Blog { }
impl WithInbox for Blog {
fn get_inbox_url ( & self ) -> String {
self . inbox_url . clone ( )
}
fn get_shared_inbox_url ( & self ) -> Option < String > {
None
}
2018-07-18 15:49:13 +02:00
fn is_local ( & self ) -> bool {
self . instance_id = = 0
}
2018-05-18 10:04:40 +02:00
}
2018-05-03 21:11:04 +02:00
impl sign ::Signer for Blog {
2018-06-21 17:25:32 +02:00
fn get_key_id ( & self ) -> String {
2018-06-21 16:48:54 +02:00
format! ( " {} #main-key " , self . ap_url )
2018-05-03 21:11:04 +02:00
}
fn sign ( & self , to_sign : String ) -> Vec < u8 > {
let key = self . get_keypair ( ) ;
let mut signer = Signer ::new ( MessageDigest ::sha256 ( ) , & key ) . unwrap ( ) ;
signer . update ( to_sign . as_bytes ( ) ) . unwrap ( ) ;
signer . sign_to_vec ( ) . unwrap ( )
}
}
2018-04-23 15:12:59 +02:00
impl NewBlog {
pub fn new_local (
actor_id : String ,
title : String ,
summary : String ,
instance_id : i32
) -> NewBlog {
2018-05-03 21:11:04 +02:00
let ( pub_key , priv_key ) = sign ::gen_keypair ( ) ;
2018-04-23 15:12:59 +02:00
NewBlog {
actor_id : actor_id ,
title : title ,
summary : summary ,
outbox_url : String ::from ( " " ) ,
inbox_url : String ::from ( " " ) ,
2018-05-01 20:02:29 +02:00
instance_id : instance_id ,
2018-05-03 21:11:04 +02:00
ap_url : String ::from ( " " ) ,
public_key : String ::from_utf8 ( pub_key ) . unwrap ( ) ,
private_key : Some ( String ::from_utf8 ( priv_key ) . unwrap ( ) )
2018-04-23 15:12:59 +02:00
}
}
}