refactor: Store domains separately from users
This commit is contained in:
parent
f1a2721cbe
commit
58a8c375f0
5 changed files with 59 additions and 88 deletions
|
|
@ -1,35 +1,4 @@
|
|||
use crate::db::error::UserCreationError;
|
||||
use crate::db::{Group, User, UserRef};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Domain {
|
||||
pub name: String,
|
||||
pub users: Vec<User>,
|
||||
#[expect(unused)]
|
||||
pub groups: Vec<Group>,
|
||||
}
|
||||
|
||||
impl Domain {
|
||||
pub fn get_user(&self, req_user: &UserRef) -> Option<User> {
|
||||
assert!(
|
||||
req_user.domain.as_deref().unwrap_or("") == self.name,
|
||||
"Should only call Domain::get_user on the matching domain. Asked for {:?} on domain {}",
|
||||
req_user,
|
||||
self.name
|
||||
);
|
||||
|
||||
self.users
|
||||
.iter()
|
||||
.find(|u| u.username == req_user.username)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn create_user(&mut self, user: User, user_ref: UserRef) -> Result<(), UserCreationError> {
|
||||
if self.get_user(&user_ref).is_some() {
|
||||
return Err(UserCreationError::UserAlreadyExists(user_ref));
|
||||
}
|
||||
|
||||
self.users.push(user);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,8 +36,12 @@ impl<D: DatabaseInterface> DatabaseInterface for Database<D> {
|
|||
.await
|
||||
}
|
||||
|
||||
async fn list_users(&self, domain: Option<String>) -> Result<Vec<User>, BoxedError> {
|
||||
self.inner.read().await.list_users(domain).await
|
||||
async fn list_all_users(&self) -> Result<Vec<User>, BoxedError> {
|
||||
self.inner.read().await.list_all_users().await
|
||||
}
|
||||
|
||||
async fn list_domain_users(&self, domain: Option<String>) -> Result<Vec<User>, BoxedError> {
|
||||
self.inner.read().await.list_domain_users(domain).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,10 +67,13 @@ pub trait DatabaseInterface: Clone + Send + Sync + 'static {
|
|||
current_user: &User,
|
||||
) -> Result<Result<(), UserCreationError>, BoxedError>;
|
||||
|
||||
/// Use empty string domain to request global users (TODO: this is not very DX)
|
||||
#[expect(unused)]
|
||||
fn list_all_users(&self) -> impl Future<Output = Result<Vec<User>, BoxedError>>;
|
||||
|
||||
/// List users on a specific domain.
|
||||
///
|
||||
/// When domain does not exist, the returned list is empty.
|
||||
fn list_users(
|
||||
/// A `None` domain requested lists global service users.
|
||||
fn list_domain_users(
|
||||
&self,
|
||||
domain: Option<String>,
|
||||
) -> impl std::future::Future<Output = Result<Vec<User>, BoxedError>> + Send;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
use std::future::{Future, ready};
|
||||
|
||||
use crate::db::error::{BoxedError, DomainCreationError, UserCreationError};
|
||||
use crate::db::{Database, DatabaseInterface, Domain, Operation, User, UserRef};
|
||||
use crate::db::{Database, DatabaseInterface, Domain, Group, Operation, User, UserRef};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MemoryDatabase {
|
||||
// We store data in tables like in SQL
|
||||
pub domains: Vec<Domain>,
|
||||
/// Where global users/groups are registered
|
||||
pub global_domain: Domain,
|
||||
#[expect(unused)]
|
||||
pub groups: Vec<Group>,
|
||||
pub users: Vec<User>,
|
||||
}
|
||||
|
||||
impl MemoryDatabase {
|
||||
pub fn new() -> Database<Self> {
|
||||
Database::new(Self::default())
|
||||
}
|
||||
|
||||
pub fn get_domain_mut(&mut self, domain: &str) -> Option<&mut Domain> {
|
||||
self.domains.iter_mut().find(|d| d.name == domain)
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseInterface for MemoryDatabase {
|
||||
|
|
@ -39,8 +37,6 @@ impl DatabaseInterface for MemoryDatabase {
|
|||
|
||||
self.domains.push(Domain {
|
||||
name: domain.to_string(),
|
||||
users: vec![],
|
||||
groups: vec![],
|
||||
});
|
||||
|
||||
ready(Ok(Ok(())))
|
||||
|
|
@ -54,37 +50,36 @@ impl DatabaseInterface for MemoryDatabase {
|
|||
ready(Ok(Some(domain.clone())))
|
||||
}
|
||||
|
||||
async fn get_user(&self, req_user: &UserRef) -> Result<Option<User>, BoxedError> {
|
||||
let Some(req_domain) = &req_user.domain else {
|
||||
// If no domain is provided for the user query, look up the global users
|
||||
return Ok(self.global_domain.get_user(req_user));
|
||||
};
|
||||
|
||||
let Some(domain) = self.get_domain(req_domain).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(domain.get_user(req_user))
|
||||
fn get_user(
|
||||
&self,
|
||||
req_user: &UserRef,
|
||||
) -> impl Future<Output = Result<Option<User>, BoxedError>> {
|
||||
ready(Ok(self
|
||||
.users
|
||||
.iter()
|
||||
.find(|u| u.username == req_user.username && u.domain == req_user.domain)
|
||||
.cloned()))
|
||||
}
|
||||
|
||||
fn create_user(
|
||||
async fn create_user(
|
||||
&mut self,
|
||||
user: User,
|
||||
) -> impl Future<Output = Result<Result<(), UserCreationError>, BoxedError>> {
|
||||
) -> Result<Result<(), UserCreationError>, BoxedError> {
|
||||
let user_ref = user.user_ref();
|
||||
|
||||
let Some(req_domain) = &user.domain else {
|
||||
// No domain requested, this is a global user creation
|
||||
return ready(Ok(self.global_domain.create_user(user, user_ref)));
|
||||
};
|
||||
if self.get_user(&user_ref).await?.is_some() {
|
||||
return Ok(Err(UserCreationError::UserAlreadyExists(user_ref)));
|
||||
}
|
||||
|
||||
let Some(domain) = self.get_domain_mut(req_domain) else {
|
||||
return ready(Ok(Err(UserCreationError::DomainNotFound(
|
||||
req_domain.clone(),
|
||||
))));
|
||||
};
|
||||
// If a domain is requested (i.e. not a global user), make sure the domain exists
|
||||
if let Some(req_domain) = &user.domain
|
||||
&& self.get_domain(req_domain).await?.is_none()
|
||||
{
|
||||
return Ok(Err(UserCreationError::DomainNotFound(req_domain.clone())));
|
||||
}
|
||||
|
||||
ready(Ok(domain.create_user(user, user_ref)))
|
||||
self.users.push(user);
|
||||
Ok(Ok(()))
|
||||
}
|
||||
|
||||
async fn try_create_user(
|
||||
|
|
@ -109,23 +104,19 @@ impl DatabaseInterface for MemoryDatabase {
|
|||
self.create_user(new_user).await
|
||||
}
|
||||
|
||||
async fn list_users(&self, domain: Option<String>) -> Result<Vec<User>, BoxedError> {
|
||||
if let Some(domain) = domain {
|
||||
if domain.is_empty() {
|
||||
Ok(self.global_domain.users.clone())
|
||||
} else {
|
||||
let Some(domain) = self.get_domain(&domain).await? else {
|
||||
return Ok(vec![]);
|
||||
};
|
||||
Ok(domain.users.clone())
|
||||
}
|
||||
} else {
|
||||
// Aggregate all users
|
||||
let mut users = self.global_domain.users.clone();
|
||||
for domain in &self.domains {
|
||||
users.extend(domain.users.clone());
|
||||
}
|
||||
Ok(users)
|
||||
}
|
||||
fn list_all_users(&self) -> impl Future<Output = Result<Vec<User>, BoxedError>> {
|
||||
ready(Ok(self.users.clone()))
|
||||
}
|
||||
|
||||
fn list_domain_users(
|
||||
&self,
|
||||
domain: Option<String>,
|
||||
) -> impl Future<Output = Result<Vec<User>, BoxedError>> {
|
||||
ready(Ok(self
|
||||
.users
|
||||
.iter()
|
||||
.filter(|u| u.domain == domain)
|
||||
.cloned()
|
||||
.collect()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,11 @@ pub async fn home<D: DatabaseInterface>(
|
|||
// When the user has no domain (service admin) list all domains
|
||||
let op = Operation::ListUsers(session.user.domain.clone());
|
||||
let other_users = if session.user.can_perform(&op) {
|
||||
match state.db.list_users(session.user.domain.clone()).await {
|
||||
match state
|
||||
.db
|
||||
.list_domain_users(session.user.domain.clone())
|
||||
.await
|
||||
{
|
||||
Ok(other_users) => other_users,
|
||||
Err(e) => {
|
||||
return format!("Database error: {e}").into_response();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
</div>
|
||||
{% if other_users %}
|
||||
<div>
|
||||
<h2>Other users you have permission to see</h2>
|
||||
<h2>Other users you have permission to see on your own domain</h2>
|
||||
<ul>
|
||||
{% for user in other_users %}
|
||||
<li>{{ user.mail }}</li>
|
||||
|
|
|
|||
Loading…
Reference in a new issue