This repository has been archived on 2025-07-31. You can view files and clone it, but cannot push or open issues or pull requests.
Files
glyph/src/routes/groups.rs
2025-06-07 12:57:10 +01:00

202 lines
5.4 KiB
Rust

use std::collections::HashMap;
use axum::{
Json, Router, extract,
http::StatusCode,
response::{IntoResponse, Redirect},
routing,
};
use non_empty_string::NonEmptyString;
use nonempty::NonEmpty;
use serde::{Deserialize, Serialize};
use crate::{config::Config, models::authelia, routes::auth, state::State};
#[derive(Debug, Serialize)]
struct GroupResponse {
users: Vec<NonEmptyString>,
}
type GroupsResponse = HashMap<NonEmptyString, GroupResponse>;
pub async fn get_all(
_: auth::User,
extract::State(config): extract::State<Config>,
) -> Result<impl IntoResponse, StatusCode> {
let users = authelia::UsersFile::load(&config.authelia.user_database)
.await
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
let mut groups_response: GroupsResponse = HashMap::new();
for (username, user) in users.iter() {
for group in &user.groups {
let group_response = groups_response
.entry(group.clone())
.or_insert_with(|| GroupResponse { users: Vec::new() });
group_response.users.push(username.clone());
}
}
Ok(Json(groups_response))
}
pub async fn get(
_: auth::User,
extract::Path(name): extract::Path<NonEmptyString>,
extract::State(config): extract::State<Config>,
) -> Result<impl IntoResponse, StatusCode> {
let users = authelia::UsersFile::load(&config.authelia.user_database)
.await
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
let group_users = users
.iter()
.filter_map(|(username, user)| {
if user.groups.contains(&name) {
Some(username.clone())
} else {
None
}
})
.collect::<Vec<_>>();
if group_users.is_empty() {
return Err(StatusCode::NOT_FOUND);
}
Ok(Json(GroupResponse { users: group_users }))
}
#[derive(Debug, Deserialize)]
pub struct GroupCreate {
name: NonEmptyString,
users: NonEmpty<NonEmptyString>,
}
pub async fn create(
_: auth::User,
extract::State(config): extract::State<Config>,
extract::Json(group_create): extract::Json<GroupCreate>,
) -> Result<impl IntoResponse, StatusCode> {
let mut users = authelia::UsersFile::load(&config.authelia.user_database)
.await
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
if users
.iter()
.any(|(_, user)| user.groups.contains(&group_create.name))
{
return Err(StatusCode::CONFLICT);
}
if !group_create
.users
.iter()
.all(|user| users.contains_key(user.as_str()))
{
return Err(StatusCode::NOT_FOUND);
}
for user in group_create.users {
users
.get_mut(user.as_str())
.unwrap()
.groups
.push(group_create.name.clone());
}
Ok(())
}
#[derive(Debug, Deserialize)]
pub struct GroupUpdate {
users: Option<NonEmpty<NonEmptyString>>,
}
pub async fn update(
session_user: auth::User,
extract::Path(name): extract::Path<NonEmptyString>,
extract::State(config): extract::State<Config>,
extract::Json(group_update): extract::Json<GroupUpdate>,
) -> Result<impl IntoResponse, StatusCode> {
let mut users = authelia::UsersFile::load(&config.authelia.user_database)
.await
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
if !users.iter().any(|(_, user)| user.groups.contains(&name)) {
return Err(StatusCode::NOT_FOUND);
}
let mut logout = false;
if let Some(new_users) = group_update.users {
for (username, user) in users.iter_mut() {
if new_users.contains(username) {
if !user.groups.contains(&name) {
user.groups.push(name.clone());
}
} else {
user.groups.retain(|g| g != &name);
}
if *username == *session_user.username
&& !user.groups.contains(&config.oauth.admin_group)
{
logout = true;
}
}
}
users
.save(&config.authelia.user_database)
.await
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
if logout {
return Ok(Redirect::to("/api/auth/logout").into_response());
}
Ok(().into_response())
}
pub async fn delete(
_: auth::User,
extract::Path(name): extract::Path<NonEmptyString>,
extract::State(config): extract::State<Config>,
) -> Result<impl IntoResponse, StatusCode> {
if name == config.oauth.admin_group {
return Err(StatusCode::FORBIDDEN);
}
let mut users = authelia::UsersFile::load(&config.authelia.user_database)
.await
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
if !users.iter().any(|(_, user)| user.groups.contains(&name)) {
return Err(StatusCode::NOT_FOUND);
}
for user in users.values_mut() {
user.groups.retain(|g| g != &name);
}
users
.save(&config.authelia.user_database)
.await
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
Ok(())
}
pub fn routes(state: State) -> Router {
Router::new()
.route("/groups", routing::get(get_all))
.route("/groups/{username}", routing::get(get))
.route("/groups", routing::post(create))
.route("/groups/{username}", routing::put(update))
.route("/groups/{username}", routing::delete(delete))
.with_state(state)
}