diff --git a/Cargo.lock b/Cargo.lock index 2fe2470..8b31016 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1040,6 +1040,40 @@ dependencies = [ "syn", ] +[[package]] +name = "diesel" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72eb77396836a4505da85bae0712fa324b74acfe1876d7c2f7e694ef3d0ee373" +dependencies = [ + "diesel_derives", + "libsqlite3-sys", + "r2d2", +] + +[[package]] +name = "diesel_derives" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad74fdcf086be3d4fdd142f67937678fe60ed431c3b2f08599e7687269410c4" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diesel_migrations" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9ae22beef5e9d6fab9225ddb073c1c6c1a7a6ded5019d5da11d1e5c5adc34e2" +dependencies = [ + "diesel", + "migrations_internals", + "migrations_macros", +] + [[package]] name = "diflouroborane" version = "0.4.2" @@ -1059,6 +1093,8 @@ dependencies = [ "clap 3.2.23", "console", "desfire", + "diesel", + "diesel_migrations", "dirs", "erased-serde", "executor", @@ -1799,6 +1835,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libsqlite3-sys" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.8" @@ -1968,6 +2014,27 @@ dependencies = [ "syn", ] +[[package]] +name = "migrations_internals" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c493c09323068c01e54c685f7da41a9ccf9219735c3766fbfd6099806ea08fbc" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "migrations_macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8ff27a350511de30cdabb77147501c36ef02e0451d957abea2f30caffb2b58" +dependencies = [ + "migrations_internals", + "proc-macro2", + "quote", +] + [[package]] name = "mime" version = "0.3.16" @@ -2330,6 +2397,30 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.51" @@ -2401,6 +2492,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + [[package]] name = "rand" version = "0.8.5" @@ -2717,6 +2819,15 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + [[package]] name = "scopeguard" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 2805841..47ff063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,8 @@ rkyv_dyn = "0.7" inventory = "0.1" linkme = "0.2.10" chrono = { version = "0.4", features = ["serde"] } +diesel = { version = "2.0.4", features = ["r2d2", "sqlite"] } +diesel_migrations = "2.0.0" # Password hashing for internal users rust-argon2 = "0.8.3" @@ -117,6 +119,10 @@ version = "2.0.0" default_features = false features = ["unstable_custom_mechanism", "provider", "registry_static", "config_builder", "plain"] +[features] +default = ["sqlite"] +sqlite = ["diesel/sqlite"] + [dev-dependencies] futures-test = "0.3.16" tempfile = "3.2" diff --git a/api/schema b/api/schema index 19f20f5..cde4677 160000 --- a/api/schema +++ b/api/schema @@ -1 +1 @@ -Subproject commit 19f20f5154f0eced6288ff56cac840025ee51da1 +Subproject commit cde4677575f8e133ac764663e131c80fc891d545 diff --git a/bffhd/lib.rs b/bffhd/lib.rs index 8e24c15..ad0c84a 100644 --- a/bffhd/lib.rs +++ b/bffhd/lib.rs @@ -16,6 +16,7 @@ pub mod config; /// Internal Databases build on top of LMDB, a mmap()'ed B-tree DB optimized for reads pub mod db; +pub mod sql; /// Shared error type pub mod error; diff --git a/bffhd/sql/mod.rs b/bffhd/sql/mod.rs new file mode 100644 index 0000000..287b028 --- /dev/null +++ b/bffhd/sql/mod.rs @@ -0,0 +1,5 @@ +mod schema; +mod models; + +#[cfg(feature = "sqlite")] +pub mod sqlite; \ No newline at end of file diff --git a/bffhd/sql/models.rs b/bffhd/sql/models.rs new file mode 100644 index 0000000..705a507 --- /dev/null +++ b/bffhd/sql/models.rs @@ -0,0 +1,16 @@ +use diesel::{Insertable, Queryable}; +use super::schema::users; + +#[derive(Queryable)] +pub struct User { + pub id: i32, + pub name: String, + pub password: Option, +} + +#[derive(Insertable)] +#[diesel(table_name = users)] +pub struct NewUser<'a> { + pub name: &'a str, + pub password: Option<&'a str>, +} \ No newline at end of file diff --git a/bffhd/sql/schema.rs b/bffhd/sql/schema.rs new file mode 100644 index 0000000..d44f8bd --- /dev/null +++ b/bffhd/sql/schema.rs @@ -0,0 +1,35 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + user_kv (id) { + id -> Integer, + user -> Nullable, + key -> Text, + value -> Binary, + } +} + +diesel::table! { + user_roles (id) { + id -> Integer, + user -> Nullable, + role -> Text, + } +} + +diesel::table! { + users (id) { + id -> Integer, + name -> Text, + password -> Nullable, + } +} + +diesel::joinable!(user_kv -> users (user)); +diesel::joinable!(user_roles -> users (user)); + +diesel::allow_tables_to_appear_in_same_query!( + user_kv, + user_roles, + users, +); diff --git a/bffhd/sql/sqlite.rs b/bffhd/sql/sqlite.rs new file mode 100644 index 0000000..d5e978d --- /dev/null +++ b/bffhd/sql/sqlite.rs @@ -0,0 +1,76 @@ +use std::error::Error; +use thiserror::Error; +use diesel::{Connection, ExpressionMethods, Insertable, OptionalExtension, QueryDsl, RunQueryDsl, SqliteConnection}; +use diesel::associations::HasTable; +use diesel::r2d2::{ConnectionManager, Pool}; +use diesel::sqlite::Sqlite; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +use crate::sql::models::{NewUser, User}; +use crate::sql::schema::users::password; +use crate::users::UserDb; + +const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); + +pub struct SqliteUserDB { + connection: Pool>, +} + +#[derive(Debug, Error)] +pub enum SqliteError { + #[error("failed to open new connection")] + PoolError(#[from] #[source] diesel::r2d2::PoolError), + #[error("failed to execute SQL commands")] + SqlError(#[from] #[source] diesel::r2d2::Error), + #[error("failed to apply migration")] + MigrationError(#[source] Box), +} + +impl SqliteUserDB { + fn new(connection: Pool>) -> Self { + Self { connection } + } + + fn run_migrations(db: &mut impl MigrationHarness) -> Result<(), SqliteError> { + let applied_migrations = db + .run_pending_migrations(MIGRATIONS) + .map_err(SqliteError::MigrationError)?; + for version in applied_migrations { + tracing::info!(?version, "applied migration"); + } + Ok(()) + } + + pub fn setup(url: impl Into) -> Result { + let mut manager = ConnectionManager::::new(url); + let pool = Pool::builder() + .test_on_check_out(true) + .build(manager)?; + + let mut conn = pool.get()?; + Self::run_migrations(&mut conn)?; + + Ok(Self::new(pool)) + } + + pub fn lookup(&self, uid: &str) -> Result, SqliteError> { + use super::schema::users::dsl::*; + + let mut conn = self.connection.get()?; + users + .filter(name.eq(uid)) + .first(&mut conn) + .optional() + .map_err(|e| SqliteError::SqlError(diesel::r2d2::Error::QueryError(e))) + } + + pub fn insert(&self, new_user: NewUser) -> Result<(), SqliteError> { + use super::schema::users::dsl::*; + + let mut conn = self.connection.get()?; + new_user + .insert_into(users) + .execute(&mut conn) + .map(|_| ()) + .map_err(|e| SqliteError::SqlError(diesel::r2d2::Error::QueryError(e))) + } +} \ No newline at end of file diff --git a/bffhd/users/mod.rs b/bffhd/users/mod.rs index bd7a7f9..ce9bc59 100644 --- a/bffhd/users/mod.rs +++ b/bffhd/users/mod.rs @@ -13,6 +13,7 @@ use std::path::Path; use std::sync::Arc; use thiserror::Error; +use crate::sql::sqlite::SqliteUserDB; pub mod db; @@ -211,3 +212,12 @@ impl Users { Ok(0) } } + +pub trait UserDb { + type User; + type Error: std::error::Error + miette::Diagnostic; + + fn lookup(&self, userid: &str) -> Result, Self::Error>; + + fn get_roles(&self, user: &Self::User) -> Result, Self::Error>; +} \ No newline at end of file diff --git a/bffhd/users/sqlite.rs b/bffhd/users/sqlite.rs new file mode 100644 index 0000000..0dd1500 --- /dev/null +++ b/bffhd/users/sqlite.rs @@ -0,0 +1,93 @@ +use std::collections::HashMap; +use std::path::Path; +use rusqlite::{Connection, OptionalExtension}; +use crate::users::db::{User, UserData}; + +pub struct UserSqliteDB { + conn: Connection, +} + +impl UserSqliteDB { + pub fn open(path: impl AsRef) -> rusqlite::Result { + let conn = Connection::open(path)?; + + conn.execute("CREATE TABLE IF NOT EXISTS users (\ + id INTEGER PRIMARY KEY,\ + name TEXT NOT NULL,\ + password TEXT\ + )", ())?; + conn.execute("CREATE TABLE IF NOT EXISTS user_kv (\ + id INTEGER PRIMARY KEY,\ + user INTEGER,\ + key TEXT NOT NULL,\ + value TEXT NOT NULL,\ + FOREIGN KEY(user) REFERENCES users(id)\ + )", ())?; + conn.execute("CREATE TABLE IF NOT EXISTS user_roles (\ + id INTEGER PRIMARY KEY,\ + user INTEGER,\ + role TEXT NOT NULL,\ + FOREIGN KEY(user) REFERENCES users(id)\ + )", ())?; + + Ok(Self { conn }) + } + + pub fn get(&self, uid: &str) -> rusqlite::Result> { + let sqlite_user = self.conn.query_row("SELECT id, name, password FROM users WHERE name = ?1", [uid], |row| { + Ok(SqliteUser { + id: row.get(0)?, + name: row.get(1)?, + password: row.get(2)?, + }) + }).optional()?; + if let Some(SqliteUser { name, password, .. }) = sqlite_user { + let mut kv_stmt = self.conn.prepare("SELECT key, value FROM user_kv WHERE user_id = :id")?; + let kv: HashMap = kv_stmt + .query_map(&[(":id", &name)], |row| Ok((row.get(0)?, row.get(1)?)))? + .filter_map(|fields| fields.ok()) + .collect(); + + let mut roles_stmt = self.conn.prepare("SELECT role FROM user_roles WHERE user_id = :id")?; + let roles: Vec = roles_stmt + .query_map(&[(":id", &name)], |row| row.get(0))? + .filter_map(|fields| fields.ok()) + .collect(); + + let passwd = if password.is_empty() { None } else { Some(password) }; + Ok(Some(User { + id: name, + userdata: UserData { + roles, + passwd, + kv, + } + })) + } else { + Ok(None) + } + } + + pub fn put(&self, uid: &str, user: &SqliteUser) -> rusqlite::Result<()> { + todo!() + } +} + +pub struct SqliteUser { + id: i32, + name: String, + password: String, +} + +struct SqliteUserKv { + id: i32, + user_id: i32, + key: String, + value: String, +} + +struct SqliteRoles { + id: i32, + user_id: i32, + role: String, +} \ No newline at end of file diff --git a/build.rs b/build.rs index 37f84e1..7974404 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,5 @@ fn main() { // Extract build-time information using the `shadow-rs` crate shadow_rs::new(); + println!("cargo:rerun-if-changed=migrations"); } diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..d81ae29 --- /dev/null +++ b/diesel.toml @@ -0,0 +1,8 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "bffhd/sql/schema.rs" + +[migrations_directory] +dir = "migrations" diff --git a/migrations/2023-04-20-114002_create_userdb/down.sql b/migrations/2023-04-20-114002_create_userdb/down.sql new file mode 100644 index 0000000..1e4e09b --- /dev/null +++ b/migrations/2023-04-20-114002_create_userdb/down.sql @@ -0,0 +1,3 @@ +DROP TABLE user_roles; +DROP TABLE user_kv; +DROP TABLE users; \ No newline at end of file diff --git a/migrations/2023-04-20-114002_create_userdb/up.sql b/migrations/2023-04-20-114002_create_userdb/up.sql new file mode 100644 index 0000000..53aef4d --- /dev/null +++ b/migrations/2023-04-20-114002_create_userdb/up.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + password TEXT +); + +CREATE TABLE IF NOT EXISTS user_kv ( + id INTEGER PRIMARY KEY NOT NULL, + user INTEGER NOT NULL, + key TEXT NOT NULL, + value BLOB NOT NULL, + FOREIGN KEY (user) REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS user_roles ( + id INTEGER PRIMARY KEY NOT NULL, + user INTEGER NOT NULL, + role TEXT NOT NULL, + FOREIGN KEY (user) REFERENCES users(id) +); diff --git a/runtime/lightproc/src/proc_handle.rs b/runtime/lightproc/src/proc_handle.rs index f04fef0..3452219 100644 --- a/runtime/lightproc/src/proc_handle.rs +++ b/runtime/lightproc/src/proc_handle.rs @@ -6,7 +6,6 @@ use crate::state::*; use std::fmt::{self, Debug, Formatter}; use std::future::Future; use std::marker::{PhantomData, Unpin}; -use std::mem::MaybeUninit; use std::pin::Pin; use std::ptr::NonNull; use std::sync::atomic::Ordering;