Skip to main content

auth/user_provider/
static_user_provider.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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        // default allow all
77        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            // write a tmp file
160            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}