Move AP ID parsers to activitypub::identifiers module
This commit is contained in:
parent
d2ba86315c
commit
9a38fb25bf
11 changed files with 156 additions and 117 deletions
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::{
|
||||
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?;
|
||||
|
|
|
@ -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(_) => (),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in a new issue