Increase maximum length of display_name to 200 chars and validate it on profile import

This commit is contained in:
silverpill 2021-12-27 19:20:21 +00:00
parent 690edddbc1
commit 1936219b3d
9 changed files with 91 additions and 13 deletions

View file

@ -0,0 +1 @@
ALTER TABLE actor_profile ALTER COLUMN display_name TYPE VARCHAR(200);

View file

@ -1,7 +1,7 @@
CREATE TABLE actor_profile ( CREATE TABLE actor_profile (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
username VARCHAR(100) NOT NULL, username VARCHAR(100) NOT NULL,
display_name VARCHAR(100), display_name VARCHAR(200),
acct VARCHAR(200) UNIQUE NOT NULL, acct VARCHAR(200) UNIQUE NOT NULL,
bio TEXT, bio TEXT,
bio_source TEXT, bio_source TEXT,

View file

@ -135,10 +135,25 @@ pub enum ImportError {
#[error(transparent)] #[error(transparent)]
FetchError(#[from] FetchError), FetchError(#[from] FetchError),
#[error(transparent)]
ValidationError(#[from] ValidationError),
#[error(transparent)] #[error(transparent)]
DatabaseError(#[from] DatabaseError), DatabaseError(#[from] DatabaseError),
} }
impl From<ImportError> for HttpError {
fn from(error: ImportError) -> Self {
match error {
ImportError::FetchError(error) => {
HttpError::ValidationError(error.to_string())
},
ImportError::ValidationError(error) => error.into(),
ImportError::DatabaseError(error) => error.into(),
}
}
}
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, instance: &Instance,
@ -187,6 +202,7 @@ pub async fn import_profile_by_actor_address(
}; };
}; };
log::info!("fetched profile {}", profile_data.acct); log::info!("fetched profile {}", profile_data.acct);
profile_data.clean()?;
let profile = create_profile(db_client, &profile_data).await?; let profile = create_profile(db_client, &profile_data).await?;
Ok(profile) Ok(profile)
} }
@ -328,14 +344,14 @@ pub async fn process_note(
&actor_address, &actor_address,
).await { ).await {
Ok(profile) => profile, Ok(profile) => profile,
Err(ImportError::DatabaseError(error)) => {
return Err(error.into());
},
Err(ImportError::FetchError(error)) => { Err(ImportError::FetchError(error)) => {
// Ignore mention if fetcher fails // Ignore mention if fetcher fails
log::warn!("{}", error); log::warn!("{}", error);
continue; continue;
}, },
Err(other_error) => {
return Err(other_error.into());
},
} }
}, },
Err(other_error) => return Err(other_error.into()), Err(other_error) => return Err(other_error.into()),

View file

@ -58,7 +58,7 @@ async fn create_status(
let current_user = get_current_user(db_client, auth.token()).await?; let current_user = get_current_user(db_client, auth.token()).await?;
let instance = config.instance(); let instance = config.instance();
let mut post_data = PostCreateData::from(data.into_inner()); let mut post_data = PostCreateData::from(data.into_inner());
post_data.validate()?; post_data.clean()?;
// Mentions // Mentions
let mention_map = find_mentioned_profiles( let mention_map = find_mentioned_profiles(
db_client, db_client,

View file

@ -205,7 +205,7 @@ pub struct PostCreateData {
impl PostCreateData { impl PostCreateData {
/// Validate and clean post data. /// Validate and clean post data.
pub fn validate(&mut self) -> Result<(), ValidationError> { pub fn clean(&mut self) -> Result<(), ValidationError> {
let content_safe = clean_html(&self.content); let content_safe = clean_html(&self.content);
let content_trimmed = content_safe.trim(); let content_trimmed = content_safe.trim();
if content_trimmed.is_empty() { if content_trimmed.is_empty() {
@ -233,7 +233,7 @@ mod tests {
object_id: None, object_id: None,
created_at: None, created_at: None,
}; };
assert_eq!(post_data_1.validate().is_ok(), false); assert_eq!(post_data_1.clean().is_ok(), false);
} }
#[test] #[test]
@ -249,7 +249,7 @@ mod tests {
object_id: None, object_id: None,
created_at: None, created_at: None,
}; };
assert_eq!(post_data_2.validate().is_ok(), true); assert_eq!(post_data_2.clean().is_ok(), true);
assert_eq!(post_data_2.content.as_str(), "test"); assert_eq!(post_data_2.content.as_str(), "test");
} }
} }

View file

@ -1,2 +1,3 @@
pub mod queries; pub mod queries;
pub mod types; pub mod types;
pub mod validators;

View file

@ -11,6 +11,10 @@ use uuid::Uuid;
use crate::activitypub::views::get_actor_url; use crate::activitypub::views::get_actor_url;
use crate::errors::{ConversionError, ValidationError}; use crate::errors::{ConversionError, ValidationError};
use crate::utils::html::clean_html; use crate::utils::html::clean_html;
use super::validators::{
validate_username,
validate_display_name,
};
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ExtraField { pub struct ExtraField {
@ -137,6 +141,14 @@ pub struct ProfileCreateData {
pub actor: Option<Value>, pub actor: Option<Value>,
} }
impl ProfileCreateData {
pub fn clean(&self) -> Result<(), ValidationError> {
validate_username(&self.username)?;
validate_display_name(self.display_name.as_ref())?;
Ok(())
}
}
pub struct ProfileUpdateData { pub struct ProfileUpdateData {
pub display_name: Option<String>, pub display_name: Option<String>,
pub bio: Option<String>, pub bio: Option<String>,
@ -169,7 +181,7 @@ impl ProfileUpdateData {
unique_labels.dedup(); unique_labels.dedup();
if unique_labels.len() < self.extra_fields.len() { if unique_labels.len() < self.extra_fields.len() {
return Err(ValidationError("duplicate labels")); return Err(ValidationError("duplicate labels"));
} };
Ok(()) Ok(())
} }
} }

View file

@ -0,0 +1,46 @@
use crate::errors::ValidationError;
pub fn validate_username(username: &str) -> Result<(), ValidationError> {
if username.len() > 100 {
return Err(ValidationError("username is too long"));
};
Ok(())
}
pub fn validate_display_name(display_name: Option<&String>)
-> Result<(), ValidationError>
{
if let Some(display_name) = display_name {
if display_name.len() > 200 {
return Err(ValidationError("display name is too long"));
};
};
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_username() {
let result_1 = validate_username("test");
assert!(result_1.is_ok());
let result_2 = validate_username(&"x".repeat(101));
assert!(result_2.is_err());
}
#[test]
fn test_validate_display_name() {
let display_name = "test".to_string();
let result_1 = validate_display_name(Some(&display_name));
assert!(result_1.is_ok());
let result_2 = validate_display_name(None);
assert!(result_2.is_ok());
let display_name = "x".repeat(201);
let result_3 = validate_display_name(Some(&display_name));
assert!(result_3.is_err());
}
}

View file

@ -6,6 +6,7 @@ use uuid::Uuid;
use crate::errors::ValidationError; use crate::errors::ValidationError;
use crate::models::profiles::types::DbActorProfile; use crate::models::profiles::types::DbActorProfile;
use crate::models::profiles::validators::validate_username;
#[derive(FromSql)] #[derive(FromSql)]
#[postgres(name = "user_account")] #[postgres(name = "user_account")]
@ -53,7 +54,7 @@ pub struct UserCreateData {
pub invite_code: Option<String>, pub invite_code: Option<String>,
} }
fn validate_username(username: &str) -> Result<(), ValidationError> { fn validate_local_username(username: &str) -> Result<(), ValidationError> {
// The username regexp should not allow domain names and IP addresses // The username regexp should not allow domain names and IP addresses
let username_regexp = Regex::new(r"^[a-z0-9_]+$").unwrap(); let username_regexp = Regex::new(r"^[a-z0-9_]+$").unwrap();
if !username_regexp.is_match(username) { if !username_regexp.is_match(username) {
@ -66,6 +67,7 @@ impl UserCreateData {
/// Validate and clean. /// Validate and clean.
pub fn clean(&self) -> Result<(), ValidationError> { pub fn clean(&self) -> Result<(), ValidationError> {
validate_username(&self.username)?; validate_username(&self.username)?;
validate_local_username(&self.username)?;
Ok(()) Ok(())
} }
} }
@ -75,10 +77,10 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_validate_username() { fn test_validate_local_username() {
let result_1 = validate_username("name_1"); let result_1 = validate_local_username("name_1");
assert_eq!(result_1.is_ok(), true); assert_eq!(result_1.is_ok(), true);
let result_2 = validate_username("name&"); let result_2 = validate_local_username("name&");
assert_eq!(result_2.is_ok(), false); assert_eq!(result_2.is_ok(), false);
} }
} }