Rewrite entire codebase to split into client and server
This commit was merged in pull request #55.
This commit is contained in:
158
src/server/input_sanitization.rs
Normal file
158
src/server/input_sanitization.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use crate::core::{
|
||||
common::UnixUser,
|
||||
protocol::server_responses::{NameValidationError, OwnerValidationError},
|
||||
};
|
||||
|
||||
const MAX_NAME_LENGTH: usize = 64;
|
||||
|
||||
pub fn validate_name(name: &str) -> Result<(), NameValidationError> {
|
||||
if name.is_empty() {
|
||||
Err(NameValidationError::EmptyString)
|
||||
} else if name.len() > MAX_NAME_LENGTH {
|
||||
Err(NameValidationError::TooLong)
|
||||
} else if !name
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
|
||||
{
|
||||
Err(NameValidationError::InvalidCharacters)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_ownership_by_unix_user(
|
||||
name: &str,
|
||||
user: &UnixUser,
|
||||
) -> Result<(), OwnerValidationError> {
|
||||
let prefixes = std::iter::once(user.username.clone())
|
||||
.chain(user.groups.iter().cloned())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
validate_ownership_by_prefixes(name, &prefixes)
|
||||
}
|
||||
|
||||
/// Core logic for validating the ownership of a database name.
|
||||
/// This function checks if the given name matches any of the given prefixes.
|
||||
/// These prefixes will in most cases be the user's unix username and any
|
||||
/// unix groups the user is a member of.
|
||||
pub fn validate_ownership_by_prefixes(
|
||||
name: &str,
|
||||
prefixes: &[String],
|
||||
) -> Result<(), OwnerValidationError> {
|
||||
if name.is_empty() {
|
||||
return Err(OwnerValidationError::StringEmpty);
|
||||
}
|
||||
|
||||
if name.starts_with('_') {
|
||||
return Err(OwnerValidationError::MissingPrefix);
|
||||
}
|
||||
|
||||
let (prefix, _) = match name.split_once('_') {
|
||||
Some(pair) => pair,
|
||||
None => return Err(OwnerValidationError::MissingPostfix),
|
||||
};
|
||||
|
||||
if !prefixes.iter().any(|g| g == prefix) {
|
||||
return Err(OwnerValidationError::NoMatch);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn quote_literal(s: &str) -> String {
|
||||
format!("'{}'", s.replace('\'', r"\'"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn quote_identifier(s: &str) -> String {
|
||||
format!("`{}`", s.replace('`', r"\`"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_quote_literal() {
|
||||
let payload = "' OR 1=1 --";
|
||||
assert_eq!(quote_literal(payload), r#"'\' OR 1=1 --'"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_identifier() {
|
||||
let payload = "` OR 1=1 --";
|
||||
assert_eq!(quote_identifier(payload), r#"`\` OR 1=1 --`"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_name() {
|
||||
assert_eq!(validate_name(""), Err(NameValidationError::EmptyString));
|
||||
assert_eq!(validate_name("abcdefghijklmnopqrstuvwxyz"), Ok(()));
|
||||
assert_eq!(validate_name("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), Ok(()));
|
||||
assert_eq!(validate_name("0123456789_-"), Ok(()));
|
||||
|
||||
for c in "\n\t\r !@#$%^&*()+=[]{}|;:,.<>?/".chars() {
|
||||
assert_eq!(
|
||||
validate_name(&c.to_string()),
|
||||
Err(NameValidationError::InvalidCharacters)
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(validate_name(&"a".repeat(MAX_NAME_LENGTH)), Ok(()));
|
||||
|
||||
assert_eq!(
|
||||
validate_name(&"a".repeat(MAX_NAME_LENGTH + 1)),
|
||||
Err(NameValidationError::TooLong)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_owner_by_prefixes() {
|
||||
let prefixes = vec!["user".to_string(), "group".to_string()];
|
||||
|
||||
assert_eq!(
|
||||
validate_ownership_by_prefixes("", &prefixes),
|
||||
Err(OwnerValidationError::StringEmpty)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
validate_ownership_by_prefixes("user", &prefixes),
|
||||
Err(OwnerValidationError::MissingPostfix)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_ownership_by_prefixes("something", &prefixes),
|
||||
Err(OwnerValidationError::MissingPostfix)
|
||||
);
|
||||
assert_eq!(
|
||||
validate_ownership_by_prefixes("user-testdb", &prefixes),
|
||||
Err(OwnerValidationError::MissingPostfix)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
validate_ownership_by_prefixes("_testdb", &prefixes),
|
||||
Err(OwnerValidationError::MissingPrefix)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
validate_ownership_by_prefixes("user_testdb", &prefixes),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
validate_ownership_by_prefixes("group_testdb", &prefixes),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
validate_ownership_by_prefixes("group_test_db", &prefixes),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
validate_ownership_by_prefixes("group_test-db", &prefixes),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
validate_ownership_by_prefixes("nonexistent_testdb", &prefixes),
|
||||
Err(OwnerValidationError::NoMatch)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user