Sign GET request when fetching remote actor profile

This commit is contained in:
silverpill 2021-11-15 19:25:43 +00:00
parent 286e7155b8
commit 924f5181b6
4 changed files with 61 additions and 13 deletions

View file

@ -1,7 +1,10 @@
use std::path::Path; use std::path::Path;
use reqwest::Method;
use serde_json::Value; use serde_json::Value;
use crate::config::Instance;
use crate::http_signatures::create::{create_http_signature, SignatureError};
use crate::models::profiles::types::ProfileCreateData; use crate::models::profiles::types::ProfileCreateData;
use crate::utils::files::{save_file, FileError}; use crate::utils::files::{save_file, FileError};
use crate::webfinger::types::JsonResourceDescriptor; use crate::webfinger::types::JsonResourceDescriptor;
@ -14,6 +17,9 @@ pub enum FetchError {
#[error("invalid URL")] #[error("invalid URL")]
UrlError(#[from] url::ParseError), UrlError(#[from] url::ParseError),
#[error(transparent)]
SignatureError(#[from] SignatureError),
#[error(transparent)] #[error(transparent)]
RequestError(#[from] reqwest::Error), RequestError(#[from] reqwest::Error),
@ -27,6 +33,37 @@ pub enum FetchError {
OtherError(&'static str), OtherError(&'static str),
} }
/// Sends GET request to fetch AP object
async fn send_request(
instance: &Instance,
url: &str,
query_params: &[(&str, &str)],
) -> Result<String, FetchError> {
let client = reqwest::Client::new();
let mut request_builder = client.get(url);
if !query_params.is_empty() {
request_builder = request_builder.query(query_params);
};
let headers = create_http_signature(
Method::GET,
url,
"",
&instance.actor_key,
&instance.actor_key_id(),
)?;
let data = request_builder
.header(reqwest::header::ACCEPT, ACTIVITY_CONTENT_TYPE)
.header("Host", headers.host)
.header("Date", headers.date)
.header("Signature", headers.signature)
.send().await?
.error_for_status()?
.text().await?;
Ok(data)
}
pub async fn fetch_avatar_and_banner( pub async fn fetch_avatar_and_banner(
actor: &Actor, actor: &Actor,
media_dir: &Path, media_dir: &Path,
@ -55,14 +92,15 @@ pub async fn fetch_avatar_and_banner(
} }
pub async fn fetch_profile( pub async fn fetch_profile(
instance: &Instance,
username: &str, username: &str,
instance_host: &str, actor_host: &str,
media_dir: &Path, media_dir: &Path,
) -> Result<ProfileCreateData, FetchError> { ) -> Result<ProfileCreateData, FetchError> {
let actor_address = format!("{}@{}", &username, &instance_host); let actor_address = format!("{}@{}", &username, &actor_host);
let webfinger_account_uri = format!("acct:{}", actor_address); let webfinger_account_uri = format!("acct:{}", actor_address);
// TOOD: support http // TOOD: support http
let webfinger_url = format!("https://{}/.well-known/webfinger", instance_host); let webfinger_url = format!("https://{}/.well-known/webfinger", actor_host);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let webfinger_data = client.get(&webfinger_url) let webfinger_data = client.get(&webfinger_url)
.query(&[("resource", webfinger_account_uri)]) .query(&[("resource", webfinger_account_uri)])
@ -75,10 +113,11 @@ pub async fn fetch_profile(
.ok_or(FetchError::OtherError("self link not found"))?; .ok_or(FetchError::OtherError("self link not found"))?;
let actor_url = link.href.as_ref() let actor_url = link.href.as_ref()
.ok_or(FetchError::OtherError("account href not found"))?; .ok_or(FetchError::OtherError("account href not found"))?;
fetch_profile_by_actor_id(actor_url, media_dir).await fetch_profile_by_actor_id(instance, actor_url, media_dir).await
} }
pub async fn fetch_profile_by_actor_id( pub async fn fetch_profile_by_actor_id(
instance: &Instance,
actor_url: &str, actor_url: &str,
media_dir: &Path, media_dir: &Path,
) -> Result<ProfileCreateData, FetchError> { ) -> Result<ProfileCreateData, FetchError> {
@ -86,11 +125,7 @@ pub async fn fetch_profile_by_actor_id(
.host_str() .host_str()
.ok_or(FetchError::OtherError("invalid URL"))? .ok_or(FetchError::OtherError("invalid URL"))?
.to_owned(); .to_owned();
let client = reqwest::Client::new(); let actor_json = send_request(instance, actor_url, &[]).await?;
let actor_json = client.get(actor_url)
.header(reqwest::header::ACCEPT, ACTIVITY_CONTENT_TYPE)
.send().await?
.text().await?;
let actor_value: Value = serde_json::from_str(&actor_json)?; let actor_value: Value = serde_json::from_str(&actor_json)?;
let actor: Actor = serde_json::from_value(actor_value.clone())?; let actor: Actor = serde_json::from_value(actor_value.clone())?;
let (avatar, banner) = fetch_avatar_and_banner(&actor, media_dir).await?; let (avatar, banner) = fetch_avatar_and_banner(&actor, media_dir).await?;

View file

@ -5,7 +5,7 @@ use serde_json::Value;
use tokio_postgres::GenericClient; use tokio_postgres::GenericClient;
use uuid::Uuid; use uuid::Uuid;
use crate::config::Config; use crate::config::{Config, Instance};
use crate::database::{Pool, get_database_client}; use crate::database::{Pool, get_database_client};
use crate::errors::{DatabaseError, HttpError, ValidationError}; use crate::errors::{DatabaseError, HttpError, ValidationError};
use crate::models::attachments::queries::create_attachment; use crate::models::attachments::queries::create_attachment;
@ -82,13 +82,17 @@ fn parse_object_id(
async fn get_or_fetch_profile_by_actor_id( async fn get_or_fetch_profile_by_actor_id(
db_client: &impl GenericClient, db_client: &impl GenericClient,
instance: &Instance,
actor_id: &str, actor_id: &str,
media_dir: &Path, media_dir: &Path,
) -> Result<DbActorProfile, HttpError> { ) -> Result<DbActorProfile, HttpError> {
let profile = match get_profile_by_actor_id(db_client, actor_id).await { let profile = match get_profile_by_actor_id(db_client, actor_id).await {
Ok(profile) => profile, Ok(profile) => profile,
Err(DatabaseError::NotFound(_)) => { Err(DatabaseError::NotFound(_)) => {
let profile_data = fetch_profile_by_actor_id(actor_id, media_dir).await let profile_data = fetch_profile_by_actor_id(
instance, actor_id, media_dir,
)
.await
.map_err(|_| ValidationError("failed to fetch actor"))?; .map_err(|_| ValidationError("failed to fetch actor"))?;
let profile = create_profile(db_client, &profile_data).await?; let profile = create_profile(db_client, &profile_data).await?;
profile profile
@ -153,6 +157,7 @@ pub async fn process_note(
.ok_or(ValidationError("unattributed note"))?; .ok_or(ValidationError("unattributed note"))?;
let author = get_or_fetch_profile_by_actor_id( let author = get_or_fetch_profile_by_actor_id(
db_client, db_client,
&config.instance(),
&attributed_to, &attributed_to,
&config.media_dir(), &config.media_dir(),
).await?; ).await?;
@ -184,6 +189,7 @@ pub async fn process_note(
if tag.tag_type == MENTION { if tag.tag_type == MENTION {
let profile = get_or_fetch_profile_by_actor_id( let profile = get_or_fetch_profile_by_actor_id(
db_client, db_client,
&config.instance(),
&tag.href, &tag.href,
&config.media_dir(), &config.media_dir(),
).await?; ).await?;
@ -270,6 +276,7 @@ pub async fn receive_activity(
(LIKE, _) => { (LIKE, _) => {
let author = get_or_fetch_profile_by_actor_id( let author = get_or_fetch_profile_by_actor_id(
db_client, db_client,
&config.instance(),
&activity.actor, &activity.actor,
&config.media_dir(), &config.media_dir(),
).await?; ).await?;
@ -298,6 +305,7 @@ pub async fn receive_activity(
(FOLLOW, _) => { (FOLLOW, _) => {
let source_profile = get_or_fetch_profile_by_actor_id( let source_profile = get_or_fetch_profile_by_actor_id(
db_client, db_client,
&config.instance(),
&activity.actor, &activity.actor,
&config.media_dir(), &config.media_dir(),
).await?; ).await?;

View file

@ -123,6 +123,7 @@ pub async fn verify_http_signature(
Err(err) => match err { Err(err) => match err {
DatabaseError::NotFound(_) => { DatabaseError::NotFound(_) => {
let profile_data = fetch_profile_by_actor_id( let profile_data = fetch_profile_by_actor_id(
&config.instance(),
&signature_data.actor_id, &signature_data.actor_id,
&config.media_dir(), &config.media_dir(),
).await.map_err(|err| { ).await.map_err(|err| {

View file

@ -49,9 +49,13 @@ async fn search_profiles(
let mut profiles = search_profile(db_client, &username, instance.as_ref()).await?; let mut profiles = search_profile(db_client, &username, instance.as_ref()).await?;
if profiles.is_empty() && instance.is_some() { if profiles.is_empty() && instance.is_some() {
let actor_host = instance.unwrap(); let actor_host = instance.unwrap();
let media_dir = config.media_dir();
// TODO: return error when trying to fetch local profile // TODO: return error when trying to fetch local profile
match fetch_profile(&username, &actor_host, &media_dir).await { match fetch_profile(
&config.instance(),
&username,
&actor_host,
&config.media_dir(),
).await {
Ok(profile_data) => { Ok(profile_data) => {
let profile = create_profile(db_client, &profile_data).await?; let profile = create_profile(db_client, &profile_data).await?;
log::info!( log::info!(