Refine permissions

next
Sayan Nandan 10 months ago
parent 87d11d6ddc
commit bed0d93628
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -39,6 +39,9 @@ pub fn exec<G: GlobalInstanceLike>(
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),

@ -78,8 +78,8 @@ async fn run_blocking_stmt(
mut state: State<'_, InplaceData>,
stmt: KeywordStmt,
) -> Result<Response, QueryError> {
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()?;

@ -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;

@ -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 <ohsayan@outlook.com>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
use {crate::util, proc_macro::TokenStream, quote::quote, syn::AttributeArgs};
type OptString = Option<String>;
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<impl quote::ToTokens> {
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<TokenStream, syn::Error> {
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()
}

@ -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 <ohsayan@outlook.com>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
use {
crate::{
dbtest_fn::{self, DBTestFunctionConfig},
util,
},
proc_macro::TokenStream,
quote::quote,
std::collections::HashSet,
syn::{self, AttributeArgs},
};
struct DBTestModuleConfig {
fcfg: DBTestFunctionConfig,
skips: HashSet<String>,
}
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()
}

@ -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`

Loading…
Cancel
Save