Validate acct during profile creation

This commit is contained in:
silverpill 2022-10-03 21:44:04 +00:00
parent 429f530a71
commit 8b6aef2b7a
5 changed files with 67 additions and 10 deletions

View file

@ -1,3 +1,6 @@
use std::str::FromStr;
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
@ -178,12 +181,6 @@ pub struct ActorAddress {
pub instance: String, pub instance: String,
} }
impl ToString for ActorAddress {
fn to_string(&self) -> String {
format!("{}@{}", self.username, self.instance)
}
}
impl ActorAddress { impl ActorAddress {
pub fn is_local(&self, instance_host: &str) -> bool { pub fn is_local(&self, instance_host: &str) -> bool {
self.instance == instance_host self.instance == instance_host
@ -199,6 +196,30 @@ impl ActorAddress {
} }
} }
// See also: USERNAME_RE in models::profiles::validators
const ACTOR_ADDRESS_RE: &str = r"(?P<username>[\w\.-]+)@(?P<instance>[\w\.-]+)";
impl FromStr for ActorAddress {
type Err = ValidationError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let actor_address_re = Regex::new(ACTOR_ADDRESS_RE).unwrap();
let caps = actor_address_re.captures(value)
.ok_or(ValidationError("invalid actor address"))?;
let actor_address = Self {
username: caps["username"].to_string(),
instance: caps["instance"].to_string(),
};
Ok(actor_address)
}
}
impl ToString for ActorAddress {
fn to_string(&self) -> String {
format!("{}@{}", self.username, self.instance)
}
}
pub type ActorKeyError = rsa::pkcs8::Error; pub type ActorKeyError = rsa::pkcs8::Error;
pub fn get_local_actor( pub fn get_local_actor(
@ -330,6 +351,15 @@ mod tests {
const INSTANCE_HOST: &str = "example.com"; const INSTANCE_HOST: &str = "example.com";
const INSTANCE_URL: &str = "https://example.com"; const INSTANCE_URL: &str = "https://example.com";
#[test]
fn test_actor_address_parsing() {
let value = "user_1@example.com";
let actor_address = value.parse::<ActorAddress>().unwrap();
assert_eq!(actor_address.username, "user_1");
assert_eq!(actor_address.instance, "example.com");
assert_eq!(actor_address.to_string(), value);
}
#[test] #[test]
fn test_get_actor_address() { fn test_get_actor_address() {
let actor = Actor { let actor = Actor {

View file

@ -40,7 +40,7 @@ enum SearchQuery {
fn parse_profile_query(query: &str) -> fn parse_profile_query(query: &str) ->
Result<(String, Option<String>), ValidationError> Result<(String, Option<String>), ValidationError>
{ {
// See also: USERNAME_RE in models::profiles::validators // See also: ACTOR_ADDRESS_RE in activitypub::actors::types
let acct_query_re = let acct_query_re =
Regex::new(r"^(@|!)?(?P<user>[\w\.-]+)(@(?P<instance>[\w\.-]+))?$").unwrap(); Regex::new(r"^(@|!)?(?P<user>[\w\.-]+)(@(?P<instance>[\w\.-]+))?$").unwrap();
let acct_query_caps = acct_query_re.captures(query) let acct_query_caps = acct_query_re.captures(query)

View file

@ -8,7 +8,7 @@ use crate::errors::{DatabaseError, ValidationError};
use crate::models::profiles::queries::get_profiles_by_accts; use crate::models::profiles::queries::get_profiles_by_accts;
use crate::models::profiles::types::DbActorProfile; use crate::models::profiles::types::DbActorProfile;
// See also: USERNAME_RE in models::profiles::validators // See also: ACTOR_ADDRESS_RE in activitypub::actors::types
const MENTION_RE: &str = r"@?(?P<user>[\w\.-]+)@(?P<instance>.+)"; const MENTION_RE: &str = r"@?(?P<user>[\w\.-]+)@(?P<instance>.+)";
const MENTION_SEARCH_RE: &str = r"(?m)(?P<before>^|\s|[\(])@(?P<user>[\w\.-]+)@(?P<instance>\S+)"; const MENTION_SEARCH_RE: &str = r"(?m)(?P<before>^|\s|[\(])@(?P<user>[\w\.-]+)@(?P<instance>\S+)";
const MENTION_SEARCH_SECONDARY_RE: &str = r"^(?P<instance>[\w\.-]+\w)(?P<after>[\.,:?\)]?(<br>)?)$"; const MENTION_SEARCH_SECONDARY_RE: &str = r"^(?P<instance>[\w\.-]+\w)(?P<after>[\.,:?\)]?(<br>)?)$";

View file

@ -10,7 +10,7 @@ use serde::{
}; };
use uuid::Uuid; use uuid::Uuid;
use crate::activitypub::actors::types::Actor; use crate::activitypub::actors::types::{Actor, ActorAddress};
use crate::activitypub::identifiers::local_actor_id; use crate::activitypub::identifiers::local_actor_id;
use crate::database::json_macro::{json_from_sql, json_to_sql}; use crate::database::json_macro::{json_from_sql, json_to_sql};
use crate::errors::{ConversionError, ValidationError}; use crate::errors::{ConversionError, ValidationError};
@ -337,6 +337,17 @@ impl ProfileCreateData {
if let Some(display_name) = &self.display_name { if let Some(display_name) = &self.display_name {
validate_display_name(display_name)?; validate_display_name(display_name)?;
}; };
let acct_username = if self.actor_json.is_none() {
// Local profile
self.acct.clone()
} else {
// Remote profile
let ActorAddress { username, .. } = self.acct.parse::<ActorAddress>()?;
username
};
if self.username != acct_username {
return Err(ValidationError("username doesn't match acct"));
};
if let Some(bio) = &self.bio { if let Some(bio) = &self.bio {
let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?; let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?;
self.bio = Some(cleaned_bio); self.bio = Some(cleaned_bio);
@ -477,4 +488,20 @@ mod tests {
remote_profile.acct, remote_profile.acct,
); );
} }
#[test]
fn test_clean_profile_create_data() {
let mut profile_data = ProfileCreateData {
username: "test".to_string(),
display_name: Some("Test Test".to_string()),
acct: "test@example.org".to_string(),
actor_json: Some(Actor {
id: "https://example.org/test".to_string(),
..Default::default()
}),
..Default::default()
};
let result = profile_data.clean();
assert_eq!(result.is_ok(), true);
}
} }

View file

@ -25,7 +25,7 @@ async fn get_user_info(
) -> Result<JsonResourceDescriptor, HttpError> { ) -> Result<JsonResourceDescriptor, HttpError> {
// Parse 'acct' URI // Parse 'acct' URI
// https://datatracker.ietf.org/doc/html/rfc7565#section-7 // https://datatracker.ietf.org/doc/html/rfc7565#section-7
// See also: USERNAME_RE in models::profiles::validators // See also: ACTOR_ADDRESS_RE in activitypub::actors::types
let uri_regexp = Regex::new(r"acct:(?P<user>[\w\.-]+)@(?P<instance>.+)").unwrap(); let uri_regexp = Regex::new(r"acct:(?P<user>[\w\.-]+)@(?P<instance>.+)").unwrap();
let uri_caps = uri_regexp.captures(&query_params.resource) let uri_caps = uri_regexp.captures(&query_params.resource)
.ok_or(ValidationError("invalid query target"))?; .ok_or(ValidationError("invalid query target"))?;