1use 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 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 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
125pub struct DefaultPermissionChecker;
128
129impl DefaultPermissionChecker {
130 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 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}