2022-02-23 23:07:51 +00:00
|
|
|
use actix_web::HttpRequest;
|
2022-12-09 23:03:07 +00:00
|
|
|
use serde::{
|
|
|
|
Deserialize,
|
|
|
|
Deserializer,
|
|
|
|
de::DeserializeOwned,
|
|
|
|
de::Error as DeserializerError,
|
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
use serde_json::Value;
|
|
|
|
|
2021-12-28 18:34:13 +00:00
|
|
|
use crate::config::Config;
|
2023-01-17 23:14:18 +00:00
|
|
|
use crate::database::{DatabaseClient, DatabaseError};
|
2022-10-23 22:18:01 +00:00
|
|
|
use crate::errors::{
|
|
|
|
ConversionError,
|
|
|
|
HttpError,
|
|
|
|
ValidationError,
|
|
|
|
};
|
2022-10-27 18:23:48 +00:00
|
|
|
use super::authentication::{
|
2022-10-23 23:46:51 +00:00
|
|
|
verify_signed_activity,
|
2022-10-27 18:23:48 +00:00
|
|
|
verify_signed_request,
|
|
|
|
AuthenticationError,
|
|
|
|
};
|
2022-12-07 18:46:00 +00:00
|
|
|
use super::fetcher::fetchers::FetchError;
|
2022-05-30 22:23:06 +00:00
|
|
|
use super::handlers::{
|
2022-12-07 20:26:51 +00:00
|
|
|
accept::handle_accept,
|
2022-07-16 01:35:10 +00:00
|
|
|
add::handle_add,
|
2022-05-30 22:23:06 +00:00
|
|
|
announce::handle_announce,
|
2022-12-07 20:26:51 +00:00
|
|
|
create::handle_create,
|
2022-05-30 22:23:06 +00:00
|
|
|
delete::handle_delete,
|
2022-05-30 22:50:57 +00:00
|
|
|
follow::handle_follow,
|
2022-05-30 22:29:09 +00:00
|
|
|
like::handle_like,
|
2022-12-07 20:26:51 +00:00
|
|
|
r#move::handle_move,
|
|
|
|
reject::handle_reject,
|
2022-07-16 01:35:10 +00:00
|
|
|
remove::handle_remove,
|
2022-05-30 23:08:49 +00:00
|
|
|
undo::handle_undo,
|
2022-12-06 23:48:24 +00:00
|
|
|
update::handle_update,
|
2022-05-30 22:23:06 +00:00
|
|
|
};
|
2022-12-31 13:28:25 +00:00
|
|
|
use super::queues::IncomingActivityJobData;
|
2021-04-09 00:22:17 +00:00
|
|
|
use super::vocabulary::*;
|
|
|
|
|
2022-10-23 22:18:01 +00:00
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
|
pub enum HandlerError {
|
|
|
|
#[error("local object")]
|
|
|
|
LocalObject,
|
|
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
FetchError(#[from] FetchError),
|
|
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
ValidationError(#[from] ValidationError),
|
|
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
DatabaseError(#[from] DatabaseError),
|
|
|
|
|
|
|
|
#[error(transparent)]
|
2022-10-27 18:23:48 +00:00
|
|
|
AuthError(#[from] AuthenticationError),
|
2022-10-23 22:18:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<HandlerError> for HttpError {
|
|
|
|
fn from(error: HandlerError) -> Self {
|
|
|
|
match error {
|
|
|
|
HandlerError::LocalObject => HttpError::InternalError,
|
|
|
|
HandlerError::FetchError(error) => {
|
|
|
|
HttpError::ValidationError(error.to_string())
|
|
|
|
},
|
|
|
|
HandlerError::ValidationError(error) => error.into(),
|
|
|
|
HandlerError::DatabaseError(error) => error.into(),
|
|
|
|
HandlerError::AuthError(_) => {
|
|
|
|
HttpError::AuthError("invalid signature")
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-13 22:30:41 +00:00
|
|
|
/// Transforms arbitrary property value into array of strings
|
2022-05-02 00:24:44 +00:00
|
|
|
pub fn parse_array(value: &Value) -> Result<Vec<String>, ConversionError> {
|
2021-11-18 23:24:13 +00:00
|
|
|
let result = match value {
|
|
|
|
Value::String(string) => vec![string.to_string()],
|
|
|
|
Value::Array(array) => {
|
2021-12-24 15:13:25 +00:00
|
|
|
let mut results = vec![];
|
|
|
|
for value in array {
|
|
|
|
match value {
|
|
|
|
Value::String(string) => results.push(string.to_string()),
|
|
|
|
Value::Object(object) => {
|
|
|
|
if let Some(string) = object["id"].as_str() {
|
|
|
|
results.push(string.to_string());
|
|
|
|
} else {
|
|
|
|
// id property is missing
|
|
|
|
return Err(ConversionError);
|
|
|
|
};
|
|
|
|
},
|
|
|
|
// Unexpected array item type
|
|
|
|
_ => return Err(ConversionError),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
results
|
2021-11-18 23:24:13 +00:00
|
|
|
},
|
2021-12-24 15:13:25 +00:00
|
|
|
// Unexpected value type
|
|
|
|
_ => return Err(ConversionError),
|
2021-11-18 23:24:13 +00:00
|
|
|
};
|
|
|
|
Ok(result)
|
|
|
|
}
|
|
|
|
|
2022-04-13 22:30:41 +00:00
|
|
|
/// Transforms arbitrary property value into array of structs
|
2022-05-02 00:24:44 +00:00
|
|
|
pub fn parse_property_value<T: DeserializeOwned>(value: &Value) -> Result<Vec<T>, ConversionError> {
|
2022-04-13 22:30:41 +00:00
|
|
|
let objects = match value {
|
|
|
|
Value::Array(array) => array.to_vec(),
|
|
|
|
Value::Object(_) => vec![value.clone()],
|
|
|
|
// Unexpected value type
|
|
|
|
_ => return Err(ConversionError),
|
|
|
|
};
|
|
|
|
let mut items = vec![];
|
|
|
|
for object in objects {
|
|
|
|
let item: T = serde_json::from_value(object)
|
|
|
|
.map_err(|_| ConversionError)?;
|
|
|
|
items.push(item);
|
|
|
|
};
|
|
|
|
Ok(items)
|
|
|
|
}
|
|
|
|
|
2021-11-29 20:45:36 +00:00
|
|
|
/// Parses object json value and returns its ID as string
|
2022-07-15 17:31:02 +00:00
|
|
|
pub fn find_object_id(object: &Value) -> Result<String, ValidationError> {
|
2021-11-29 20:45:36 +00:00
|
|
|
let object_id = match object.as_str() {
|
|
|
|
Some(object_id) => object_id.to_owned(),
|
|
|
|
None => {
|
2022-05-30 19:51:32 +00:00
|
|
|
let object_id = object["id"].as_str()
|
|
|
|
.ok_or(ValidationError("missing object ID"))?
|
|
|
|
.to_string();
|
|
|
|
object_id
|
2021-11-29 20:45:36 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
Ok(object_id)
|
|
|
|
}
|
|
|
|
|
2022-12-09 23:03:07 +00:00
|
|
|
pub fn deserialize_into_object_id<'de, D>(
|
|
|
|
deserializer: D,
|
|
|
|
) -> Result<String, D::Error>
|
|
|
|
where D: Deserializer<'de>
|
|
|
|
{
|
|
|
|
let value = Value::deserialize(deserializer)?;
|
|
|
|
let object_id = find_object_id(&value)
|
|
|
|
.map_err(DeserializerError::custom)?;
|
|
|
|
Ok(object_id)
|
|
|
|
}
|
|
|
|
|
2022-12-31 00:06:33 +00:00
|
|
|
pub async fn handle_activity(
|
2022-12-11 17:31:48 +00:00
|
|
|
config: &Config,
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &mut impl DatabaseClient,
|
2022-12-11 17:31:48 +00:00
|
|
|
activity: &Value,
|
|
|
|
is_authenticated: bool,
|
|
|
|
) -> Result<(), HandlerError> {
|
|
|
|
let activity_type = activity["type"].as_str()
|
|
|
|
.ok_or(ValidationError("type property is missing"))?
|
|
|
|
.to_owned();
|
|
|
|
let activity_actor = activity["actor"].as_str()
|
|
|
|
.ok_or(ValidationError("actor property is missing"))?
|
|
|
|
.to_owned();
|
|
|
|
let activity = activity.clone();
|
|
|
|
let maybe_object_type = match activity_type.as_str() {
|
|
|
|
ACCEPT => {
|
|
|
|
handle_accept(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
ADD => {
|
|
|
|
handle_add(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
ANNOUNCE => {
|
|
|
|
handle_announce(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
CREATE => {
|
|
|
|
handle_create(config, db_client, activity, is_authenticated).await?
|
|
|
|
},
|
|
|
|
DELETE => {
|
|
|
|
handle_delete(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
FOLLOW => {
|
|
|
|
handle_follow(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
LIKE | EMOJI_REACT => {
|
|
|
|
handle_like(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
MOVE => {
|
|
|
|
handle_move(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
REJECT => {
|
|
|
|
handle_reject(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
REMOVE => {
|
|
|
|
handle_remove(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
UNDO => {
|
|
|
|
handle_undo(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
UPDATE => {
|
|
|
|
handle_update(config, db_client, activity).await?
|
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
log::warn!("activity type is not supported: {}", activity);
|
|
|
|
None
|
|
|
|
},
|
|
|
|
};
|
|
|
|
if let Some(object_type) = maybe_object_type {
|
|
|
|
log::info!(
|
|
|
|
"processed {}({}) from {}",
|
|
|
|
activity_type,
|
|
|
|
object_type,
|
|
|
|
activity_actor,
|
|
|
|
);
|
|
|
|
};
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
pub async fn receive_activity(
|
|
|
|
config: &Config,
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &mut impl DatabaseClient,
|
2022-02-23 23:07:51 +00:00
|
|
|
request: &HttpRequest,
|
2022-12-09 21:16:53 +00:00
|
|
|
activity: &Value,
|
2022-10-23 22:18:01 +00:00
|
|
|
) -> Result<(), HandlerError> {
|
2022-12-09 21:16:53 +00:00
|
|
|
let activity_type = activity["type"].as_str()
|
2022-12-08 18:01:47 +00:00
|
|
|
.ok_or(ValidationError("type property is missing"))?;
|
2022-12-09 21:16:53 +00:00
|
|
|
let activity_actor = activity["actor"].as_str()
|
2022-12-08 18:01:47 +00:00
|
|
|
.ok_or(ValidationError("actor property is missing"))?;
|
2022-05-30 17:35:43 +00:00
|
|
|
|
2022-12-19 15:12:21 +00:00
|
|
|
let actor_hostname = url::Url::parse(activity_actor)
|
|
|
|
.map_err(|_| ValidationError("invalid actor ID"))?
|
|
|
|
.host_str()
|
|
|
|
.ok_or(ValidationError("invalid actor ID"))?
|
|
|
|
.to_string();
|
|
|
|
if config.blocked_instances.iter()
|
|
|
|
.any(|instance_hostname| &actor_hostname == instance_hostname)
|
|
|
|
{
|
|
|
|
log::warn!("ignoring activity from blocked instance: {}", activity);
|
|
|
|
return Ok(());
|
|
|
|
};
|
|
|
|
|
2022-05-30 19:51:32 +00:00
|
|
|
let is_self_delete = if activity_type == DELETE {
|
2022-12-09 21:16:53 +00:00
|
|
|
let object_id = find_object_id(&activity["object"])?;
|
2022-12-08 18:01:47 +00:00
|
|
|
object_id == activity_actor
|
2022-05-30 19:51:32 +00:00
|
|
|
} else { false };
|
2022-12-07 22:10:58 +00:00
|
|
|
|
|
|
|
// HTTP signature is required
|
2022-11-06 22:23:28 +00:00
|
|
|
let mut signer = match verify_signed_request(
|
|
|
|
config,
|
|
|
|
db_client,
|
|
|
|
request,
|
|
|
|
// Don't fetch signer if this is Delete(Person) activity
|
|
|
|
is_self_delete,
|
|
|
|
).await {
|
|
|
|
Ok(request_signer) => {
|
|
|
|
log::debug!("request signed by {}", request_signer.acct);
|
|
|
|
request_signer
|
|
|
|
},
|
2022-05-30 17:35:43 +00:00
|
|
|
Err(error) => {
|
2022-12-07 22:10:58 +00:00
|
|
|
if is_self_delete && matches!(
|
|
|
|
error,
|
|
|
|
AuthenticationError::NoHttpSignature |
|
2023-01-11 00:51:03 +00:00
|
|
|
AuthenticationError::DatabaseError(DatabaseError::NotFound(_))
|
2022-12-07 22:10:58 +00:00
|
|
|
) {
|
2022-05-30 17:35:43 +00:00
|
|
|
// Ignore Delete(Person) activities without HTTP signatures
|
2022-12-07 22:10:58 +00:00
|
|
|
// or if signer is not found in local database
|
2022-05-30 17:35:43 +00:00
|
|
|
return Ok(());
|
|
|
|
};
|
2022-10-23 23:46:51 +00:00
|
|
|
log::warn!("invalid HTTP signature: {}", error);
|
2022-10-23 21:35:32 +00:00
|
|
|
return Err(error.into());
|
2022-05-30 17:35:43 +00:00
|
|
|
},
|
|
|
|
};
|
2022-10-23 23:46:51 +00:00
|
|
|
|
2022-12-07 22:10:58 +00:00
|
|
|
// JSON signature is optional
|
2022-12-08 16:05:14 +00:00
|
|
|
match verify_signed_activity(
|
|
|
|
config,
|
|
|
|
db_client,
|
2022-12-09 21:16:53 +00:00
|
|
|
activity,
|
2022-12-08 16:05:14 +00:00
|
|
|
// Don't fetch actor if this is Delete(Person) activity
|
|
|
|
is_self_delete,
|
|
|
|
).await {
|
2022-11-06 22:23:28 +00:00
|
|
|
Ok(activity_signer) => {
|
|
|
|
if activity_signer.acct != signer.acct {
|
2022-10-31 22:25:02 +00:00
|
|
|
log::warn!(
|
|
|
|
"request signer {} is different from activity signer {}",
|
2022-11-06 22:23:28 +00:00
|
|
|
signer.acct,
|
|
|
|
activity_signer.acct,
|
2022-10-31 22:25:02 +00:00
|
|
|
);
|
|
|
|
} else {
|
2022-11-06 22:23:28 +00:00
|
|
|
log::debug!("activity signed by {}", activity_signer.acct);
|
2022-10-31 22:25:02 +00:00
|
|
|
};
|
2022-11-06 22:23:28 +00:00
|
|
|
// Activity signature has higher priority
|
|
|
|
signer = activity_signer;
|
2022-10-23 23:46:51 +00:00
|
|
|
},
|
|
|
|
Err(AuthenticationError::NoJsonSignature) => (), // ignore
|
|
|
|
Err(other_error) => {
|
2022-11-02 19:23:32 +00:00
|
|
|
log::warn!("invalid JSON signature: {}", other_error);
|
2022-10-23 23:46:51 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-10-04 19:18:05 +00:00
|
|
|
if config.blocked_instances.iter()
|
|
|
|
.any(|instance| signer.hostname.as_ref() == Some(instance))
|
|
|
|
{
|
2022-12-09 21:16:53 +00:00
|
|
|
log::warn!("ignoring activity from blocked instance: {}", activity);
|
2022-06-14 19:46:46 +00:00
|
|
|
return Ok(());
|
2022-05-30 17:35:43 +00:00
|
|
|
};
|
|
|
|
|
2022-11-06 22:23:28 +00:00
|
|
|
let signer_id = signer.actor_id(&config.instance_url());
|
2022-12-08 18:01:47 +00:00
|
|
|
let is_authenticated = activity_actor == signer_id;
|
|
|
|
if !is_authenticated {
|
|
|
|
match activity_type {
|
|
|
|
CREATE => (), // Accept forwarded Create() activities
|
|
|
|
DELETE => {
|
|
|
|
// Ignore forwarded Delete(Person) and Delete(Note) activities
|
|
|
|
return Ok(());
|
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
// Reject other types
|
|
|
|
log::warn!(
|
|
|
|
"request signer {} does not match actor {}",
|
|
|
|
signer_id,
|
|
|
|
activity_actor,
|
|
|
|
);
|
|
|
|
return Err(AuthenticationError::UnexpectedSigner.into());
|
|
|
|
},
|
|
|
|
};
|
|
|
|
};
|
2022-11-06 22:23:28 +00:00
|
|
|
|
2022-12-12 18:11:48 +00:00
|
|
|
if let ANNOUNCE | CREATE | UPDATE = activity_type {
|
2022-12-11 23:29:57 +00:00
|
|
|
// Add activity to job queue and release lock
|
2022-12-31 13:28:25 +00:00
|
|
|
IncomingActivityJobData::new(activity, is_authenticated)
|
|
|
|
.into_job(db_client, 0).await?;
|
2022-12-12 18:11:48 +00:00
|
|
|
log::debug!("activity added to the queue: {}", activity_type);
|
2022-12-11 23:29:57 +00:00
|
|
|
return Ok(());
|
|
|
|
};
|
|
|
|
|
2022-12-11 17:31:48 +00:00
|
|
|
handle_activity(
|
|
|
|
config,
|
|
|
|
db_client,
|
|
|
|
activity,
|
|
|
|
is_authenticated,
|
|
|
|
).await
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-11-18 23:24:13 +00:00
|
|
|
use serde_json::json;
|
2021-04-09 00:22:17 +00:00
|
|
|
use super::*;
|
|
|
|
|
2021-11-18 23:24:13 +00:00
|
|
|
#[test]
|
|
|
|
fn test_parse_array_with_string() {
|
|
|
|
let value = json!("test");
|
|
|
|
assert_eq!(
|
|
|
|
parse_array(&value).unwrap(),
|
|
|
|
vec!["test".to_string()],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_array_with_array() {
|
|
|
|
let value = json!(["test1", "test2"]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_array(&value).unwrap(),
|
|
|
|
vec!["test1".to_string(), "test2".to_string()],
|
|
|
|
);
|
|
|
|
}
|
2021-11-29 20:45:36 +00:00
|
|
|
|
2021-12-24 15:13:25 +00:00
|
|
|
#[test]
|
|
|
|
fn test_parse_array_with_array_of_objects() {
|
|
|
|
let value = json!([{"id": "test1"}, {"id": "test2"}]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_array(&value).unwrap(),
|
|
|
|
vec!["test1".to_string(), "test2".to_string()],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-02 21:46:24 +00:00
|
|
|
#[test]
|
|
|
|
fn test_parse_property_value_tag_list() {
|
|
|
|
let value = json!({"type": "Mention"});
|
|
|
|
let value_list: Vec<Value> = parse_property_value(&value).unwrap();
|
|
|
|
assert_eq!(value_list, vec![value]);
|
|
|
|
}
|
|
|
|
|
2021-11-29 20:45:36 +00:00
|
|
|
#[test]
|
2022-07-15 17:31:02 +00:00
|
|
|
fn test_find_object_id_from_string() {
|
2021-11-29 20:45:36 +00:00
|
|
|
let value = json!("test_id");
|
2022-07-15 17:31:02 +00:00
|
|
|
assert_eq!(find_object_id(&value).unwrap(), "test_id");
|
2021-11-29 20:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-07-15 17:31:02 +00:00
|
|
|
fn test_find_object_id_from_object() {
|
2021-11-29 20:45:36 +00:00
|
|
|
let value = json!({"id": "test_id", "type": "Note"});
|
2022-07-15 17:31:02 +00:00
|
|
|
assert_eq!(find_object_id(&value).unwrap(), "test_id");
|
2021-11-29 20:45:36 +00:00
|
|
|
}
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|