risingwave_frontend/user/
user_authentication.rsuse std::collections::HashMap;
use risingwave_pb::user::auth_info::EncryptionType;
use risingwave_pb::user::AuthInfo;
use risingwave_sqlparser::ast::SqlOption;
use sha2::{Digest, Sha256};
use crate::WithOptions;
const SHA256_ENCRYPTED_PREFIX: &str = "SHA-256:";
const MD5_ENCRYPTED_PREFIX: &str = "md5";
const VALID_SHA256_ENCRYPTED_LEN: usize = SHA256_ENCRYPTED_PREFIX.len() + 64;
const VALID_MD5_ENCRYPTED_LEN: usize = MD5_ENCRYPTED_PREFIX.len() + 32;
pub const OAUTH_JWKS_URL_KEY: &str = "jwks_url";
pub const OAUTH_ISSUER_KEY: &str = "issuer";
#[inline(always)]
pub fn build_oauth_info(options: &Vec<SqlOption>) -> Option<AuthInfo> {
let metadata: HashMap<String, String> = WithOptions::oauth_options_to_map(options.as_slice())
.ok()?
.into_iter()
.collect();
if !metadata.contains_key(OAUTH_JWKS_URL_KEY) || !metadata.contains_key(OAUTH_ISSUER_KEY) {
return None;
}
Some(AuthInfo {
encryption_type: EncryptionType::Oauth as i32,
encrypted_value: Vec::new(),
metadata,
})
}
#[inline(always)]
pub fn encrypted_password(name: &str, password: &str) -> Option<AuthInfo> {
if password.is_empty() {
return None;
}
if valid_sha256_password(password) {
Some(AuthInfo {
encryption_type: EncryptionType::Sha256 as i32,
encrypted_value: password.trim_start_matches(SHA256_ENCRYPTED_PREFIX).into(),
metadata: HashMap::new(),
})
} else if valid_md5_password(password) {
Some(AuthInfo {
encryption_type: EncryptionType::Md5 as i32,
encrypted_value: password.trim_start_matches(MD5_ENCRYPTED_PREFIX).into(),
metadata: HashMap::new(),
})
} else {
Some(encrypt_default(name, password))
}
}
#[inline(always)]
fn encrypt_default(name: &str, password: &str) -> AuthInfo {
AuthInfo {
encryption_type: EncryptionType::Md5 as i32,
encrypted_value: md5_hash(name, password),
metadata: HashMap::new(),
}
}
pub fn encrypted_raw_password(info: &AuthInfo) -> String {
let encrypted_pwd = String::from_utf8(info.encrypted_value.clone()).unwrap();
let prefix = match info.get_encryption_type().unwrap() {
EncryptionType::Unspecified => unreachable!(),
EncryptionType::Plaintext => "",
EncryptionType::Sha256 => SHA256_ENCRYPTED_PREFIX,
EncryptionType::Md5 => MD5_ENCRYPTED_PREFIX,
EncryptionType::Oauth => "",
};
format!("{}{}", prefix, encrypted_pwd)
}
#[inline(always)]
pub fn md5_hash_with_salt(encrypted_value: &[u8], salt: &[u8; 4]) -> Vec<u8> {
let mut ctx = md5::Context::new();
ctx.consume(encrypted_value);
ctx.consume(salt);
format!("md5{:x}", ctx.compute()).into_bytes()
}
#[inline(always)]
fn valid_sha256_password(password: &str) -> bool {
password.starts_with(SHA256_ENCRYPTED_PREFIX) && password.len() == VALID_SHA256_ENCRYPTED_LEN
}
#[inline(always)]
fn valid_md5_password(password: &str) -> bool {
password.starts_with(MD5_ENCRYPTED_PREFIX) && password.len() == VALID_MD5_ENCRYPTED_LEN
}
#[cfg_attr(not(test), expect(dead_code))]
#[inline(always)]
pub fn sha256_hash(name: &str, password: &str) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(password.as_bytes());
hasher.update(name.as_bytes());
format!("{:x}", hasher.finalize()).into_bytes()
}
#[inline(always)]
pub fn md5_hash(name: &str, password: &str) -> Vec<u8> {
let mut ctx = md5::Context::new();
ctx.consume(password.as_bytes());
ctx.consume(name.as_bytes());
format!("{:x}", ctx.compute()).into_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encrypt_password() {
let (user_name, password) = ("foo", "bar");
assert_eq!(
b"96948aad3fcae80c08a35c9b5958cd89".to_vec(),
md5_hash(user_name, password)
);
assert_eq!(
b"md59f2fa6a30871a92249bdd2f1eeee4ef6".to_vec(),
md5_hash_with_salt(
b"96948aad3fcae80c08a35c9b5958cd89",
&[0x1a, 0x2b, 0x3d, 0x4e]
)
);
assert_eq!(
b"88ecde925da3c6f8ec3d140683da9d2a422f26c1ae1d9212da1e5a53416dcc88".to_vec(),
sha256_hash(user_name, password)
);
let input_passwords = [
"bar",
"",
"md596948aad3fcae80c08a35c9b5958cd89",
"SHA-256:88ecde925da3c6f8ec3d140683da9d2a422f26c1ae1d9212da1e5a53416dcc88",
];
let expected_output_passwords = vec![
Some(AuthInfo {
encryption_type: EncryptionType::Md5 as i32,
encrypted_value: md5_hash(user_name, password),
metadata: HashMap::new(),
}),
None,
Some(AuthInfo {
encryption_type: EncryptionType::Md5 as i32,
encrypted_value: md5_hash(user_name, password),
metadata: HashMap::new(),
}),
Some(AuthInfo {
encryption_type: EncryptionType::Sha256 as i32,
encrypted_value: sha256_hash(user_name, password),
metadata: HashMap::new(),
}),
];
let output_passwords = input_passwords
.iter()
.map(|&p| encrypted_password(user_name, p))
.collect::<Vec<_>>();
assert_eq!(output_passwords, expected_output_passwords);
}
}