Use file as source of truth
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -8,41 +8,37 @@ use axum::{
|
||||
};
|
||||
|
||||
use non_empty_string::NonEmptyString;
|
||||
use nonempty::NonEmpty;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
models::{self, groups::Group},
|
||||
routes::auth,
|
||||
state::State,
|
||||
};
|
||||
use crate::{config::Config, models::authelia, routes::auth, state::State};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct GroupResponse {
|
||||
users: Vec<String>,
|
||||
users: Vec<NonEmptyString>,
|
||||
}
|
||||
|
||||
impl From<models::groups::GroupWithUsers> for GroupResponse {
|
||||
fn from(group: models::groups::GroupWithUsers) -> Self {
|
||||
Self { users: group.users }
|
||||
}
|
||||
}
|
||||
|
||||
type GroupsResponse = HashMap<String, GroupResponse>;
|
||||
type GroupsResponse = HashMap<NonEmptyString, GroupResponse>;
|
||||
|
||||
pub async fn get_all(
|
||||
_: auth::User,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
extract::State(config): extract::State<Config>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let groups_with_users = models::groups::GroupWithUsers::select(&pg_pool)
|
||||
let users = authelia::UsersFile::load(&config.authelia.user_database)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
let groups_response = groups_with_users
|
||||
.into_iter()
|
||||
.map(|group| (group.name.clone(), GroupResponse::from(group)))
|
||||
.collect::<GroupsResponse>();
|
||||
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))
|
||||
}
|
||||
@@ -50,102 +46,114 @@ pub async fn get_all(
|
||||
pub async fn get(
|
||||
_: auth::User,
|
||||
extract::Path(name): extract::Path<NonEmptyString>,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
extract::State(config): extract::State<Config>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let group_with_users = models::groups::GroupWithUsers::select_by_name(&pg_pool, name.as_str())
|
||||
let users = authelia::UsersFile::load(&config.authelia.user_database)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
Ok(Json(GroupResponse::from(group_with_users)))
|
||||
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: Vec<NonEmptyString>,
|
||||
users: NonEmpty<NonEmptyString>,
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
_: auth::User,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
extract::State(config): extract::State<Config>,
|
||||
extract::Json(group_create): extract::Json<GroupCreate>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
if models::groups::Group::select_by_name(&pg_pool, group_create.name.as_str())
|
||||
let mut users = authelia::UsersFile::load(&config.authelia.user_database)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.is_some()
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
if users
|
||||
.iter()
|
||||
.any(|(_, user)| user.groups.contains(&group_create.name))
|
||||
{
|
||||
return Err(StatusCode::CONFLICT);
|
||||
}
|
||||
|
||||
let users = group_create
|
||||
if !group_create
|
||||
.users
|
||||
.into_iter()
|
||||
.map(|u| u.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !models::users::User::all_exist_by_names(&pg_pool, &users)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.iter()
|
||||
.all(|user| users.contains_key(user.as_str()))
|
||||
{
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
let group_with_users = models::groups::GroupWithUsers {
|
||||
name: group_create.name.to_string(),
|
||||
users,
|
||||
};
|
||||
|
||||
models::groups::GroupWithUsers::insert(&pg_pool, &group_with_users)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
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<Vec<NonEmptyString>>,
|
||||
users: Option<NonEmpty<NonEmptyString>>,
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
session_user: auth::User,
|
||||
extract::Path(name): extract::Path<NonEmptyString>,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
extract::State(config): extract::State<Config>,
|
||||
extract::Json(group_update): extract::Json<GroupUpdate>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let group = models::groups::Group::select_by_name(&pg_pool, name.as_str())
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
let mut logout = false;
|
||||
|
||||
if let Some(users) = &group_update.users {
|
||||
let users = users.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||
|
||||
if !models::users::User::all_exist_by_names(&pg_pool, &users)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
{
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
models::intersections::UsersGroups::set_users_for_group(
|
||||
&pg_pool,
|
||||
group.name.as_str(),
|
||||
&users,
|
||||
)
|
||||
let mut users = authelia::UsersFile::load(&config.authelia.user_database)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
if name == config.oauth.admin_group && !users.contains(&session_user.username) {
|
||||
logout = true;
|
||||
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());
|
||||
}
|
||||
@@ -155,20 +163,27 @@ pub async fn update(
|
||||
|
||||
pub async fn delete(
|
||||
_: auth::User,
|
||||
extract::Path(name): extract::Path<String>,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
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 group = models::groups::Group::select_by_name(&pg_pool, &name)
|
||||
let mut users = authelia::UsersFile::load(&config.authelia.user_database)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
Group::delete_by_name(&pg_pool, &group.name)
|
||||
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))?;
|
||||
|
||||
|
Reference in New Issue
Block a user