diff --git a/Cargo.lock b/Cargo.lock index 5c5e5e9..4a36088 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,9 +60,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -75,33 +75,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", @@ -267,9 +267,9 @@ dependencies = [ [[package]] name = "axum-jwt-login" -version = "0.1.1" +version = "0.1.2" source = "sparse+https://crates.esteil.dedyn.io/api/v1/crates/" -checksum = "b45378650bae1dfcc3ed411ffc7b2bfe5977f6c3de5f140993b1ee2979cccc4c" +checksum = "7866571ffba399da64ce58fab21c6eb0ad76a312ec7d546d5dfada2435ab26ea" dependencies = [ "axum", "chrono", @@ -322,9 +322,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bitflags" @@ -489,9 +489,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" @@ -1187,9 +1187,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "bytes", "futures-core", diff --git a/Cargo.toml b/Cargo.toml index bc3b1e0..a7ac8b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ colored = "3.0.0" uuid = { version = "1.17.0", features = ["v4"] } blake3 = "1.1.18" minisign = "0.7.9" -axum-jwt-login = { version = "0.1.1", registry = "kellnr" } +axum-jwt-login = { version = "0.1.2", registry = "kellnr" } rust-argon2 = "2.1.0" rand = "0.9.1" ldap3 = "0.11.5" diff --git a/src/api/backend/integrity_verification.rs b/src/api/backend/integrity_verification.rs index b372462..160c0ef 100644 --- a/src/api/backend/integrity_verification.rs +++ b/src/api/backend/integrity_verification.rs @@ -17,13 +17,13 @@ pub struct IntegrityVerifier { } impl IntegrityVerifier { - pub fn new() -> Result { - Ok(Self { + pub fn new() -> Self { + Self { mac: Hasher::new_derive_key(include_str!("./verification_secret")), - }) + } } - pub fn sign(&self, data: &T) -> Result + pub fn sign(&self, data: &T) -> Hash where T: CustomHash, { @@ -33,7 +33,7 @@ impl IntegrityVerifier { let signature = mac.finalize(); mac.reset(); - Ok(signature) + signature } pub fn verify(&self, data: &T, signature: Hash) -> Result<(), ApiError> @@ -73,14 +73,14 @@ mod test { } } - let verifier = IntegrityVerifier::new().unwrap(); + let verifier = IntegrityVerifier::new(); let mut item = TestData { one: 24, two: "MyTesting".to_string(), }; - let signature = verifier.sign(&item).unwrap(); + let signature = verifier.sign(&item); assert!(verifier.verify(&item, signature).is_ok()); item.two = "Wrong".to_string(); diff --git a/src/api/routes/api_keys/models.rs b/src/api/routes/api_keys/models.rs index f1e8e04..4ef7556 100644 --- a/src/api/routes/api_keys/models.rs +++ b/src/api/routes/api_keys/models.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::BTreeSet; use axum_jwt_login::ApiKey as ApiKeyTrait; use blake3::{Hash, Hasher}; @@ -59,7 +59,7 @@ impl ApiKey { pub fn create( name: &str, requires_auth: bool, - permissions: HashSet, + permissions: BTreeSet, config: &Configuration, ) -> Result<(String, Self), ApiError> { // create uuid @@ -128,14 +128,14 @@ impl ApiKeyTrait for ApiKey { fn requires_user_auth(&self) -> bool { self.auth_required } - fn get_permissions(&self) -> HashSet { + fn get_permissions(&self) -> BTreeSet { self.permissions.0.clone() } } #[cfg(test)] mod test { - use std::collections::HashSet; + use std::collections::BTreeSet; use crate::config::Configuration; @@ -148,7 +148,7 @@ mod test { ..Default::default() }; let (key, api_key) = - ApiKey::create("name", true, HashSet::new(), &config).expect("Error creating API Key"); + ApiKey::create("name", true, BTreeSet::new(), &config).expect("Error creating API Key"); println!("{key}, {api_key:?}"); assert_eq!(Ok(()), api_key.validate(&key)); assert_ne!(Ok(()), api_key.validate("124")); diff --git a/src/api/routes/users/models.rs b/src/api/routes/users/models.rs index fdb5320..9a09caf 100644 --- a/src/api/routes/users/models.rs +++ b/src/api/routes/users/models.rs @@ -1,5 +1,5 @@ use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeSet, HashMap, HashSet}, fmt, }; @@ -12,7 +12,10 @@ use ts_rs::TS; use utoipa::ToSchema; use crate::{ - api::routes::{departments::models::ShortDepartment, models::Status}, + api::{ + backend::integrity_verification::CustomHash, + routes::{departments::models::ShortDepartment, models::Status}, + }, errors::ApiError, }; @@ -104,6 +107,17 @@ impl fmt::Debug for User { } } +impl CustomHash for User { + fn updater(&self, hasher: &mut blake3::Hasher) { + hasher.update(self.user_id.as_bytes()); + hasher.update(self.name.as_bytes()); + hasher.update(self.surname.as_bytes()); + for permission in &self.permissions.0 { + hasher.update(permission.to_string().as_bytes()); + } + } +} + impl UserPermissions for User { type Error = ApiError; type Permission = Permission; @@ -113,10 +127,10 @@ impl UserPermissions for User { self.user_id.clone() } - fn get_user_permissions(&self) -> Result, Self::Error> { + fn get_user_permissions(&self) -> Result, Self::Error> { Ok(self.permissions.0.iter().cloned().collect()) } - fn get_group_permissions(&self) -> Result, Self::Error> { + fn get_group_permissions(&self) -> Result, Self::Error> { Ok(self.group_permissions.0.iter().cloned().collect()) } } diff --git a/src/api/routes/users/permissions.rs b/src/api/routes/users/permissions.rs index 6ea70eb..5a8f73e 100644 --- a/src/api/routes/users/permissions.rs +++ b/src/api/routes/users/permissions.rs @@ -1,11 +1,13 @@ -use std::{collections::HashSet, convert::Infallible, fmt::Display, str::FromStr}; +use std::{collections::BTreeSet, convert::Infallible, fmt::Display, str::FromStr}; use serde::{Deserialize, Serialize}; use strum::EnumString; use ts_rs::TS; use utoipa::ToSchema; -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, TS, ToSchema)] +#[derive( + Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord, TS, ToSchema, +)] pub enum Permission { Read(PermissionDetail), Write(PermissionDetail), @@ -52,6 +54,8 @@ impl Display for Permission { Deserialize, PartialEq, Eq, + PartialOrd, + Ord, Hash, EnumString, strum::Display, @@ -70,17 +74,17 @@ pub enum PermissionDetail { } #[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)] -pub struct PermissionContainer(pub HashSet); +pub struct PermissionContainer(pub BTreeSet); impl From>> for PermissionContainer { fn from(value: Option>) -> Self { let set = match value { - Some(values) => HashSet::from_iter( + Some(values) => BTreeSet::from_iter( values .iter() .map(|s| Permission::from_str(s).unwrap_or(Permission::None)), ), - None => HashSet::new(), + None => BTreeSet::new(), }; Self(set) @@ -89,9 +93,24 @@ impl From>> for PermissionContainer { #[cfg(test)] mod test { - use std::str::FromStr; + use std::{ + collections::{BTreeSet, HashSet}, + str::FromStr, + }; - use crate::api::routes::users::permissions::PermissionDetail; + use chrono::Local; + + use crate::api::{ + backend::integrity_verification::IntegrityVerifier, + routes::{ + departments::models::ShortDepartment, + models::Status, + users::{ + models::{DepartmentContainer, GroupContainer, User}, + permissions::{PermissionContainer, PermissionDetail}, + }, + }, + }; use super::Permission; @@ -103,4 +122,41 @@ mod test { let parsed = Permission::from_str("Write:Users").unwrap(); assert_eq!(parsed, Permission::Write(PermissionDetail::Users)); } + + #[tokio::test] + async fn test_integrity_validation() { + let mut user = User { + user_id: "id".to_string(), + active_directory_auth: false, + password: "Some PW".to_string(), + name: "Clark".to_string(), + surname: "Kent".to_string(), + email: "Superman@heros.com".to_string(), + department: DepartmentContainer(Some(ShortDepartment { + id: 1, + name: "Some name".to_string(), + description: None, + })), + groups: GroupContainer(HashSet::new()), + group_permissions: PermissionContainer(BTreeSet::new()), + permissions: PermissionContainer(BTreeSet::from_iter([Permission::Read( + PermissionDetail::Departments, + )])), + status_flag: Status::Active, + creation_date: Local::now().naive_local(), + last_change: Local::now().naive_local(), + }; + + let verifier = IntegrityVerifier::new(); + + let hash = verifier.sign(&user); + + assert!(verifier.verify(&user, hash).is_ok()); + + user.permissions + .0 + .insert(Permission::Write(PermissionDetail::Units)); + + assert!(verifier.verify(&user, hash).is_err()); + } } diff --git a/src/cli.rs b/src/cli.rs index 9d62a2f..eb194a1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, path::PathBuf}; +use std::{collections::BTreeSet, path::PathBuf}; use clap::{command, Parser, Subcommand}; use colored::Colorize; @@ -134,7 +134,7 @@ impl Cli { } => { // create API Key let (key_secret, key) = - ApiKey::create(name, *requires_auth, HashSet::new(), config) + ApiKey::create(name, *requires_auth, BTreeSet::new(), config) .change_context(AppError)?; // Add API Key to Database