auth/user_provider/
static_user_provider.rs1use async_trait::async_trait;
16use snafu::OptionExt;
17
18use crate::error::{InvalidConfigSnafu, Result};
19use crate::user_provider::{
20 UserInfoMap, authenticate_with_credential, load_credential_from_file, parse_credential_line,
21};
22use crate::{Identity, Password, UserInfoRef, UserProvider};
23
24pub(crate) const STATIC_USER_PROVIDER: &str = "static_user_provider";
25
26pub struct StaticUserProvider {
27 users: UserInfoMap,
28}
29
30impl StaticUserProvider {
31 pub(crate) fn new(value: &str) -> Result<Self> {
32 let (mode, content) = value.split_once(':').context(InvalidConfigSnafu {
33 value: value.to_string(),
34 msg: "StaticUserProviderOption must be in format `<option>:<value>`",
35 })?;
36 match mode {
37 "file" => {
38 let users = load_credential_from_file(content)?;
39 Ok(StaticUserProvider { users })
40 }
41 "cmd" => content
42 .split(',')
43 .map(|kv| {
44 parse_credential_line(kv).context(InvalidConfigSnafu {
45 value: kv.to_string(),
46 msg: "StaticUserProviderOption cmd values must be in format `user=pwd[,user=pwd]`",
47 })
48 })
49 .collect::<Result<UserInfoMap>>()
50 .map(|users| StaticUserProvider { users }),
51 _ => InvalidConfigSnafu {
52 value: mode.to_string(),
53 msg: "StaticUserProviderOption must be in format `file:<path>` or `cmd:<values>`",
54 }
55 .fail(),
56 }
57 }
58}
59
60#[async_trait]
61impl UserProvider for StaticUserProvider {
62 fn name(&self) -> &str {
63 STATIC_USER_PROVIDER
64 }
65
66 async fn authenticate(&self, id: Identity<'_>, pwd: Password<'_>) -> Result<UserInfoRef> {
67 authenticate_with_credential(&self.users, id, pwd)
68 }
69
70 async fn authorize(
71 &self,
72 _catalog: &str,
73 _schema: &str,
74 _user_info: &UserInfoRef,
75 ) -> Result<()> {
76 Ok(())
78 }
79}
80
81#[cfg(test)]
82pub mod test {
83 use std::fs::File;
84 use std::io::{LineWriter, Write};
85
86 use common_test_util::temp_dir::create_temp_dir;
87 use pbkdf2::pbkdf2_hmac;
88 use sha2::Sha256;
89
90 use crate::UserProvider;
91 use crate::user_info::DefaultUserInfo;
92 use crate::user_provider::static_user_provider::StaticUserProvider;
93 use crate::user_provider::{Identity, Password};
94
95 async fn test_authenticate(provider: &dyn UserProvider, username: &str, password: &str) {
96 let re = provider
97 .authenticate(
98 Identity::UserId(username, None),
99 Password::PlainText(password.to_string().into()),
100 )
101 .await;
102 let _ = re.unwrap();
103 }
104
105 async fn test_authenticate_fails(provider: &dyn UserProvider, username: &str, password: &str) {
106 let re = provider
107 .authenticate(
108 Identity::UserId(username, None),
109 Password::PlainText(password.to_string().into()),
110 )
111 .await;
112 assert!(re.is_err());
113 }
114
115 fn pbkdf2_sha256_verifier(password: &str) -> String {
116 let iterations = 4096;
117 let salt = b"salt";
118 let mut hash = [0u8; 32];
119 pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, iterations, &mut hash);
120 format!(
121 "pbkdf2_sha256:{iterations}:{}:{}",
122 hex::encode(salt),
123 hex::encode(hash)
124 )
125 }
126
127 #[tokio::test]
128 async fn test_authorize() {
129 let user_info = DefaultUserInfo::with_name("root");
130 let provider = StaticUserProvider::new("cmd:root=123456,admin=654321").unwrap();
131 provider
132 .authorize("catalog", "schema", &user_info)
133 .await
134 .unwrap();
135 }
136
137 #[tokio::test]
138 async fn test_inline_provider() {
139 let provider = StaticUserProvider::new("cmd:root=123456,admin=654321").unwrap();
140 test_authenticate(&provider, "root", "123456").await;
141 test_authenticate(&provider, "admin", "654321").await;
142 }
143
144 #[tokio::test]
145 async fn test_inline_provider_with_pbkdf2_sha256_verifier() {
146 let provider =
147 StaticUserProvider::new(&format!("cmd:root={}", pbkdf2_sha256_verifier("123456")))
148 .unwrap();
149
150 test_authenticate(&provider, "root", "123456").await;
151 test_authenticate_fails(&provider, "root", "654321").await;
152 }
153
154 #[tokio::test]
155 async fn test_file_provider() {
156 let dir = create_temp_dir("test_file_provider");
157 let file_path = format!("{}/test_file_provider", dir.path().to_str().unwrap());
158 {
159 let file = File::create(&file_path);
161 let file = file.unwrap();
162 let mut lw = LineWriter::new(file);
163 assert!(
164 lw.write_all(
165 b"root=123456
166admin=654321",
167 )
168 .is_ok()
169 );
170 lw.flush().unwrap();
171 }
172
173 let param = format!("file:{file_path}");
174 let provider = StaticUserProvider::new(param.as_str()).unwrap();
175 test_authenticate(&provider, "root", "123456").await;
176 test_authenticate(&provider, "admin", "654321").await;
177 }
178}