Fix binary collation issues for privs as well

Ref #66
This commit is contained in:
Oystein Kristoffer Tveit 2024-08-19 17:44:21 +02:00
parent f43499fca0
commit 20669569f3
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
4 changed files with 31 additions and 27 deletions

View File

@ -14,8 +14,8 @@ use crate::server::sql::database_privilege_operations::{
pub fn db_priv_field_human_readable_name(name: &str) -> String { pub fn db_priv_field_human_readable_name(name: &str) -> String {
match name { match name {
"db" => "Database".to_owned(), "Db" => "Database".to_owned(),
"user" => "User".to_owned(), "User" => "User".to_owned(),
"select_priv" => "Select".to_owned(), "select_priv" => "Select".to_owned(),
"insert_priv" => "Insert".to_owned(), "insert_priv" => "Insert".to_owned(),
"update_priv" => "Update".to_owned(), "update_priv" => "Update".to_owned(),
@ -128,8 +128,8 @@ pub fn format_privileges_line_for_editor(
DATABASE_PRIVILEGE_FIELDS DATABASE_PRIVILEGE_FIELDS
.into_iter() .into_iter()
.map(|field| match field { .map(|field| match field {
"db" => format!("{:width$}", privs.db, width = database_name_len), "Db" => format!("{:width$}", privs.db, width = database_name_len),
"user" => format!("{:width$}", privs.user, width = username_len), "User" => format!("{:width$}", privs.user, width = username_len),
privilege => format!( privilege => format!(
"{:width$}", "{:width$}",
yn(privs.get_privilege_by_name(privilege)), yn(privs.get_privilege_by_name(privilege)),

View File

@ -1,4 +1,5 @@
use crate::core::common::UnixUser; use crate::core::common::UnixUser;
use sqlx::prelude::*;
/// This function creates a regex that matches items (users, databases) /// This function creates a regex that matches items (users, databases)
/// that belong to the user or any of the user's groups. /// that belong to the user or any of the user's groups.
@ -9,3 +10,17 @@ pub fn create_user_group_matching_regex(user: &UnixUser) -> String {
format!("({}|{})(_.+)?", user.username, user.groups.join("|")) format!("({}|{})(_.+)?", user.username, user.groups.join("|"))
} }
} }
/// Some mysql versions with some collations mark some columns as binary fields,
/// which in the current version of sqlx is not parsable as string.
/// See: https://github.com/launchbadge/sqlx/issues/3387
#[inline]
pub fn try_get_with_binary_fallback(
row: &sqlx::mysql::MySqlRow,
column: &str,
) -> Result<String, sqlx::Error> {
row.try_get(column).or_else(|_| {
row.try_get::<Vec<u8>, _>(column)
.map(|v| String::from_utf8_lossy(&v).to_string())
})
}

View File

@ -32,7 +32,7 @@ use crate::{
}, },
}, },
server::{ server::{
common::create_user_group_matching_regex, common::{create_user_group_matching_regex, try_get_with_binary_fallback},
input_sanitization::{quote_identifier, validate_name, validate_ownership_by_unix_user}, input_sanitization::{quote_identifier, validate_name, validate_ownership_by_unix_user},
sql::database_operations::unsafe_database_exists, sql::database_operations::unsafe_database_exists,
}, },
@ -42,8 +42,8 @@ use crate::{
/// from the `db` table in the database. If you need to add or remove privilege /// from the `db` table in the database. If you need to add or remove privilege
/// fields, this is a good place to start. /// fields, this is a good place to start.
pub const DATABASE_PRIVILEGE_FIELDS: [&str; 13] = [ pub const DATABASE_PRIVILEGE_FIELDS: [&str; 13] = [
"db", "Db",
"user", "User",
"select_priv", "select_priv",
"insert_priv", "insert_priv",
"update_priv", "update_priv",
@ -97,6 +97,8 @@ impl DatabasePrivilegeRow {
} }
} }
// TODO: get by name instead of row tuple position
#[inline] #[inline]
fn get_mysql_row_priv_field(row: &MySqlRow, position: usize) -> Result<bool, sqlx::Error> { fn get_mysql_row_priv_field(row: &MySqlRow, position: usize) -> Result<bool, sqlx::Error> {
let field = DATABASE_PRIVILEGE_FIELDS[position]; let field = DATABASE_PRIVILEGE_FIELDS[position];
@ -113,8 +115,8 @@ fn get_mysql_row_priv_field(row: &MySqlRow, position: usize) -> Result<bool, sql
impl FromRow<'_, MySqlRow> for DatabasePrivilegeRow { impl FromRow<'_, MySqlRow> for DatabasePrivilegeRow {
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> { fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
Ok(Self { Ok(Self {
db: row.try_get("db")?, db: try_get_with_binary_fallback(row, "Db")?,
user: row.try_get("user")?, user: try_get_with_binary_fallback(row, "User")?,
select_priv: get_mysql_row_priv_field(row, 2)?, select_priv: get_mysql_row_priv_field(row, 2)?,
insert_priv: get_mysql_row_priv_field(row, 3)?, insert_priv: get_mysql_row_priv_field(row, 3)?,
update_priv: get_mysql_row_priv_field(row, 4)?, update_priv: get_mysql_row_priv_field(row, 4)?,
@ -137,7 +139,7 @@ async fn unsafe_get_database_privileges(
connection: &mut MySqlConnection, connection: &mut MySqlConnection,
) -> Result<Vec<DatabasePrivilegeRow>, sqlx::Error> { ) -> Result<Vec<DatabasePrivilegeRow>, sqlx::Error> {
let result = sqlx::query_as::<_, DatabasePrivilegeRow>(&format!( let result = sqlx::query_as::<_, DatabasePrivilegeRow>(&format!(
"SELECT {} FROM `db` WHERE `db` = ?", "SELECT {} FROM `db` WHERE `Db` = ?",
DATABASE_PRIVILEGE_FIELDS DATABASE_PRIVILEGE_FIELDS
.iter() .iter()
.map(|field| quote_identifier(field)) .map(|field| quote_identifier(field))
@ -166,7 +168,7 @@ pub async fn unsafe_get_database_privileges_for_db_user_pair(
connection: &mut MySqlConnection, connection: &mut MySqlConnection,
) -> Result<Option<DatabasePrivilegeRow>, sqlx::Error> { ) -> Result<Option<DatabasePrivilegeRow>, sqlx::Error> {
let result = sqlx::query_as::<_, DatabasePrivilegeRow>(&format!( let result = sqlx::query_as::<_, DatabasePrivilegeRow>(&format!(
"SELECT {} FROM `db` WHERE `db` = ? AND `user` = ?", "SELECT {} FROM `db` WHERE `Db` = ? AND `User` = ?",
DATABASE_PRIVILEGE_FIELDS DATABASE_PRIVILEGE_FIELDS
.iter() .iter()
.map(|field| quote_identifier(field)) .map(|field| quote_identifier(field))
@ -316,7 +318,7 @@ async fn unsafe_apply_privilege_diff(
.join(","); .join(",");
sqlx::query( sqlx::query(
format!("UPDATE `db` SET {} WHERE `db` = ? AND `user` = ?", changes).as_str(), format!("UPDATE `db` SET {} WHERE `Db` = ? AND `User` = ?", changes).as_str(),
) )
.bind(p.db.to_string()) .bind(p.db.to_string())
.bind(p.user.to_string()) .bind(p.user.to_string())
@ -325,7 +327,7 @@ async fn unsafe_apply_privilege_diff(
.map(|_| ()) .map(|_| ())
} }
DatabasePrivilegesDiff::Deleted(p) => { DatabasePrivilegesDiff::Deleted(p) => {
sqlx::query("DELETE FROM `db` WHERE `db` = ? AND `user` = ?") sqlx::query("DELETE FROM `db` WHERE `Db` = ? AND `User` = ?")
.bind(p.db.to_string()) .bind(p.db.to_string())
.bind(p.user.to_string()) .bind(p.user.to_string())
.execute(connection) .execute(connection)

View File

@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
use sqlx::prelude::*; use sqlx::prelude::*;
use sqlx::MySqlConnection; use sqlx::MySqlConnection;
use crate::server::common::try_get_with_binary_fallback;
use crate::{ use crate::{
core::{ core::{
common::UnixUser, common::UnixUser,
@ -350,20 +351,6 @@ pub struct DatabaseUser {
pub databases: Vec<String>, pub databases: Vec<String>,
} }
/// Some mysql versions with some collations mark some columns as binary fields,
/// which in the current version of sqlx is not parsable as string.
/// See: https://github.com/launchbadge/sqlx/issues/3387
#[inline]
fn try_get_with_binary_fallback(
row: &sqlx::mysql::MySqlRow,
column: &str,
) -> Result<String, sqlx::Error> {
row.try_get(column).or_else(|_| {
row.try_get::<Vec<u8>, _>(column)
.map(|v| String::from_utf8_lossy(&v).to_string())
})
}
impl FromRow<'_, sqlx::mysql::MySqlRow> for DatabaseUser { impl FromRow<'_, sqlx::mysql::MySqlRow> for DatabaseUser {
fn from_row(row: &sqlx::mysql::MySqlRow) -> Result<Self, sqlx::Error> { fn from_row(row: &sqlx::mysql::MySqlRow) -> Result<Self, sqlx::Error> {
Ok(Self { Ok(Self {