202 lines
5.4 KiB
Rust
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)
|
|
}
|