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, ResultExt};
17
18use crate::error::{FromUtf8Snafu, 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    /// Return a random username/password pair
60    /// This is useful for invoking from other components in the cluster
61    pub fn get_one_user_pwd(&self) -> Result<(String, String)> {
62        let kv = self.users.iter().next().context(InvalidConfigSnafu {
63            value: "",
64            msg: "Expect at least one pair of username and password",
65        })?;
66        let username = kv.0;
67        let pwd = String::from_utf8(kv.1.0.clone()).context(FromUtf8Snafu)?;
68        Ok((username.clone(), pwd))
69    }
70}
71
72#[async_trait]
73impl UserProvider for StaticUserProvider {
74    fn name(&self) -> &str {
75        STATIC_USER_PROVIDER
76    }
77
78    async fn authenticate(&self, id: Identity<'_>, pwd: Password<'_>) -> Result<UserInfoRef> {
79        authenticate_with_credential(&self.users, id, pwd)
80    }
81
82    async fn authorize(
83        &self,
84        _catalog: &str,
85        _schema: &str,
86        _user_info: &UserInfoRef,
87    ) -> Result<()> {
88        // default allow all
89        Ok(())
90    }
91}
92
93#[cfg(test)]
94pub mod test {
95    use std::fs::File;
96    use std::io::{LineWriter, Write};
97
98    use common_test_util::temp_dir::create_temp_dir;
99
100    use crate::UserProvider;
101    use crate::user_info::DefaultUserInfo;
102    use crate::user_provider::static_user_provider::StaticUserProvider;
103    use crate::user_provider::{Identity, Password};
104
105    async fn test_authenticate(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        let _ = re.unwrap();
113    }
114
115    #[tokio::test]
116    async fn test_authorize() {
117        let user_info = DefaultUserInfo::with_name("root");
118        let provider = StaticUserProvider::new("cmd:root=123456,admin=654321").unwrap();
119        provider
120            .authorize("catalog", "schema", &user_info)
121            .await
122            .unwrap();
123    }
124
125    #[tokio::test]
126    async fn test_inline_provider() {
127        let provider = StaticUserProvider::new("cmd:root=123456,admin=654321").unwrap();
128        test_authenticate(&provider, "root", "123456").await;
129        test_authenticate(&provider, "admin", "654321").await;
130    }
131
132    #[tokio::test]
133    async fn test_file_provider() {
134        let dir = create_temp_dir("test_file_provider");
135        let file_path = format!("{}/test_file_provider", dir.path().to_str().unwrap());
136        {
137            // write a tmp file
138            let file = File::create(&file_path);
139            let file = file.unwrap();
140            let mut lw = LineWriter::new(file);
141            assert!(
142                lw.write_all(
143                    b"root=123456
144admin=654321",
145                )
146                .is_ok()
147            );
148            lw.flush().unwrap();
149        }
150
151        let param = format!("file:{file_path}");
152        let provider = StaticUserProvider::new(param.as_str()).unwrap();
153        test_authenticate(&provider, "root", "123456").await;
154        test_authenticate(&provider, "admin", "654321").await;
155    }
156}