1pub(crate) mod static_user_provider;
16pub(crate) mod watch_file_user_provider;
17
18use std::collections::HashMap;
19use std::fs::File;
20use std::io;
21use std::io::BufRead;
22use std::path::Path;
23
24use common_base::secrets::ExposeSecret;
25use snafu::{OptionExt, ResultExt, ensure};
26
27use crate::common::{Identity, Password};
28use crate::error::{
29 IllegalParamSnafu, InvalidConfigSnafu, IoSnafu, Result, UnsupportedPasswordTypeSnafu,
30 UserNotFoundSnafu, UserPasswordMismatchSnafu,
31};
32use crate::user_info::{DefaultUserInfo, PermissionMode};
33use crate::{UserInfoRef, auth_mysql};
34
35#[async_trait::async_trait]
36pub trait UserProvider: Send + Sync {
37 fn name(&self) -> &str;
38
39 async fn authenticate(&self, id: Identity<'_>, password: Password<'_>) -> Result<UserInfoRef>;
41
42 async fn authorize(&self, catalog: &str, schema: &str, user_info: &UserInfoRef) -> Result<()>;
46
47 async fn auth(
50 &self,
51 id: Identity<'_>,
52 password: Password<'_>,
53 catalog: &str,
54 schema: &str,
55 ) -> Result<UserInfoRef> {
56 let user_info = self.authenticate(id, password).await?;
57 self.authorize(catalog, schema, &user_info).await?;
58 Ok(user_info)
59 }
60
61 fn external(&self) -> bool {
63 false
64 }
65}
66
67pub type UserInfoMap = HashMap<String, (Vec<u8>, PermissionMode)>;
70
71fn load_credential_from_file(filepath: &str) -> Result<UserInfoMap> {
72 let path = Path::new(filepath);
74 if !path.exists() {
75 return InvalidConfigSnafu {
76 value: filepath.to_string(),
77 msg: "UserProvider file must exist",
78 }
79 .fail();
80 }
81
82 ensure!(
83 path.is_file(),
84 InvalidConfigSnafu {
85 value: filepath,
86 msg: "UserProvider file must be a file",
87 }
88 );
89 let file = File::open(path).context(IoSnafu)?;
90 let credential = io::BufReader::new(file)
91 .lines()
92 .map_while(std::result::Result::ok)
93 .filter_map(|line| {
94 let line = line.trim();
100 if line.is_empty() || line.starts_with('#') {
101 return None;
102 }
103
104 parse_credential_line(line)
105 })
106 .collect::<HashMap<String, _>>();
107
108 ensure!(
109 !credential.is_empty(),
110 InvalidConfigSnafu {
111 value: filepath,
112 msg: "UserProvider's file must contains at least one valid credential",
113 }
114 );
115
116 Ok(credential)
117}
118
119pub(crate) fn parse_credential_line(line: &str) -> Option<(String, (Vec<u8>, PermissionMode))> {
121 let parts = line.split('=').collect::<Vec<&str>>();
122 if parts.len() != 2 {
123 return None;
124 }
125
126 let (username_part, password) = (parts[0], parts[1]);
127 let (username, permission_mode) = if let Some((user, perm)) = username_part.split_once(':') {
128 (user, PermissionMode::from_str(perm))
129 } else {
130 (username_part, PermissionMode::default())
131 };
132
133 Some((
134 username.to_string(),
135 (password.as_bytes().to_vec(), permission_mode),
136 ))
137}
138
139fn authenticate_with_credential(
140 users: &UserInfoMap,
141 input_id: Identity<'_>,
142 input_pwd: Password<'_>,
143) -> Result<UserInfoRef> {
144 match input_id {
145 Identity::UserId(username, _) => {
146 ensure!(
147 !username.is_empty(),
148 IllegalParamSnafu {
149 msg: "blank username"
150 }
151 );
152 let (save_pwd, permission_mode) = users.get(username).context(UserNotFoundSnafu {
153 username: username.to_string(),
154 })?;
155
156 match input_pwd {
157 Password::PlainText(pwd) => {
158 ensure!(
159 !pwd.expose_secret().is_empty(),
160 IllegalParamSnafu {
161 msg: "blank password"
162 }
163 );
164 if save_pwd == pwd.expose_secret().as_bytes() {
165 Ok(DefaultUserInfo::with_name_and_permission(
166 username,
167 *permission_mode,
168 ))
169 } else {
170 UserPasswordMismatchSnafu {
171 username: username.to_string(),
172 }
173 .fail()
174 }
175 }
176 Password::MysqlNativePassword(auth_data, salt) => {
177 auth_mysql(auth_data, salt, username, save_pwd).map(|_| {
178 DefaultUserInfo::with_name_and_permission(username, *permission_mode)
179 })
180 }
181 Password::PgMD5(_, _) => UnsupportedPasswordTypeSnafu {
182 password_type: "pg_md5",
183 }
184 .fail(),
185 }
186 }
187 }
188}
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_parse_credential_line() {
195 let result = parse_credential_line("admin=password123");
197 assert_eq!(
198 result,
199 Some((
200 "admin".to_string(),
201 ("password123".as_bytes().to_vec(), PermissionMode::default())
202 ))
203 );
204
205 let result = parse_credential_line("user:ReadOnly=secret");
207 assert_eq!(
208 result,
209 Some((
210 "user".to_string(),
211 ("secret".as_bytes().to_vec(), PermissionMode::ReadOnly)
212 ))
213 );
214 let result = parse_credential_line("user:ro=secret");
215 assert_eq!(
216 result,
217 Some((
218 "user".to_string(),
219 ("secret".as_bytes().to_vec(), PermissionMode::ReadOnly)
220 ))
221 );
222 let result = parse_credential_line("writer:WriteOnly=mypass");
224 assert_eq!(
225 result,
226 Some((
227 "writer".to_string(),
228 ("mypass".as_bytes().to_vec(), PermissionMode::WriteOnly)
229 ))
230 );
231
232 let result = parse_credential_line("writer:wo=mypass");
234 assert_eq!(
235 result,
236 Some((
237 "writer".to_string(),
238 ("mypass".as_bytes().to_vec(), PermissionMode::WriteOnly)
239 ))
240 );
241
242 let result = parse_credential_line("admin:rw=p@ssw0rd!123");
244 assert_eq!(
245 result,
246 Some((
247 "admin".to_string(),
248 (
249 "p@ssw0rd!123".as_bytes().to_vec(),
250 PermissionMode::ReadWrite
251 )
252 ))
253 );
254
255 let result = parse_credential_line("user name:WriteOnly=password");
257 assert_eq!(
258 result,
259 Some((
260 "user name".to_string(),
261 ("password".as_bytes().to_vec(), PermissionMode::WriteOnly)
262 ))
263 );
264
265 let result = parse_credential_line("invalid_line");
267 assert_eq!(result, None);
268
269 let result = parse_credential_line("user=pass=word");
271 assert_eq!(result, None);
272
273 let result = parse_credential_line("user=");
275 assert_eq!(
276 result,
277 Some((
278 "user".to_string(),
279 ("".as_bytes().to_vec(), PermissionMode::default())
280 ))
281 );
282
283 let result = parse_credential_line("=password");
285 assert_eq!(
286 result,
287 Some((
288 "".to_string(),
289 ("password".as_bytes().to_vec(), PermissionMode::default())
290 ))
291 );
292 }
293}