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