Integrity Verification & BTreeSet

This commit is contained in:
Hlars 2025-06-05 21:21:45 +02:00
parent ef9f0fd31c
commit f4776e3c8b
7 changed files with 114 additions and 44 deletions

36
Cargo.lock generated
View File

@ -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",

View File

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

View File

@ -17,13 +17,13 @@ pub struct IntegrityVerifier {
}
impl IntegrityVerifier {
pub fn new() -> Result<Self, ApiError> {
Ok(Self {
pub fn new() -> Self {
Self {
mac: Hasher::new_derive_key(include_str!("./verification_secret")),
})
}
}
pub fn sign<T>(&self, data: &T) -> Result<Hash, ApiError>
pub fn sign<T>(&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<T>(&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();

View File

@ -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<Permission>,
permissions: BTreeSet<Permission>,
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<Self::Permission> {
fn get_permissions(&self) -> BTreeSet<Self::Permission> {
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"));

View File

@ -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<HashSet<Self::Permission>, Self::Error> {
fn get_user_permissions(&self) -> Result<BTreeSet<Self::Permission>, Self::Error> {
Ok(self.permissions.0.iter().cloned().collect())
}
fn get_group_permissions(&self) -> Result<HashSet<Self::Permission>, Self::Error> {
fn get_group_permissions(&self) -> Result<BTreeSet<Self::Permission>, Self::Error> {
Ok(self.group_permissions.0.iter().cloned().collect())
}
}

View File

@ -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<Permission>);
pub struct PermissionContainer(pub BTreeSet<Permission>);
impl From<Option<Vec<String>>> for PermissionContainer {
fn from(value: Option<Vec<String>>) -> 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<Option<Vec<String>>> 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());
}
}

View File

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