Skip to main content

auth/
permission.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 std::fmt::Debug;
16use std::sync::Arc;
17
18use api::v1::greptime_request::Request;
19use api::v1::query_request::Query;
20use common_telemetry::debug;
21use sql::statements::statement::Statement;
22
23use crate::error::{PermissionDeniedSnafu, Result};
24use crate::user_info::DefaultUserInfo;
25use crate::{PermissionCheckerRef, UserInfo, UserInfoRef};
26
27#[derive(Debug, Clone)]
28pub enum PermissionReq<'a> {
29    GrpcRequest(&'a Request),
30    SqlStatement(&'a Statement),
31    PromQuery,
32    LogQuery,
33    Opentsdb,
34    LineProtocol,
35    PromStoreWrite,
36    PromStoreRead,
37    Otlp,
38    LogWrite,
39    BulkInsert {
40        catalog: &'a str,
41        schema: &'a str,
42        table: &'a str,
43    },
44}
45
46impl<'a> PermissionReq<'a> {
47    /// Returns true if the permission request is for read operations.
48    pub fn is_readonly(&self) -> bool {
49        match self {
50            PermissionReq::GrpcRequest(Request::Query(query_request)) => {
51                !matches!(query_request.query, Some(Query::InsertIntoPlan(_)))
52            }
53            PermissionReq::PromQuery | PermissionReq::LogQuery | PermissionReq::PromStoreRead => {
54                true
55            }
56            PermissionReq::SqlStatement(stmt) => stmt.is_readonly(),
57
58            PermissionReq::GrpcRequest(_)
59            | PermissionReq::Opentsdb
60            | PermissionReq::LineProtocol
61            | PermissionReq::PromStoreWrite
62            | PermissionReq::Otlp
63            | PermissionReq::LogWrite
64            | PermissionReq::BulkInsert { .. } => false,
65        }
66    }
67
68    /// Returns true if the permission request is for write operations.
69    pub fn is_write(&self) -> bool {
70        !self.is_readonly()
71    }
72}
73
74#[derive(Debug)]
75pub enum PermissionResp {
76    Allow,
77    Reject,
78}
79
80pub trait PermissionChecker: Send + Sync {
81    fn check_permission(
82        &self,
83        user_info: UserInfoRef,
84        req: PermissionReq,
85    ) -> Result<PermissionResp>;
86
87    fn check_permission_with_context(
88        &self,
89        user_info: UserInfoRef,
90        req: PermissionReq,
91        _current_schema: Option<&str>,
92    ) -> Result<PermissionResp> {
93        self.check_permission(user_info, req)
94    }
95}
96
97impl PermissionChecker for Option<&PermissionCheckerRef> {
98    fn check_permission(
99        &self,
100        user_info: UserInfoRef,
101        req: PermissionReq,
102    ) -> Result<PermissionResp> {
103        self.check_permission_with_context(user_info, req, None)
104    }
105
106    fn check_permission_with_context(
107        &self,
108        user_info: UserInfoRef,
109        req: PermissionReq,
110        current_schema: Option<&str>,
111    ) -> Result<PermissionResp> {
112        match self {
113            Some(checker) => {
114                match checker.check_permission_with_context(user_info, req, current_schema) {
115                    Ok(PermissionResp::Reject) => PermissionDeniedSnafu.fail(),
116                    Ok(PermissionResp::Allow) => Ok(PermissionResp::Allow),
117                    Err(e) => Err(e),
118                }
119            }
120            None => Ok(PermissionResp::Allow),
121        }
122    }
123}
124
125/// The default permission checker implementation.
126/// It checks the permission mode of [DefaultUserInfo].
127pub struct DefaultPermissionChecker;
128
129impl DefaultPermissionChecker {
130    /// Returns a new [PermissionCheckerRef] instance.
131    pub fn arc() -> PermissionCheckerRef {
132        Arc::new(DefaultPermissionChecker)
133    }
134}
135
136impl PermissionChecker for DefaultPermissionChecker {
137    fn check_permission(
138        &self,
139        user_info: UserInfoRef,
140        req: PermissionReq,
141    ) -> Result<PermissionResp> {
142        if let Some(default_user) = user_info.as_any().downcast_ref::<DefaultUserInfo>() {
143            let permission_mode = default_user.permission_mode();
144
145            if req.is_readonly() && !permission_mode.can_read() {
146                debug!(
147                    "Permission denied: read operation not allowed, user = {}, permission = {}",
148                    default_user.username(),
149                    permission_mode.as_str()
150                );
151                return Ok(PermissionResp::Reject);
152            }
153
154            if req.is_write() && !permission_mode.can_write() {
155                debug!(
156                    "Permission denied: write operation not allowed, user = {}, permission = {}",
157                    default_user.username(),
158                    permission_mode.as_str()
159                );
160                return Ok(PermissionResp::Reject);
161            }
162        }
163
164        // default allow all
165        Ok(PermissionResp::Allow)
166    }
167}
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use crate::user_info::PermissionMode;
172
173    #[test]
174    fn test_default_permission_checker_allow_all_operations() {
175        let checker = DefaultPermissionChecker;
176        let user_info =
177            DefaultUserInfo::with_name_and_permission("test_user", PermissionMode::ReadWrite);
178
179        let read_req = PermissionReq::PromQuery;
180        let write_req = PermissionReq::PromStoreWrite;
181
182        let read_result = checker
183            .check_permission(user_info.clone(), read_req)
184            .unwrap();
185        let write_result = checker.check_permission(user_info, write_req).unwrap();
186
187        assert!(matches!(read_result, PermissionResp::Allow));
188        assert!(matches!(write_result, PermissionResp::Allow));
189    }
190
191    #[test]
192    fn test_default_permission_checker_readonly_user() {
193        let checker = DefaultPermissionChecker;
194        let user_info =
195            DefaultUserInfo::with_name_and_permission("readonly_user", PermissionMode::ReadOnly);
196
197        let read_req = PermissionReq::PromQuery;
198        let write_req = PermissionReq::PromStoreWrite;
199
200        let read_result = checker
201            .check_permission(user_info.clone(), read_req)
202            .unwrap();
203        let write_result = checker.check_permission(user_info, write_req).unwrap();
204
205        assert!(matches!(read_result, PermissionResp::Allow));
206        assert!(matches!(write_result, PermissionResp::Reject));
207    }
208
209    #[test]
210    fn test_default_permission_checker_writeonly_user() {
211        let checker = DefaultPermissionChecker;
212        let user_info =
213            DefaultUserInfo::with_name_and_permission("writeonly_user", PermissionMode::WriteOnly);
214
215        let read_req = PermissionReq::LogQuery;
216        let write_req = PermissionReq::LogWrite;
217
218        let read_result = checker
219            .check_permission(user_info.clone(), read_req)
220            .unwrap();
221        let write_result = checker.check_permission(user_info, write_req).unwrap();
222
223        assert!(matches!(read_result, PermissionResp::Reject));
224        assert!(matches!(write_result, PermissionResp::Allow));
225    }
226
227    #[test]
228    fn test_grpc_insert_into_plan_is_write_request() {
229        let request = Request::Query(api::v1::QueryRequest {
230            query: Some(Query::InsertIntoPlan(api::v1::InsertIntoPlan::default())),
231        });
232        let req = PermissionReq::GrpcRequest(&request);
233
234        assert!(req.is_write());
235    }
236
237    #[test]
238    fn test_bulk_insert_is_write_request() {
239        let req = PermissionReq::BulkInsert {
240            catalog: "greptime",
241            schema: "public",
242            table: "metrics",
243        };
244
245        assert!(req.is_write());
246    }
247}