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    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        // default allow all
85        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            // write a tmp file
170            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}