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)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Domain {
|
pub struct Domain {
|
||||||
pub name: String,
|
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
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_users(&self, domain: Option<String>) -> Result<Vec<User>, BoxedError> {
|
async fn list_all_users(&self) -> Result<Vec<User>, BoxedError> {
|
||||||
self.inner.read().await.list_users(domain).await
|
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,
|
current_user: &User,
|
||||||
) -> Result<Result<(), UserCreationError>, BoxedError>;
|
) -> 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.
|
/// A `None` domain requested lists global service users.
|
||||||
fn list_users(
|
fn list_domain_users(
|
||||||
&self,
|
&self,
|
||||||
domain: Option<String>,
|
domain: Option<String>,
|
||||||
) -> impl std::future::Future<Output = Result<Vec<User>, BoxedError>> + Send;
|
) -> impl std::future::Future<Output = Result<Vec<User>, BoxedError>> + Send;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,21 @@
|
||||||
use std::future::{Future, ready};
|
use std::future::{Future, ready};
|
||||||
|
|
||||||
use crate::db::error::{BoxedError, DomainCreationError, UserCreationError};
|
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)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct MemoryDatabase {
|
pub struct MemoryDatabase {
|
||||||
|
// We store data in tables like in SQL
|
||||||
pub domains: Vec<Domain>,
|
pub domains: Vec<Domain>,
|
||||||
/// Where global users/groups are registered
|
#[expect(unused)]
|
||||||
pub global_domain: Domain,
|
pub groups: Vec<Group>,
|
||||||
|
pub users: Vec<User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryDatabase {
|
impl MemoryDatabase {
|
||||||
pub fn new() -> Database<Self> {
|
pub fn new() -> Database<Self> {
|
||||||
Database::new(Self::default())
|
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 {
|
impl DatabaseInterface for MemoryDatabase {
|
||||||
|
|
@ -39,8 +37,6 @@ impl DatabaseInterface for MemoryDatabase {
|
||||||
|
|
||||||
self.domains.push(Domain {
|
self.domains.push(Domain {
|
||||||
name: domain.to_string(),
|
name: domain.to_string(),
|
||||||
users: vec![],
|
|
||||||
groups: vec![],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ready(Ok(Ok(())))
|
ready(Ok(Ok(())))
|
||||||
|
|
@ -54,37 +50,36 @@ impl DatabaseInterface for MemoryDatabase {
|
||||||
ready(Ok(Some(domain.clone())))
|
ready(Ok(Some(domain.clone())))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_user(&self, req_user: &UserRef) -> Result<Option<User>, BoxedError> {
|
fn get_user(
|
||||||
let Some(req_domain) = &req_user.domain else {
|
&self,
|
||||||
// If no domain is provided for the user query, look up the global users
|
req_user: &UserRef,
|
||||||
return Ok(self.global_domain.get_user(req_user));
|
) -> impl Future<Output = Result<Option<User>, BoxedError>> {
|
||||||
};
|
ready(Ok(self
|
||||||
|
.users
|
||||||
let Some(domain) = self.get_domain(req_domain).await? else {
|
.iter()
|
||||||
return Ok(None);
|
.find(|u| u.username == req_user.username && u.domain == req_user.domain)
|
||||||
};
|
.cloned()))
|
||||||
|
|
||||||
Ok(domain.get_user(req_user))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_user(
|
async fn create_user(
|
||||||
&mut self,
|
&mut self,
|
||||||
user: User,
|
user: User,
|
||||||
) -> impl Future<Output = Result<Result<(), UserCreationError>, BoxedError>> {
|
) -> Result<Result<(), UserCreationError>, BoxedError> {
|
||||||
let user_ref = user.user_ref();
|
let user_ref = user.user_ref();
|
||||||
|
|
||||||
let Some(req_domain) = &user.domain else {
|
if self.get_user(&user_ref).await?.is_some() {
|
||||||
// No domain requested, this is a global user creation
|
return Ok(Err(UserCreationError::UserAlreadyExists(user_ref)));
|
||||||
return ready(Ok(self.global_domain.create_user(user, user_ref)));
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let Some(domain) = self.get_domain_mut(req_domain) else {
|
// If a domain is requested (i.e. not a global user), make sure the domain exists
|
||||||
return ready(Ok(Err(UserCreationError::DomainNotFound(
|
if let Some(req_domain) = &user.domain
|
||||||
req_domain.clone(),
|
&& 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(
|
async fn try_create_user(
|
||||||
|
|
@ -109,23 +104,19 @@ impl DatabaseInterface for MemoryDatabase {
|
||||||
self.create_user(new_user).await
|
self.create_user(new_user).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_users(&self, domain: Option<String>) -> Result<Vec<User>, BoxedError> {
|
fn list_all_users(&self) -> impl Future<Output = Result<Vec<User>, BoxedError>> {
|
||||||
if let Some(domain) = domain {
|
ready(Ok(self.users.clone()))
|
||||||
if domain.is_empty() {
|
}
|
||||||
Ok(self.global_domain.users.clone())
|
|
||||||
} else {
|
fn list_domain_users(
|
||||||
let Some(domain) = self.get_domain(&domain).await? else {
|
&self,
|
||||||
return Ok(vec![]);
|
domain: Option<String>,
|
||||||
};
|
) -> impl Future<Output = Result<Vec<User>, BoxedError>> {
|
||||||
Ok(domain.users.clone())
|
ready(Ok(self
|
||||||
}
|
.users
|
||||||
} else {
|
.iter()
|
||||||
// Aggregate all users
|
.filter(|u| u.domain == domain)
|
||||||
let mut users = self.global_domain.users.clone();
|
.cloned()
|
||||||
for domain in &self.domains {
|
.collect()))
|
||||||
users.extend(domain.users.clone());
|
|
||||||
}
|
|
||||||
Ok(users)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,11 @@ pub async fn home<D: DatabaseInterface>(
|
||||||
// When the user has no domain (service admin) list all domains
|
// When the user has no domain (service admin) list all domains
|
||||||
let op = Operation::ListUsers(session.user.domain.clone());
|
let op = Operation::ListUsers(session.user.domain.clone());
|
||||||
let other_users = if session.user.can_perform(&op) {
|
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,
|
Ok(other_users) => other_users,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return format!("Database error: {e}").into_response();
|
return format!("Database error: {e}").into_response();
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% if other_users %}
|
{% if other_users %}
|
||||||
<div>
|
<div>
|
||||||
<h2>Other users you have permission to see</h2>
|
<h2>Other users you have permission to see on your own domain</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{% for user in other_users %}
|
{% for user in other_users %}
|
||||||
<li>{{ user.mail }}</li>
|
<li>{{ user.mail }}</li>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue