risingwave_frontend/expr/function_impl/
has_privilege.rs

1// Copyright 2025 RisingWave Labs
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::collections::HashSet;
16
17use risingwave_common::session_config::SearchPath;
18use risingwave_expr::{ExprError, Result, capture_context, function};
19use risingwave_pb::user::grant_privilege::{Action, Object};
20use risingwave_sqlparser::parser::Parser;
21use thiserror_ext::AsReport;
22
23use super::context::{AUTH_CONTEXT, CATALOG_READER, DB_NAME, SEARCH_PATH, USER_INFO_READER};
24use crate::catalog::CatalogReader;
25use crate::catalog::root_catalog::SchemaPath;
26use crate::catalog::system_catalog::is_system_catalog;
27use crate::session::AuthContext;
28use crate::user::user_service::UserInfoReader;
29use crate::{Binder, bind_data_type};
30
31#[inline(always)]
32pub fn user_not_found_err(inner_err: &str) -> ExprError {
33    ExprError::InvalidParam {
34        name: "user",
35        reason: inner_err.into(),
36    }
37}
38
39#[function("has_table_privilege(int4, int4, varchar) -> boolean")]
40fn has_table_privilege(user_id: i32, table_oid: i32, privileges: &str) -> Result<bool> {
41    // does user have privilege for table
42    let user_name = get_user_name_by_id_captured(user_id)?;
43    has_table_privilege_1(user_name.as_str(), table_oid, privileges)
44}
45
46#[function("has_table_privilege(varchar, int4, varchar) -> boolean")]
47fn has_table_privilege_1(user_name: &str, table_oid: i32, privileges: &str) -> Result<bool> {
48    let allowed_actions = HashSet::new();
49    let actions = parse_privilege(privileges, &allowed_actions)?;
50    if is_system_catalog(table_oid as _) {
51        return Ok(true);
52    }
53    has_privilege_impl_captured(
54        user_name,
55        &get_grant_object_by_oid_captured(table_oid)?,
56        &actions,
57    )
58}
59
60#[function("has_any_column_privilege(int4, int4, varchar) -> boolean")]
61fn has_any_column_privilege(user_id: i32, table_oid: i32, privileges: &str) -> Result<bool> {
62    // does user have privilege for any column of table
63    let user_name = get_user_name_by_id_captured(user_id)?;
64    has_any_column_privilege_1(user_name.as_str(), table_oid, privileges)
65}
66
67#[function("has_any_column_privilege(varchar, int4, varchar) -> boolean")]
68fn has_any_column_privilege_1(user_name: &str, table_oid: i32, privileges: &str) -> Result<bool> {
69    let allowed_actions = HashSet::from_iter([Action::Select, Action::Insert, Action::Update]);
70    let actions = parse_privilege(privileges, &allowed_actions)?;
71    has_privilege_impl_captured(
72        user_name,
73        &get_grant_object_by_oid_captured(table_oid)?,
74        &actions,
75    )
76}
77
78#[function("has_schema_privilege(varchar, int4, varchar) -> boolean")]
79fn has_schema_privilege(user_name: &str, schema_oid: i32, privileges: &str) -> Result<bool> {
80    // does user have privilege for schema
81    let allowed_actions = HashSet::from_iter([Action::Create, Action::Usage]);
82    let actions = parse_privilege(privileges, &allowed_actions)?;
83    has_privilege_impl_captured(user_name, &Object::SchemaId(schema_oid as u32), &actions)
84}
85
86#[function("has_schema_privilege(int4, varchar, varchar) -> boolean")]
87fn has_schema_privilege_1(user_id: i32, schema_name: &str, privileges: &str) -> Result<bool> {
88    let user_name = get_user_name_by_id_captured(user_id)?;
89    let schema_oid = get_schema_id_by_name_captured(schema_name)?;
90    has_schema_privilege(user_name.as_str(), schema_oid, privileges)
91}
92
93#[function("has_schema_privilege(int4, int4, varchar) -> boolean")]
94fn has_schema_privilege_2(user_id: i32, schema_oid: i32, privileges: &str) -> Result<bool> {
95    let user_name = get_user_name_by_id_captured(user_id)?;
96    has_schema_privilege(user_name.as_str(), schema_oid, privileges)
97}
98
99#[function("has_schema_privilege(varchar, varchar, varchar) -> boolean")]
100fn has_schema_privilege_3(user_name: &str, schema_name: &str, privileges: &str) -> Result<bool> {
101    let schema_oid = get_schema_id_by_name_captured(schema_name)?;
102    has_schema_privilege(user_name, schema_oid, privileges)
103}
104
105#[function("has_function_privilege(varchar, int4, varchar) -> boolean")]
106fn has_function_privilege(user_name: &str, function_oid: i32, privileges: &str) -> Result<bool> {
107    // does user have privilege for function
108    let allowed_actions = HashSet::from_iter([Action::Execute]);
109    let actions = parse_privilege(privileges, &allowed_actions)?;
110    has_privilege_impl_captured(
111        user_name,
112        &Object::FunctionId(function_oid as u32),
113        &actions,
114    )
115}
116
117#[function("has_function_privilege(int4, int4, varchar) -> boolean")]
118fn has_function_privilege_1(user_id: i32, function_oid: i32, privileges: &str) -> Result<bool> {
119    let user_name = get_user_name_by_id_captured(user_id)?;
120    has_function_privilege(user_name.as_str(), function_oid, privileges)
121}
122
123#[function("has_function_privilege(varchar, varchar, varchar) -> boolean")]
124fn has_function_privilege_2(
125    user_name: &str,
126    function_name: &str,
127    privileges: &str,
128) -> Result<bool> {
129    let function_oid = get_function_id_by_name_captured(function_name)?;
130    has_function_privilege(user_name, function_oid, privileges)
131}
132
133#[function("has_function_privilege(int4, varchar, varchar) -> boolean")]
134fn has_function_privilege_3(user_id: i32, function_name: &str, privileges: &str) -> Result<bool> {
135    let user_name = get_user_name_by_id_captured(user_id)?;
136    let function_oid = get_function_id_by_name_captured(function_name)?;
137    has_function_privilege(user_name.as_str(), function_oid, privileges)
138}
139
140#[capture_context(USER_INFO_READER)]
141fn has_privilege_impl(
142    user_info_reader: &UserInfoReader,
143    user_name: &str,
144    object: &Object,
145    actions: &Vec<(Action, bool)>,
146) -> Result<bool> {
147    let user_info = &user_info_reader.read_guard();
148    let user_catalog = user_info
149        .get_user_by_name(user_name)
150        .ok_or(user_not_found_err(
151            format!("User {} not found", user_name).as_str(),
152        ))?;
153    Ok(user_catalog.check_privilege_with_grant_option(object, actions))
154}
155
156#[capture_context(USER_INFO_READER)]
157fn get_user_name_by_id(user_info_reader: &UserInfoReader, user_id: i32) -> Result<String> {
158    let user_info = &user_info_reader.read_guard();
159    user_info
160        .get_user_name_by_id(user_id as u32)
161        .ok_or(user_not_found_err(
162            format!("User {} not found", user_id).as_str(),
163        ))
164}
165
166#[capture_context(CATALOG_READER, DB_NAME)]
167fn get_grant_object_by_oid(
168    catalog_reader: &CatalogReader,
169    db_name: &str,
170    oid: i32,
171) -> Result<Object> {
172    catalog_reader
173        .read_guard()
174        .get_database_by_name(db_name)
175        .map_err(|e| ExprError::InvalidParam {
176            name: "oid",
177            reason: e.to_report_string().into(),
178        })?
179        .get_grant_object_by_oid(oid as u32)
180        .ok_or(ExprError::InvalidParam {
181            name: "oid",
182            reason: format!("Table {} not found", oid).as_str().into(),
183        })
184}
185
186#[capture_context(CATALOG_READER, DB_NAME)]
187fn get_schema_id_by_name(
188    catalog_reader: &CatalogReader,
189    db_name: &str,
190    schema_name: &str,
191) -> Result<i32> {
192    let reader = &catalog_reader.read_guard();
193    Ok(reader
194        .get_schema_by_name(db_name, schema_name)
195        .map_err(|e| ExprError::InvalidParam {
196            name: "schema",
197            reason: e.to_report_string().into(),
198        })?
199        .id() as i32)
200}
201
202#[capture_context(CATALOG_READER, DB_NAME, AUTH_CONTEXT, SEARCH_PATH)]
203fn get_function_id_by_name(
204    catalog_reader: &CatalogReader,
205    db_name: &str,
206    auth_context: &AuthContext,
207    search_path: &SearchPath,
208    function_name: &str,
209) -> Result<i32> {
210    let desc =
211        Parser::parse_function_desc_str(function_name).map_err(|e| ExprError::InvalidParam {
212            name: "function",
213            reason: e.to_report_string().into(),
214        })?;
215    let mut arg_types = vec![];
216    if let Some(args) = desc.args {
217        for arg in &args {
218            arg_types.push(bind_data_type(&arg.data_type).map_err(|e| {
219                ExprError::InvalidParam {
220                    name: "function",
221                    reason: e.to_report_string().into(),
222                }
223            })?);
224        }
225    }
226
227    let (schema, name) =
228        Binder::resolve_schema_qualified_name(db_name, desc.name).map_err(|e| {
229            ExprError::InvalidParam {
230                name: "function",
231                reason: e.to_report_string().into(),
232            }
233        })?;
234    let schema_path = SchemaPath::new(schema.as_deref(), search_path, &auth_context.user_name);
235
236    let reader = &catalog_reader.read_guard();
237    Ok(reader
238        .get_function_by_name_args(db_name, schema_path, &name, &arg_types)
239        .map_err(|e| ExprError::InvalidParam {
240            name: "function",
241            reason: e.to_report_string().into(),
242        })?
243        .0
244        .id
245        .function_id() as i32)
246}
247
248fn parse_privilege(
249    privilege_string: &str,
250    allowed_actions: &HashSet<Action>,
251) -> Result<Vec<(Action, bool)>> {
252    let mut privileges = Vec::new();
253    for part in privilege_string.split(',').map(str::trim) {
254        let (privilege_type, grant_option) = match part.rsplit_once(" WITH GRANT OPTION") {
255            Some((p, _)) => (p, true),
256            None => (part, false),
257        };
258        match Action::from_str_name(privilege_type.to_uppercase().as_str()) {
259            Some(Action::Unspecified) | None => {
260                return Err(ExprError::InvalidParam {
261                    name: "privilege",
262                    reason: format!("unrecognized privilege type: \"{}\"", part).into(),
263                });
264            }
265            Some(action) => {
266                if allowed_actions.is_empty() || allowed_actions.contains(&action) {
267                    privileges.push((action, grant_option))
268                } else {
269                    return Err(ExprError::InvalidParam {
270                        name: "privilege",
271                        reason: format!("unrecognized privilege type: \"{}\"", part).into(),
272                    });
273                }
274            }
275        }
276    }
277    Ok(privileges)
278}