From bed0d93628f013d985848127e248879c1cc80f24 Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Wed, 29 Nov 2023 09:08:47 +0530 Subject: [PATCH] Refine permissions --- server/src/engine/core/dcl.rs | 3 + server/src/engine/core/exec.rs | 4 +- server/src/engine/ql/dcl.rs | 6 + sky-macros/src/dbtest_fn.rs | 376 --------------------------------- sky-macros/src/dbtest_mod.rs | 116 ---------- sky-macros/src/lib.rs | 53 ----- 6 files changed, 11 insertions(+), 547 deletions(-) delete mode 100644 sky-macros/src/dbtest_fn.rs delete mode 100644 sky-macros/src/dbtest_mod.rs diff --git a/server/src/engine/core/dcl.rs b/server/src/engine/core/dcl.rs index 7bf0d6c8..cfb3356f 100644 --- a/server/src/engine/core/dcl.rs +++ b/server/src/engine/core/dcl.rs @@ -39,6 +39,9 @@ pub fn exec( current_user: &ClientLocalState, cmd: SysctlCommand, ) -> QueryResult<()> { + if cmd.needs_root() & !current_user.is_root() { + return Err(QueryError::SysPermissionDenied); + } match cmd { SysctlCommand::CreateUser(new) => create_user(&g, new), SysctlCommand::DropUser(drop) => drop_user(&g, current_user, drop), diff --git a/server/src/engine/core/exec.rs b/server/src/engine/core/exec.rs index 8daf9dae..29586c2e 100644 --- a/server/src/engine/core/exec.rs +++ b/server/src/engine/core/exec.rs @@ -78,8 +78,8 @@ async fn run_blocking_stmt( mut state: State<'_, InplaceData>, stmt: KeywordStmt, ) -> Result { - if !cstate.is_root() { - // all the actions here need root permission + if !(cstate.is_root() | (stmt == KeywordStmt::Sysctl)) { + // all the actions here need root permission (but we do an exception for sysctl which allows status to be called by anyone) return Err(QueryError::SysPermissionDenied); } state.ensure_minimum_for_blocking_stmt()?; diff --git a/server/src/engine/ql/dcl.rs b/server/src/engine/ql/dcl.rs index ec4be180..b04737c3 100644 --- a/server/src/engine/ql/dcl.rs +++ b/server/src/engine/ql/dcl.rs @@ -46,6 +46,12 @@ pub enum SysctlCommand<'a> { ReportStatus, } +impl<'a> SysctlCommand<'a> { + pub fn needs_root(&self) -> bool { + !matches!(self, Self::ReportStatus) + } +} + impl<'a> traits::ASTNode<'a> for SysctlCommand<'a> { const MUST_USE_FULL_TOKEN_RANGE: bool = true; const VERIFIES_FULL_TOKEN_RANGE_USAGE: bool = false; diff --git a/sky-macros/src/dbtest_fn.rs b/sky-macros/src/dbtest_fn.rs deleted file mode 100644 index 2b4ef21a..00000000 --- a/sky-macros/src/dbtest_fn.rs +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Created on Wed Mar 09 2022 - * - * This file is a part of Skytable - * Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source - * NoSQL database written by Sayan Nandan ("the Author") with the - * vision to provide flexibility in data modelling without compromising - * on performance, queryability or scalability. - * - * Copyright (c) 2022, Sayan Nandan - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * -*/ - -use {crate::util, proc_macro::TokenStream, quote::quote, syn::AttributeArgs}; - -type OptString = Option; - -pub struct DBTestFunctionConfig { - table_decl: String, - port: u16, - host: String, - tls_cert: OptString, - login: (OptString, OptString), - testuser: bool, - rootuser: bool, - norun: bool, - skip_cfg: quote::__private::TokenStream, -} - -impl DBTestFunctionConfig { - pub fn default() -> Self { - Self { - table_decl: "(string, string)".to_owned(), - port: 2003, - host: "127.0.0.1".to_owned(), - tls_cert: None, - login: (None, None), - testuser: false, - rootuser: false, - norun: false, - skip_cfg: quote! {}, - } - } - pub fn get_connection_tokens(&self) -> impl quote::ToTokens { - let DBTestFunctionConfig { - port, - host, - tls_cert, - .. - } = &self; - match tls_cert { - Some(cert) => { - quote! { - let certpath = ::std::format!("{}/{}", crate::ROOT_DIR, #cert); - let mut con = skytable::aio::TlsConnection::new( - #host, #port, &certpath - ).await.unwrap(); - } - } - None => quote! { - let mut con = skytable::AsyncConnection::new(#host, #port).await.unwrap(); - }, - } - } - pub fn get_create_table_tokens(&self, table_name: &str) -> impl quote::ToTokens { - let Self { table_decl, .. } = self; - quote! { - con.run_query_raw( - &skytable::query!(format!("create model {}{} volatile", #table_name, #table_decl)) - ).await.unwrap() - } - } - pub fn get_skip_cfg_tokens(&self) -> &impl quote::ToTokens { - &self.skip_cfg - } - pub fn get_login_tokens(&self) -> Option { - let Self { - login, - testuser, - rootuser, - .. - } = self; - let conflict = (*rootuser && *testuser) - || ((*rootuser || *testuser) && (login.0.is_some() || login.1.is_some())); - if conflict { - panic!("Expected either of `username` and `password`, or `auth_rootuser`, or `auth_testuser`"); - } - let ret; - if *testuser { - ret = quote! { - let __username__ = crate::auth::provider::testsuite_data::TESTSUITE_TEST_USER; - let __password__ = crate::auth::provider::testsuite_data::TESTSUITE_TEST_TOKEN; - }; - } else if *rootuser { - ret = quote! { - let __username__ = crate::auth::provider::testsuite_data::TESTSUITE_ROOT_USER; - let __password__ = crate::auth::provider::testsuite_data::TESTSUITE_ROOT_TOKEN; - }; - } else { - let (username, password) = login; - match (username, password) { - (Some(username), Some(password)) => { - ret = quote! { - let __username__ = #username; - let __password__ = #password; - } - } - (None, None) => return None, - _ => panic!("Expected both `username` and `password`"), - } - } - Some(quote! { - #ret - let __loginquery__ = ::skytable::query!("auth", "login", __username__, __password__); - assert_eq!( - con.run_query_raw(&__loginquery__).await.unwrap(), - ::skytable::Element::RespCode(::skytable::RespCode::Okay), - "Failed to login" - ); - }) - } -} - -pub fn parse_dbtest_func_args( - arg: &str, - lit: &syn::Lit, - span: proc_macro2::Span, - fcfg: &mut DBTestFunctionConfig, -) { - match arg { - "table" => { - // check if the user wants some special table declaration - fcfg.table_decl = - util::parse_string(lit, span, "table").expect("Expected a value for `table`"); - } - "port" => { - // check if we need a custom port - fcfg.port = util::parse_number(lit, span, "port").expect("Expected a u16"); - } - "host" => { - fcfg.host = util::parse_string(lit, span, "host").expect("Expected a string"); - } - "tls_cert" => { - fcfg.tls_cert = Some(util::parse_string(lit, span, "host").expect("Expected a string")); - } - "username" => { - fcfg.login.0 = - Some(util::parse_string(lit, span, "username").expect("Expected a string")) - } - "password" => { - fcfg.login.1 = - Some(util::parse_string(lit, span, "password").expect("Expected a string")) - } - "auth_testuser" => { - fcfg.testuser = util::parse_bool(lit, span, "auth_testuser").expect("Expected a bool") - } - "auth_rootuser" => { - fcfg.rootuser = util::parse_bool(lit, span, "auth_testuser").expect("Expected a bool") - } - "norun" => fcfg.norun = util::parse_bool(lit, span, "norun").expect("Expected a bool"), - "run_if_cfg" => { - let cfg_name = util::parse_string(lit, span, "run_if_cfg").expect("Expected a string"); - fcfg.skip_cfg = quote! { - #[cfg_attr(not(feature = #cfg_name), ignore)] - }; - } - "skip_if_cfg" => { - let cfg_name = util::parse_string(lit, span, "run_if_cfg").expect("Expected a string"); - fcfg.skip_cfg = quote! { - #[cfg_attr(feature = #cfg_name, ignore)] - }; - } - x => panic!("unknown attribute {x} specified"), - } -} - -/// This parses a function within a `dbtest` module -/// -/// This accepts an `async` function and returns a non-`async` version of it - by -/// making the body of the function use the `tokio` runtime -fn generate_dbtest( - mut input: syn::ItemFn, - rng: &mut impl rand::Rng, - fcfg: &DBTestFunctionConfig, -) -> Result { - let sig = &mut input.sig; - let fname = sig.ident.to_string(); - let testbody = &input.block; - let attrs = &input.attrs; - let vis = &input.vis; - let header = quote! { - #[::core::prelude::v1::test] - }; - if sig.asyncness.is_none() { - let msg = "`dbtest` functions need to be async"; - return Err(syn::Error::new_spanned(sig.fn_token, msg)); - } - sig.asyncness = None; - let rand_string = util::get_rand_string(rng); - let mut body = quote! {}; - - // first add connection tokens - let connection_tokens = fcfg.get_connection_tokens(); - body = quote! { - #body - #connection_tokens - }; - - // check if we need to log in - if let Some(login_tokens) = fcfg.get_login_tokens() { - body = quote! { - #body - #login_tokens - }; - } - - if !fcfg.norun { - // now create keyspace - body = quote! { - #body - let __create_ks = - con.run_query_raw( - &skytable::query!("create space testsuite") - ).await.unwrap(); - if !( - __create_ks == skytable::Element::RespCode(skytable::RespCode::Okay) || - __create_ks == skytable::Element::RespCode( - skytable::RespCode::ErrorString( - skytable::error::errorstring::ERR_ALREADY_EXISTS.to_owned() - ) - ) - ) { - panic!("Failed to create keyspace: {:?}", __create_ks); - } - }; - // now switch keyspace - body = quote! { - #body - let __switch_ks = - con.run_query_raw( - &skytable::query!("use testsuite") - ).await.unwrap(); - if (__switch_ks != skytable::Element::RespCode(skytable::RespCode::Okay)) { - panic!("Failed to switch keyspace: {:?}", __switch_ks); - } - }; - // now create table - let create_table_tokens = fcfg.get_create_table_tokens(&rand_string); - body = quote! { - #body - assert_eq!( - #create_table_tokens, - skytable::Element::RespCode(skytable::RespCode::Okay), - "Failed to create table" - ); - }; - // now generate the __MYENTITY__ string - body = quote! { - #body - let mut __concat_entity = std::string::String::new(); - __concat_entity.push_str("testsuite."); - __concat_entity.push_str(&#rand_string); - let __MYTABLE__ : String = #rand_string.to_string(); - let __MYKS__: String = "testsuite".to_owned(); - let __MYENTITY__: String = __concat_entity.clone(); - }; - // now switch to the temporary table we created - body = quote! { - #body - let __switch_entity = - con.run_query_raw( - &skytable::query!(format!("use {}", __concat_entity)) - ).await.unwrap(); - assert_eq!( - __switch_entity, skytable::Element::RespCode(skytable::RespCode::Okay), "Failed to switch" - ); - }; - } - // now give the query ghost variable - body = quote! { - #body - let mut query = skytable::Query::new(); - }; - // IMPORTANT: now append the actual test body - body = quote! { - #body - #testbody - }; - if !fcfg.norun { - // now we're done with the test so flush the table - body = quote! { - #body - { - let mut __flush__ = skytable::Query::from("flushdb"); - std::assert_eq!( - con.run_query_raw(&__flush__).await.unwrap(), - skytable::Element::RespCode(skytable::RespCode::Okay) - ); - } - }; - } - let skip_cfg = fcfg.get_skip_cfg_tokens(); - let result = quote! { - #skip_cfg - #header - #(#attrs)* - #vis #sig { - tokio::runtime::Builder::new_multi_thread() - .worker_threads(4) - .thread_name(#fname) - .thread_stack_size(3 * 1024 * 1024) - .enable_all() - .build() - .unwrap() - .block_on(async { #body }); - } - }; - Ok(result.into()) -} - -/// This function checks if the current function is eligible to be a test and if so, returns -/// the generated test -pub fn generate_test( - input: syn::ItemFn, - rng: &mut impl rand::Rng, - fcfg: &DBTestFunctionConfig, -) -> TokenStream { - for attr in &input.attrs { - if attr.path.is_ident("test") { - let msg = "second test attribute is supplied"; - return syn::Error::new_spanned(&attr, msg) - .to_compile_error() - .into(); - } - } - - if !input.sig.inputs.is_empty() { - let msg = "the test function cannot accept arguments"; - return syn::Error::new_spanned(&input.sig.inputs, msg) - .to_compile_error() - .into(); - } - generate_dbtest(input, rng, fcfg).unwrap_or_else(|e| e.to_compile_error().into()) -} - -fn parse_dbtest_func_attrs(attrs: AttributeArgs) -> DBTestFunctionConfig { - let mut fcfg = DBTestFunctionConfig::default(); - attrs.iter().for_each(|arg| { - if let syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) = arg { - let (ident, lit, span) = util::get_metanamevalue_data(namevalue); - parse_dbtest_func_args(&ident, lit, span, &mut fcfg) - } - }); - fcfg -} - -pub fn dbtest_func(args: TokenStream, item: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(item as syn::ItemFn); - let attrs = syn::parse_macro_input!(args as AttributeArgs); - let mut rng = rand::thread_rng(); - let fcfg = parse_dbtest_func_attrs(attrs); - generate_dbtest(input, &mut rng, &fcfg).unwrap() -} diff --git a/sky-macros/src/dbtest_mod.rs b/sky-macros/src/dbtest_mod.rs deleted file mode 100644 index d57a1754..00000000 --- a/sky-macros/src/dbtest_mod.rs +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Created on Wed Mar 09 2022 - * - * This file is a part of Skytable - * Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source - * NoSQL database written by Sayan Nandan ("the Author") with the - * vision to provide flexibility in data modelling without compromising - * on performance, queryability or scalability. - * - * Copyright (c) 2022, Sayan Nandan - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * -*/ - -use { - crate::{ - dbtest_fn::{self, DBTestFunctionConfig}, - util, - }, - proc_macro::TokenStream, - quote::quote, - std::collections::HashSet, - syn::{self, AttributeArgs}, -}; - -struct DBTestModuleConfig { - fcfg: DBTestFunctionConfig, - skips: HashSet, -} - -impl DBTestModuleConfig { - fn default() -> Self { - Self { - skips: HashSet::new(), - fcfg: DBTestFunctionConfig::default(), - } - } -} - -fn parse_dbtest_module_args(args: AttributeArgs) -> DBTestModuleConfig { - let mut modcfg = DBTestModuleConfig::default(); - for arg in args { - if let syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) = arg { - let (ident, lit, span) = util::get_metanamevalue_data(&namevalue); - match ident.as_str() { - "skip" => { - modcfg.skips = util::parse_string(lit, span, "skip") - .expect("Expected a value for argument `skip`") - .split_whitespace() - .map(|val| val.to_string()) - .collect(); - } - possibly_func_arg => dbtest_fn::parse_dbtest_func_args( - possibly_func_arg, - lit, - span, - &mut modcfg.fcfg, - ), - } - } - } - modcfg -} - -/// This function accepts an entire **inline** module which comprises of `dbtest` functions. -/// It takes each function in turn, and generates `#[test]`able functions for them -pub fn parse_test_module(args: TokenStream, item: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(item as syn::ItemMod); - let content = match input.content { - Some((_, c)) => c, - None => { - return syn::Error::new_spanned(&input, "Couldn't get the module content") - .to_compile_error() - .into() - } - }; - let modcfg = parse_dbtest_module_args(syn::parse_macro_input!(args as AttributeArgs)); - let mut result = quote! {}; - let mut rng = rand::thread_rng(); - for item in content { - match item { - // We just care about functions, so parse functions and ignore everything - // else - syn::Item::Fn(function) if !modcfg.skips.contains(&function.sig.ident.to_string()) => { - let generated_fn = dbtest_fn::generate_test(function, &mut rng, &modcfg.fcfg); - let __tok: syn::ItemFn = syn::parse_macro_input!(generated_fn as syn::ItemFn); - let tok = quote! { - #__tok - }; - result = quote! { - #result - #tok - }; - } - token => { - result = quote! { - #result - #token - }; - } - } - } - result.into() -} diff --git a/sky-macros/src/lib.rs b/sky-macros/src/lib.rs index ad2356fe..cb04fb94 100644 --- a/sky-macros/src/lib.rs +++ b/sky-macros/src/lib.rs @@ -50,61 +50,8 @@ use { syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, Lit, Meta, NestedMeta}, }; -mod dbtest_fn; -mod dbtest_mod; mod util; -#[proc_macro_attribute] -/// The `dbtest_module` function accepts an inline module of `dbtest_func` compatible functions, -/// unpacking each function into a dbtest -pub fn dbtest_module(args: TokenStream, item: TokenStream) -> TokenStream { - dbtest_mod::parse_test_module(args, item) -} - -/// The `dbtest_func` macro starts an async server in the background and is meant for -/// use within the `skyd` or `WORKSPACEROOT/server/` crate. If you use this compiler -/// macro in any other crate, you'll simply get compilation errors -/// -/// All tests will clean up all values once a single test is over -/// -/// ## Arguments -/// - `table -> str`: Custom table declaration -/// - `port -> u16`: Custom port -/// - `host -> str`: Custom host -/// - `tls_cert -> str`: TLS cert (makes the connection a TLS one) -/// - `username -> str`: Username for authn -/// - `password -> str`: Password for authn -/// - `auth_testuser -> bool`: Login as the test user -/// - `auth_rootuser -> bool`: Login as the root user -/// - `norun -> bool`: Don't execute anything on the connection -/// -/// ## _Ghost_ values -/// This macro gives: -/// - `con`: a `skytable::AsyncConnection` -/// - `query`: a mutable `skytable::Query` -/// - `__MYENTITY__`: the entity set on launch -/// - `__MYTABLE__`: the table set on launch -/// - `__MYKS__`: the keyspace set on launch -/// -/// ## Requirements -/// -/// The `#[dbtest]` macro expects several things. The calling crate: -/// - should have the `tokio` crate as a dependency and should have the -/// `features` set to full -/// - should have the `skytable` crate as a dependency and should have the `features` set to `async` and version -/// upstreamed to `next` on skytable/client-rust -/// -/// ## Collisions -/// -/// The sample space for table name generation is so large (in the order of 4.3 to the 50) that collisions -/// are practially impossible. Hence we do not bother with a global random string table and instead proceed -/// to generate tables randomly at the point of invocation -/// -#[proc_macro_attribute] -pub fn dbtest_func(args: TokenStream, item: TokenStream) -> TokenStream { - dbtest_fn::dbtest_func(args, item) -} - #[proc_macro] /// Get a compile time respcode/respstring array. For example, if you pass: "Unknown action", /// it will return: `!14\nUnknown Action\n`