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