Aldonu datumbazon kaj sesia-administradon

This commit is contained in:
Savanni D'Gerinel 2021-12-30 16:40:14 -05:00
parent e48ed1f2dd
commit 823a701a2f
8 changed files with 431 additions and 70 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
node_modules
target
*.db

82
Cargo.lock generated
View File

@ -2,6 +2,23 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.3",
"once_cell",
"version_check",
]
[[package]]
name = "anyhow"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
[[package]]
name = "autocfg"
version = "1.0.1"
@ -84,6 +101,18 @@ dependencies = [
"generic-array",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fnv"
version = "1.0.7"
@ -219,6 +248,18 @@ name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "hashlink"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
dependencies = [
"hashbrown",
]
[[package]]
name = "headers"
@ -361,6 +402,10 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
name = "kampanja-kontrolada-servilo"
version = "0.1.0"
dependencies = [
"anyhow",
"rusqlite",
"tempfile",
"thiserror",
"tokio",
"uuid",
"warp",
@ -378,6 +423,16 @@ version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
[[package]]
name = "libsqlite3-sys"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cafc7c74096c336d9d27145f7ebd4f4b6f95ba16aa5a282387267e6925cb58"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "lock_api"
version = "0.4.5"
@ -558,6 +613,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "ppv-lite86"
version = "0.2.10"
@ -687,6 +748,21 @@ dependencies = [
"winapi",
]
[[package]]
name = "rusqlite"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ba4d3462c8b2e4d7f4fcfcf2b296dc6b65404fbbc7b63daa37fd485c149daf7"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"memchr",
"smallvec",
]
[[package]]
name = "ryu"
version = "1.0.5"
@ -1058,6 +1134,12 @@ dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.3"

View File

@ -6,6 +6,12 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1", features = ["full"] }
uuid = { version = "0.8.2", features = ["v4"] }
warp = { version = "0.3.1" }
anyhow = { version = "1" }
rusqlite = { version = "0.26" }
thiserror = { version = "1" }
tokio = { version = "1", features = ["full"] }
uuid = { version = "0.8", features = ["v4"] }
warp = { version = "0.3" }
[dev-dependencies]
tempfile = { version = "3" }

View File

@ -1,3 +1,5 @@
use crate::datumbazo::Datumbazo;
use rusqlite::OptionalExtension;
use std::{
collections::HashMap,
convert::Infallible,
@ -30,6 +32,21 @@ impl From<Ĵetono> for String {
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct UzantIdentigilo(String);
impl From<&str> for UzantIdentigilo {
fn from(s: &str) -> Self {
UzantIdentigilo(s.to_owned())
}
}
impl From<UzantIdentigilo> for String {
fn from(s: UzantIdentigilo) -> Self {
s.0.clone()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Uzantnomo(String);
@ -45,33 +62,95 @@ impl From<Uzantnomo> for String {
}
}
pub trait AŭtentigoDB: Clone + Send + Sync {
fn aŭtentigu(&self, ĵetono: Ĵetono) -> Option<Uzantnomo>;
pub trait AŭtentigoDB: Send {
fn aŭtentigu(&self, ĵetono: Ĵetono) -> Option<(UzantIdentigilo, Uzantnomo)>;
fn kreu_ĵetono(&mut self, uzantnomo: Uzantnomo) -> Ĵetono;
}
#[derive(Clone)]
pub struct MemAŭtentigo {
mem: HashMap<Ĵetono, Uzantnomo>,
sesioj: HashMap<Ĵetono, UzantIdentigilo>,
uzantoj: HashMap<UzantIdentigilo, Uzantnomo>,
inversa_uzantoj: HashMap<Uzantnomo, UzantIdentigilo>,
}
impl MemAŭtentigo {
pub fn new() -> Self {
Self {
mem: HashMap::new(),
sesioj: HashMap::new(),
uzantoj: HashMap::new(),
inversa_uzantoj: HashMap::new(),
}
}
}
impl AŭtentigoDB for MemAŭtentigo {
fn aŭtentigu(&self, ĵetono: Ĵetono) -> Option<Uzantnomo> {
self.mem.get(&ĵetono).cloned()
fn aŭtentigu(&self, ĵetono: Ĵetono) -> Option<(UzantIdentigilo, Uzantnomo)> {
let identigilo = self.sesioj.get(&ĵetono).cloned()?;
let uzantnomo = self.uzantoj.get(&identigilo).cloned()?;
Some((identigilo, uzantnomo))
}
fn kreu_ĵetono(&mut self, uzantnomo: Uzantnomo) -> Ĵetono {
let identigilo = self.inversa_uzantoj.get(&uzantnomo).cloned().unwrap();
let ĵetono = Ĵetono::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
self.sesioj.insert(ĵetono.clone(), identigilo);
ĵetono
}
}
pub struct DBAŭtentigo {
pool: Datumbazo,
}
impl DBAŭtentigo {
pub fn kreu(pool: Datumbazo) -> Self {
DBAŭtentigo { pool }
}
}
impl AŭtentigoDB for DBAŭtentigo {
fn aŭtentigu(&self, ĵetono: Ĵetono) -> Option<(UzantIdentigilo, Uzantnomo)> {
let konekto = self.pool.konektu().unwrap();
konekto
.query_row(
"SELECT uzantoj.id, uzantoj.nomo FROM sesioj INNER JOIN uzantoj on sesioj.uzanto == uzantoj.id WHERE sesioj.id = ?",
[String::from(ĵetono)],
|row| {
let identigilo = row.get("id")
.map(|s: String| UzantIdentigilo::from(s.as_str())).unwrap();
let nomo = row.get("nomo")
.map(|s: String| Uzantnomo::from(s.as_str())).unwrap();
Ok((identigilo, nomo))
},
)
.optional()
.unwrap()
}
fn kreu_ĵetono(&mut self, uzantnomo: Uzantnomo) -> Ĵetono {
let ĵetono = Ĵetono::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
self.mem.insert(ĵetono.clone(), uzantnomo);
let mut konekto = self.pool.konektu().unwrap();
let tr = konekto.transaction().unwrap();
let uzanta_id: Option<String> = tr
.query_row(
"SELECT id FROM uzantoj WHERE nomo = ?",
[String::from(uzantnomo.clone())],
|row| row.get("id"),
)
.optional()
.unwrap();
match uzanta_id {
None => panic!("uzanto ne ekzistas"),
Some(id) => {
tr.execute(
"INSERT INTO sesioj VALUES(?, ?)",
[String::from(ĵetono.clone()), id],
)
.unwrap();
}
}
tr.commit().unwrap();
ĵetono
}
}
@ -82,8 +161,8 @@ pub struct AŭtentigoPostulas;
impl Reject for AŭtentigoPostulas {}
pub fn kun_aŭtentigo(
auth_ctx: Arc<RwLock<impl AŭtentigoDB>>,
) -> impl Filter<Extract = (Uzantnomo,), Error = Rejection> + Clone {
auth_ctx: Arc<RwLock<impl AŭtentigoDB + Sync>>,
) -> impl Filter<Extract = ((UzantIdentigilo, Uzantnomo),), Error = Rejection> + Clone {
let auth_ctx = auth_ctx.clone();
warp::header("authentication").and_then({
let auth_ctx = auth_ctx.clone();
@ -91,7 +170,7 @@ pub fn kun_aŭtentigo(
let auth_ctx = auth_ctx.clone();
async move {
match auth_ctx.read().unwrap().aŭtentigu(text) {
Some(uzantnomo) => Ok(uzantnomo),
Some(salutiloj) => Ok(salutiloj),
None => Err(reject::custom(AŭtentigoPostulas)),
}
}

116
servilo/src/datumbazo.rs Normal file
View File

@ -0,0 +1,116 @@
use rusqlite::{params, Connection};
use std::{
ops::{Deref, DerefMut},
path::PathBuf,
sync::{Arc, Mutex},
};
pub struct ManagedConnection<'a> {
pool: &'a Datumbazo,
konekto: Option<Connection>,
}
#[derive(Clone)]
pub struct Datumbazo {
dosierindiko: PathBuf,
pool: Arc<Mutex<Vec<Connection>>>,
}
impl Datumbazo {
pub fn kreu(dosierindiko: PathBuf) -> Result<Datumbazo, anyhow::Error> {
let mut konekto = Connection::open(dosierindiko.clone())?;
let tx = konekto.transaction()?;
let versio: i32 = tx.pragma_query_value(None, "user_version", |r| r.get(0))?;
println!("versio: {}", versio);
if versio == 0 {
tx.execute_batch(
"CREATE TABLE uzantoj (id string primary key, nomo text);
CREATE TABLE rajtoj (uzanto string, rajto string, foreign key(uzanto) references uzanto(id));
CREATE TABLE sesioj (id string primary key not null, uzanto string, foreign key(uzanto) references uzantoj(id));
PRAGMA user_version = 1;",
)?;
}
let versio: i32 = tx.pragma_query_value(None, "user_version", |r| r.get(0))?;
println!("versio: {}", versio);
tx.commit()?;
Ok(Datumbazo {
dosierindiko,
pool: Arc::new(Mutex::new(vec![konekto])),
})
}
pub fn konektu<'a>(&'a self) -> Result<ManagedConnection<'a>, anyhow::Error> {
let mut pool = self.pool.lock().unwrap();
match pool.pop() {
Some(konekto) => Ok(ManagedConnection {
pool: &self,
konekto: Some(konekto),
}),
None => {
let konekto = Connection::open(self.dosierindiko.clone())?;
Ok(ManagedConnection {
pool: &self,
konekto: Some(konekto),
})
}
}
}
pub fn revenu(&self, konekto: Connection) {
let mut pool = self.pool.lock().unwrap();
pool.push(konekto);
}
}
impl Deref for ManagedConnection<'_> {
type Target = Connection;
fn deref(&self) -> &Connection {
self.konekto.as_ref().unwrap()
}
}
impl DerefMut for ManagedConnection<'_> {
fn deref_mut(&mut self) -> &mut Connection {
self.konekto.as_mut().unwrap()
}
}
impl Drop for ManagedConnection<'_> {
fn drop(&mut self) {
self.pool.revenu(self.konekto.take().unwrap());
}
}
#[cfg(test)]
mod test {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn povas_krei_uzanton() {
let vojo = NamedTempFile::new().unwrap().into_temp_path();
let datumbazo = Datumbazo::kreu(vojo.to_path_buf()).unwrap();
let mut konekto = datumbazo.konektu().unwrap();
let tr = konekto.transaction().unwrap();
tr.execute(
"INSERT INTO uzantoj VALUES(?, ?)",
params![1, String::from("mia-uzantnomo")],
)
.unwrap();
tr.commit().unwrap();
let konekto = datumbazo.konektu().unwrap();
let id: Option<u32> = konekto
.query_row(
"SELECT id FROM uzantoj WHERE nomo = ?",
[String::from("mia-uzantnomo")],
|row| row.get("id"),
)
.unwrap();
assert_eq!(id, Some(1));
}
}

View File

@ -1,21 +1,27 @@
use std::convert::Infallible;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use warp::{Filter, Rejection};
#[path = "aŭtentigo.rs"]
mod aŭtentigo;
use aŭtentigo::{kun_aŭtentigo, AŭtentigoDB, AŭtentigoPostulas, MemAŭtentigo, Uzantnomo};
use aŭtentigo::{kun_aŭtentigo, AŭtentigoDB, AŭtentigoPostulas, Uzantnomo};
mod datumbazo;
use datumbazo::Datumbazo;
use crate::rajtigo::{MemRajtigo, Rajtigo, Rajto};
mod rajtigo;
use rajtigo::{DBRajtigo, Rajtigo, Rajto};
use crate::aŭtentigo::{DBAŭtentigo, UzantIdentigilo};
async fn traktilo_de_listigu_ludantojn(
uzantnomo: Uzantnomo,
identigilo: UzantIdentigilo,
rajtigo: Arc<RwLock<impl Rajtigo>>,
) -> Result<impl warp::Reply, Rejection> {
if rajtigo.read().unwrap().havas_rajton(uzantnomo, |rajtoj| {
rajtoj.fold(false, |acc, r| acc || *r == Rajto::from("admin"))
if rajtigo.read().unwrap().havas_rajton(identigilo, |rajtoj| {
rajtoj.fold(false, |acc, r| acc || r == Rajto::from("admin"))
}) {
Ok(warp::reply::json(&vec!["Alice", "Betty", "Charles"]))
} else {
@ -42,17 +48,15 @@ async fn traktilu_erarojn(err: Rejection) -> Result<impl warp::Reply, Infallible
#[tokio::main]
pub async fn main() {
let auth_ctx = Arc::new(RwLock::new(MemAŭtentigo::new()));
let db = Datumbazo::kreu(PathBuf::from("../servilo.db")).unwrap();
let auth_ctx = Arc::new(RwLock::new(DBAŭtentigo::kreu(db.clone())));
let ĵetono = auth_ctx
.write()
.unwrap()
.kreu_ĵetono(Uzantnomo::from("Savanni"));
.kreu_ĵetono(Uzantnomo::from("savanni"));
println!("ĵetono: {}", String::from(ĵetono));
let rajtigo = Arc::new(RwLock::new(MemRajtigo::new()));
rajtigo.write().unwrap().aldonu_rajtoj(
Uzantnomo::from("Savanni"),
&mut (vec![Rajto::from("admin")].into_iter()),
);
let rajtigo = Arc::new(RwLock::new(DBRajtigo::kreu(db)));
let listigu_ludantojn = {
let auth_ctx = auth_ctx.clone();
@ -60,9 +64,14 @@ pub async fn main() {
.and(warp::path!("api" / "ludantoj"))
.and_then({
let rajtigo = rajtigo.clone();
move |uzantnomo| {
move |(identigilo, uzantnomo): (UzantIdentigilo, Uzantnomo)| {
println!(
"[{}] {}",
String::from(identigilo.clone()),
String::from(uzantnomo.clone())
);
let rajtigo = rajtigo.clone();
traktilo_de_listigu_ludantojn(uzantnomo, rajtigo)
traktilo_de_listigu_ludantojn(identigilo, rajtigo)
}
})
};

View File

@ -1,4 +1,5 @@
use crate::aŭtentigo::Uzantnomo;
use crate::{aŭtentigo::UzantIdentigilo, datumbazo::Datumbazo};
use rusqlite::OptionalExtension;
use std::collections::{HashMap, HashSet};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
@ -17,58 +18,124 @@ impl From<Rajto> for String {
}
pub trait Rajtigo {
fn havas_rajton<'a, F>(&'a self, uzantnomo: Uzantnomo, provo: F) -> bool
fn havas_rajton<'a, F>(&'a self, identigilo: UzantIdentigilo, provo: F) -> bool
where
F: FnOnce(&mut (dyn Iterator<Item = &'a Rajto> + 'a)) -> bool;
fn rajtoj(&self, uzantnomo: &Uzantnomo) -> Vec<Rajto>;
fn aldonu_rajtoj(&mut self, uzantnomo: Uzantnomo, rajtoj: &mut (dyn Iterator<Item = Rajto>));
fn foriru_rajtoj(&mut self, uzantnomo: Uzantnomo, rajtoj: &mut (dyn Iterator<Item = Rajto>));
fn foriru_uzanto(&mut self, uzantnomo: Uzantnomo);
F: FnOnce(&mut (dyn Iterator<Item = Rajto>)) -> bool;
fn rajtoj(&self, identigilo: &UzantIdentigilo) -> Vec<Rajto>;
fn aldonu_rajtoj(
&mut self,
identigilo: UzantIdentigilo,
rajtoj: &mut (dyn Iterator<Item = Rajto>),
);
fn foriru_rajtoj(
&mut self,
identigilo: UzantIdentigilo,
rajtoj: &mut (dyn Iterator<Item = Rajto>),
);
fn foriru_uzanto(&mut self, identigilo: UzantIdentigilo);
}
/*
pub struct MemRajtigo {
rajtoj: HashMap<Uzantnomo, HashSet<Rajto>>,
rajtoj: HashMap<UzantIdentigilo, HashSet<Rajto>>,
}
impl MemRajtigo {
pub fn new() -> Self {
MemRajtigo {
rajtoj: HashMap::new(),
}
}
pub fn new() -> Self {
MemRajtigo {
rajtoj: HashMap::new(),
}
}
}
impl Rajtigo for MemRajtigo {
fn havas_rajton<'a, F>(&'a self, uzantnomo: Uzantnomo, provo: F) -> bool
where
F: FnOnce(&mut (dyn Iterator<Item = &'a Rajto> + 'a)) -> bool,
{
match self.rajtoj.get(&uzantnomo) {
Some(rajtoj) => provo(&mut rajtoj.iter()),
None => false,
}
}
fn rajtoj(&self, uzantnomo: &Uzantnomo) -> Vec<Rajto> {
self.rajtoj
.get(&uzantnomo)
.map(|r| r.into_iter().cloned().collect::<Vec<Rajto>>())
.unwrap_or(vec![])
}
fn aldonu_rajtoj(&mut self, uzantnomo: Uzantnomo, rajtoj: &mut (dyn Iterator<Item = Rajto>)) {
let valoro = self.rajtoj.entry(uzantnomo).or_insert(HashSet::new());
fn havas_rajton<'a, F>(&'a self, identigilo: UzantIdentigilo, provo: F) -> bool
where
F: FnOnce(&mut (dyn Iterator<Item = Rajto> + 'a)) -> bool,
{
match self.rajtoj.get(&identigilo) {
Some(rajtoj) => provo(&mut rajtoj.clone().into_iter()),
None => false,
}
}
fn rajtoj(&self, identigilo: &UzantIdentigilo) -> Vec<Rajto> {
self.rajtoj
.get(&identigilo)
.map(|r| r.into_iter().cloned().collect::<Vec<Rajto>>())
.unwrap_or(vec![])
}
fn aldonu_rajtoj(
&mut self,
identigilo: UzantIdentigilo,
rajtoj: &mut (dyn Iterator<Item = Rajto>),
) {
let valoro = self.rajtoj.entry(identigilo).or_insert(HashSet::new());
while let Some(r) = rajtoj.next() {
valoro.insert(r);
}
}
fn foriru_rajtoj(&mut self, uzantnomo: Uzantnomo, rajtoj: &mut (dyn Iterator<Item = Rajto>)) {
let valoro = self.rajtoj.entry(uzantnomo).or_insert(HashSet::new());
while let Some(r) = rajtoj.next() {
valoro.insert(r);
}
}
fn foriru_rajtoj(
&mut self,
identigilo: UzantIdentigilo,
rajtoj: &mut (dyn Iterator<Item = Rajto>),
) {
let valoro = self.rajtoj.entry(identigilo).or_insert(HashSet::new());
while let Some(r) = rajtoj.next() {
valoro.remove(&r);
}
}
fn foriru_uzanto(&mut self, uzantnomo: Uzantnomo) {
let _ = self.rajtoj.remove(&uzantnomo);
while let Some(r) = rajtoj.next() {
valoro.remove(&r);
}
}
fn foriru_uzanto(&mut self, identigilo: UzantIdentigilo) {
let _ = self.rajtoj.remove(&identigilo);
}
}
*/
pub struct DBRajtigo {
pool: Datumbazo,
}
impl DBRajtigo {
pub fn kreu(pool: Datumbazo) -> Self {
DBRajtigo { pool }
}
}
impl Rajtigo for DBRajtigo {
fn havas_rajton<'a, F>(&'a self, identigilo: UzantIdentigilo, provo: F) -> bool
where
F: FnOnce(&mut (dyn Iterator<Item = Rajto>)) -> bool,
{
let konekto = self.pool.konektu().unwrap();
let mut stmt = konekto
.prepare("SELECT rajto FROM rajtoj WHERE uzanto = ?")
.unwrap();
let mut rajtoj = stmt
.query_map([String::from(identigilo)], |row| {
row.get("rajto").map(|s: String| Rajto::from(s.as_ref()))
})
.unwrap()
.map(|r| r.unwrap());
provo(&mut rajtoj)
}
fn rajtoj(&self, identigilo: &UzantIdentigilo) -> Vec<Rajto> {
vec![]
}
fn aldonu_rajtoj(
&mut self,
identigilo: UzantIdentigilo,
rajtoj: &mut (dyn Iterator<Item = Rajto>),
) {
}
fn foriru_rajtoj(
&mut self,
identigilo: UzantIdentigilo,
rajtoj: &mut (dyn Iterator<Item = Rajto>),
) {
}
fn foriru_uzanto(&mut self, identigilo: UzantIdentigilo) {}
}

View File

@ -14,6 +14,7 @@ in pkgs.mkShell {
pkgs.nodejs
pkgs.openssl
pkgs.pkg-config
pkgs.sqlite
pkgs.wrapGAppsHook
rust
unstable.rust-analyzer