Move AP ID parsers to activitypub::identifiers module

This commit is contained in:
silverpill 2022-07-16 01:49:27 +00:00
parent d2ba86315c
commit 9a38fb25bf
11 changed files with 156 additions and 117 deletions

View file

@ -9,7 +9,7 @@ use crate::activitypub::handlers::{
create_note::handle_note,
update_person::update_remote_profile,
};
use crate::activitypub::receiver::parse_object_id;
use crate::activitypub::identifiers::parse_local_object_id;
use crate::config::{Config, Instance};
use crate::errors::{DatabaseError, HttpError, ValidationError};
use crate::models::posts::queries::get_post_by_object_id;
@ -193,7 +193,7 @@ pub async fn import_post(
loop {
let object_id = match maybe_object_id_to_fetch {
Some(object_id) => {
if parse_object_id(&instance.url(), &object_id).is_ok() {
if parse_local_object_id(&instance.url(), &object_id).is_ok() {
// Object is a local post
assert!(objects.len() > 0);
break;

View file

@ -2,7 +2,8 @@ use tokio_postgres::GenericClient;
use crate::activitypub::{
activity::Activity,
receiver::{find_object_id, parse_object_id},
identifiers::parse_local_object_id,
receiver::find_object_id,
vocabulary::FOLLOW,
};
use crate::config::Config;
@ -21,7 +22,10 @@ pub async fn handle_accept_follow(
) -> HandlerResult {
let actor_profile = get_profile_by_actor_id(db_client, &activity.actor).await?;
let object_id = find_object_id(&activity.object)?;
let follow_request_id = parse_object_id(&config.instance_url(), &object_id)?;
let follow_request_id = parse_local_object_id(
&config.instance_url(),
&object_id,
)?;
let follow_request = get_follow_request_by_id(db_client, &follow_request_id).await?;
if follow_request.target_id != actor_profile.id {
return Err(ValidationError("actor is not a target").into());

View file

@ -3,7 +3,8 @@ use tokio_postgres::GenericClient;
use crate::activitypub::{
activity::Activity,
fetcher::helpers::{get_or_import_profile_by_actor_id, import_post},
receiver::{find_object_id, parse_object_id},
identifiers::parse_local_object_id,
receiver::find_object_id,
vocabulary::NOTE,
};
use crate::config::Config;
@ -30,7 +31,7 @@ pub async fn handle_announce(
&activity.actor,
).await?;
let object_id = find_object_id(&activity.object)?;
let post_id = match parse_object_id(&config.instance_url(), &object_id) {
let post_id = match parse_local_object_id(&config.instance_url(), &object_id) {
Ok(post_id) => post_id,
Err(_) => {
// Try to get remote post

View file

@ -4,21 +4,19 @@ use std::path::Path;
use tokio_postgres::GenericClient;
use uuid::Uuid;
use crate::activitypub::activity::{Attachment, Object};
use crate::activitypub::constants::AP_PUBLIC;
use crate::activitypub::fetcher::fetchers::fetch_file;
use crate::activitypub::fetcher::helpers::{
get_or_import_profile_by_actor_id,
import_profile_by_actor_address,
ImportError,
use crate::activitypub::{
activity::{Attachment, Object},
constants::AP_PUBLIC,
fetcher::fetchers::fetch_file,
fetcher::helpers::{
get_or_import_profile_by_actor_id,
import_profile_by_actor_address,
ImportError,
},
identifiers::{parse_local_actor_id, parse_local_object_id},
receiver::{parse_array, parse_property_value},
vocabulary::{DOCUMENT, HASHTAG, IMAGE, MENTION, NOTE},
};
use crate::activitypub::receiver::{
parse_actor_id,
parse_array,
parse_object_id,
parse_property_value,
};
use crate::activitypub::vocabulary::{DOCUMENT, HASHTAG, IMAGE, MENTION, NOTE};
use crate::config::Instance;
use crate::errors::{DatabaseError, ValidationError};
use crate::models::attachments::queries::create_attachment;
@ -163,7 +161,7 @@ pub async fn handle_note(
} else if tag.tag_type == MENTION {
// Try to find profile by actor ID.
if let Some(href) = tag.href {
if let Ok(username) = parse_actor_id(&instance.url(), &href) {
if let Ok(username) = parse_local_actor_id(&instance.url(), &href) {
let user = get_user_by_name(db_client, &username).await?;
if !mentions.contains(&user.id) {
mentions.push(user.id);
@ -245,7 +243,7 @@ pub async fn handle_note(
};
let in_reply_to_id = match object.in_reply_to {
Some(object_id) => {
match parse_object_id(&instance.url(), &object_id) {
match parse_local_object_id(&instance.url(), &object_id) {
Ok(post_id) => {
// Local post
let post = get_post_by_id(db_client, &post_id).await?;

View file

@ -4,7 +4,8 @@ use crate::activitypub::{
activity::Activity,
builders::accept_follow::prepare_accept_follow,
fetcher::helpers::{get_or_import_profile_by_actor_id, ImportError},
receiver::{find_object_id, parse_actor_id},
identifiers::parse_local_actor_id,
receiver::find_object_id,
vocabulary::PERSON,
};
use crate::config::Config;
@ -27,7 +28,10 @@ pub async fn handle_follow(
let source_actor = source_profile.actor_json
.ok_or(ImportError::LocalObject)?;
let target_actor_id = find_object_id(&activity.object)?;
let target_username = parse_actor_id(&config.instance_url(), &target_actor_id)?;
let target_username = parse_local_actor_id(
&config.instance_url(),
&target_actor_id,
)?;
let target_user = get_user_by_name(db_client, &target_username).await?;
match follow(db_client, &source_profile.id, &target_user.profile.id).await {
Ok(_) => (),

View file

@ -3,7 +3,8 @@ use tokio_postgres::GenericClient;
use crate::activitypub::{
activity::Activity,
fetcher::helpers::get_or_import_profile_by_actor_id,
receiver::{find_object_id, parse_object_id},
identifiers::parse_local_object_id,
receiver::find_object_id,
vocabulary::NOTE,
};
use crate::config::Config;
@ -24,7 +25,7 @@ pub async fn handle_like(
&activity.actor,
).await?;
let object_id = find_object_id(&activity.object)?;
let post_id = match parse_object_id(&config.instance_url(), &object_id) {
let post_id = match parse_local_object_id(&config.instance_url(), &object_id) {
Ok(post_id) => post_id,
Err(_) => {
let post = match get_post_by_object_id(db_client, &object_id).await {

View file

@ -2,7 +2,8 @@ use tokio_postgres::GenericClient;
use crate::activitypub::{
activity::Activity,
receiver::{find_object_id, parse_object_id},
identifiers::parse_local_object_id,
receiver::find_object_id,
vocabulary::FOLLOW,
};
use crate::config::Config;
@ -21,7 +22,10 @@ pub async fn handle_reject_follow(
) -> HandlerResult {
let actor_profile = get_profile_by_actor_id(db_client, &activity.actor).await?;
let object_id = find_object_id(&activity.object)?;
let follow_request_id = parse_object_id(&config.instance_url(), &object_id)?;
let follow_request_id = parse_local_object_id(
&config.instance_url(),
&object_id,
)?;
let follow_request = get_follow_request_by_id(db_client, &follow_request_id).await?;
if follow_request.target_id != actor_profile.id {
return Err(ValidationError("actor is not a target").into());

View file

@ -2,7 +2,7 @@ use tokio_postgres::GenericClient;
use crate::activitypub::{
activity::{Activity, Object},
receiver::parse_actor_id,
identifiers::parse_local_actor_id,
vocabulary::FOLLOW,
};
use crate::config::Config;
@ -24,7 +24,10 @@ pub async fn handle_undo_follow(
let source_profile = get_profile_by_actor_id(db_client, &activity.actor).await?;
let target_actor_id = object.object
.ok_or(ValidationError("invalid object"))?;
let target_username = parse_actor_id(&config.instance_url(), &target_actor_id)?;
let target_username = parse_local_actor_id(
&config.instance_url(),
&target_actor_id,
)?;
// acct equals username if profile is local
let target_profile = get_profile_by_acct(db_client, &target_username).await?;
match unfollow(db_client, &source_profile.id, &target_profile.id).await {

View file

@ -2,7 +2,7 @@ use chrono::Utc;
use tokio_postgres::GenericClient;
use crate::activitypub::activity::Object;
use crate::activitypub::receiver::parse_object_id;
use crate::activitypub::identifiers::parse_local_object_id;
use crate::activitypub::vocabulary::NOTE;
use crate::errors::DatabaseError;
use crate::models::posts::queries::{
@ -18,7 +18,7 @@ pub async fn handle_update_note(
instance_url: &str,
object: Object,
) -> HandlerResult {
let post_id = match parse_object_id(instance_url, &object.id) {
let post_id = match parse_local_object_id(instance_url, &object.id) {
Ok(post_id) => post_id,
Err(_) => {
let post = match get_post_by_object_id(db_client, &object.id).await {

View file

@ -1,5 +1,8 @@
use regex::Regex;
use uuid::Uuid;
use crate::errors::ValidationError;
pub enum LocalActorCollection {
Inbox,
Outbox,
@ -57,3 +60,109 @@ pub fn local_instance_actor_id(instance_url: &str) -> String {
pub fn local_object_id(instance_url: &str, internal_object_id: &Uuid) -> String {
format!("{}/objects/{}", instance_url, internal_object_id)
}
pub fn parse_local_actor_id(
instance_url: &str,
actor_id: &str,
) -> Result<String, ValidationError> {
let url_regexp_str = format!(
"^{}/users/(?P<username>[0-9a-z_]+)$",
instance_url.replace('.', r"\."),
);
let url_regexp = Regex::new(&url_regexp_str)
.map_err(|_| ValidationError("error"))?;
let url_caps = url_regexp.captures(actor_id)
.ok_or(ValidationError("invalid actor ID"))?;
let username = url_caps.name("username")
.ok_or(ValidationError("invalid actor ID"))?
.as_str()
.to_owned();
Ok(username)
}
pub fn parse_local_object_id(
instance_url: &str,
object_id: &str,
) -> Result<Uuid, ValidationError> {
let url_regexp_str = format!(
"^{}/objects/(?P<uuid>[0-9a-f-]+)$",
instance_url.replace('.', r"\."),
);
let url_regexp = Regex::new(&url_regexp_str)
.map_err(|_| ValidationError("error"))?;
let url_caps = url_regexp.captures(object_id)
.ok_or(ValidationError("invalid object ID"))?;
let internal_object_id: Uuid = url_caps.name("uuid")
.ok_or(ValidationError("invalid object ID"))?
.as_str().parse()
.map_err(|_| ValidationError("invalid object ID"))?;
Ok(internal_object_id)
}
#[cfg(test)]
mod tests {
use crate::utils::id::new_uuid;
use super::*;
const INSTANCE_URL: &str = "https://example.org";
#[test]
fn test_parse_local_actor_id() {
let username = parse_local_actor_id(
INSTANCE_URL,
"https://example.org/users/test",
).unwrap();
assert_eq!(username, "test".to_string());
}
#[test]
fn test_parse_local_actor_id_wrong_path() {
let error = parse_local_actor_id(
INSTANCE_URL,
"https://example.org/user/test",
).unwrap_err();
assert_eq!(error.to_string(), "invalid actor ID");
}
#[test]
fn test_parse_local_actor_id_invalid_username() {
let error = parse_local_actor_id(
INSTANCE_URL,
"https://example.org/users/tes-t",
).unwrap_err();
assert_eq!(error.to_string(), "invalid actor ID");
}
#[test]
fn test_parse_local_actor_id_invalid_instance_url() {
let error = parse_local_actor_id(
INSTANCE_URL,
"https://example.gov/users/test",
).unwrap_err();
assert_eq!(error.to_string(), "invalid actor ID");
}
#[test]
fn test_parse_local_object_id() {
let expected_uuid = new_uuid();
let object_id = format!(
"https://example.org/objects/{}",
expected_uuid,
);
let internal_object_id = parse_local_object_id(
INSTANCE_URL,
&object_id,
).unwrap();
assert_eq!(internal_object_id, expected_uuid);
}
#[test]
fn test_parse_local_object_id_invalid_uuid() {
let object_id = "https://example.org/objects/1234";
let error = parse_local_object_id(
INSTANCE_URL,
object_id,
).unwrap_err();
assert_eq!(error.to_string(), "invalid object ID");
}
}

View file

@ -1,9 +1,7 @@
use actix_web::HttpRequest;
use regex::Regex;
use serde::de::DeserializeOwned;
use serde_json::Value;
use tokio_postgres::GenericClient;
use uuid::Uuid;
use crate::config::Config;
use crate::errors::{ConversionError, HttpError, ValidationError};
@ -24,44 +22,6 @@ use super::handlers::{
};
use super::vocabulary::*;
pub fn parse_actor_id(
instance_url: &str,
actor_id: &str,
) -> Result<String, ValidationError> {
let url_regexp_str = format!(
"^{}/users/(?P<username>[0-9a-z_]+)$",
instance_url.replace('.', r"\."),
);
let url_regexp = Regex::new(&url_regexp_str)
.map_err(|_| ValidationError("error"))?;
let url_caps = url_regexp.captures(actor_id)
.ok_or(ValidationError("invalid actor ID"))?;
let username = url_caps.name("username")
.ok_or(ValidationError("invalid actor ID"))?
.as_str()
.to_owned();
Ok(username)
}
pub fn parse_object_id(
instance_url: &str,
object_id: &str,
) -> Result<Uuid, ValidationError> {
let url_regexp_str = format!(
"^{}/objects/(?P<uuid>[0-9a-f-]+)$",
instance_url.replace('.', r"\."),
);
let url_regexp = Regex::new(&url_regexp_str)
.map_err(|_| ValidationError("error"))?;
let url_caps = url_regexp.captures(object_id)
.ok_or(ValidationError("invalid object ID"))?;
let internal_object_id: Uuid = url_caps.name("uuid")
.ok_or(ValidationError("invalid object ID"))?
.as_str().parse()
.map_err(|_| ValidationError("invalid object ID"))?;
Ok(internal_object_id)
}
/// Transforms arbitrary property value into array of strings
pub fn parse_array(value: &Value) -> Result<Vec<String>, ConversionError> {
let result = match value {
@ -252,53 +212,8 @@ pub async fn receive_activity(
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::utils::id::new_uuid;
use super::*;
const INSTANCE_URL: &str = "https://example.org";
#[test]
fn test_parse_actor_id() {
let username = parse_actor_id(INSTANCE_URL, "https://example.org/users/test").unwrap();
assert_eq!(username, "test".to_string());
}
#[test]
fn test_parse_actor_id_wrong_path() {
let error = parse_actor_id(INSTANCE_URL, "https://example.org/user/test").unwrap_err();
assert_eq!(error.to_string(), "invalid actor ID");
}
#[test]
fn test_parse_actor_id_invalid_username() {
let error = parse_actor_id(INSTANCE_URL, "https://example.org/users/tes-t").unwrap_err();
assert_eq!(error.to_string(), "invalid actor ID");
}
#[test]
fn test_parse_actor_id_invalid_instance_url() {
let error = parse_actor_id(INSTANCE_URL, "https://example.gov/users/test").unwrap_err();
assert_eq!(error.to_string(), "invalid actor ID");
}
#[test]
fn test_parse_object_id() {
let expected_uuid = new_uuid();
let object_id = format!(
"https://example.org/objects/{}",
expected_uuid,
);
let internal_object_id = parse_object_id(INSTANCE_URL, &object_id).unwrap();
assert_eq!(internal_object_id, expected_uuid);
}
#[test]
fn test_parse_object_id_invalid_uuid() {
let object_id = "https://example.org/objects/1234";
let error = parse_object_id(INSTANCE_URL, object_id).unwrap_err();
assert_eq!(error.to_string(), "invalid object ID");
}
#[test]
fn test_parse_array_with_string() {
let value = json!("test");