BasicOIDC/src/controllers/admin_controller.rs
Pierre Hubert 4f7c56a4b8
All checks were successful
continuous-integration/drone/push Build is passing
Loads clients list only once (#106)
Currently, the list of client is loaded separately for each Actix HTTP handler threads.

In prevision of future improvements, it is worthwhile to load this list only once.

Reviewed-on: #106
2023-04-17 16:49:19 +00:00

301 lines
8.9 KiB
Rust

use std::ops::Deref;
use std::sync::Arc;
use actix::Addr;
use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use crate::actors::users_actor;
use crate::actors::users_actor::UsersActor;
use crate::constants::TEMPORARY_PASSWORDS_LEN;
use crate::controllers::settings_controller::BaseSettingsPage;
use crate::data::action_logger::{Action, ActionLogger};
use crate::data::client::{Client, ClientID, ClientManager};
use crate::data::current_user::CurrentUser;
use crate::data::user::{GeneralSettings, GrantedClients, User, UserID};
use crate::utils::string_utils::rand_str;
#[derive(Template)]
#[template(path = "settings/clients_list.html")]
struct ClientsListTemplate {
_p: BaseSettingsPage,
clients: Vec<Client>,
}
#[derive(Template)]
#[template(path = "settings/users_list.html")]
struct UsersListTemplate {
_p: BaseSettingsPage,
users: Vec<User>,
}
#[derive(Template)]
#[template(path = "settings/edit_user.html")]
struct EditUserTemplate {
_p: BaseSettingsPage,
u: User,
clients: Vec<Client>,
}
pub async fn clients_route(
user: CurrentUser,
clients: web::Data<Arc<ClientManager>>,
) -> impl Responder {
HttpResponse::Ok().body(
ClientsListTemplate {
_p: BaseSettingsPage::get("Clients list", &user, None, None),
clients: clients.cloned(),
}
.render()
.unwrap(),
)
}
#[derive(serde::Deserialize, Debug)]
pub struct UpdateUserQuery {
uid: UserID,
username: String,
first_name: String,
last_name: String,
email: String,
gen_new_password: Option<String>,
enabled: Option<String>,
two_factor_exemption_after_successful_login: Option<String>,
admin: Option<String>,
grant_type: String,
granted_clients: String,
two_factor: String,
clear_2fa_history: Option<String>,
}
pub async fn users_route(
admin: CurrentUser,
users: web::Data<Addr<UsersActor>>,
update_query: Option<web::Form<UpdateUserQuery>>,
logger: ActionLogger,
) -> impl Responder {
let mut danger = None;
let mut success = None;
if let Some(update) = update_query {
let edited_user: Option<User> = users
.send(users_actor::GetUserRequest(update.uid.clone()))
.await
.unwrap()
.0;
let is_creating = edited_user.is_none();
let settings = GeneralSettings {
uid: update.0.uid,
username: update.0.username,
first_name: update.0.first_name,
last_name: update.0.last_name,
email: update.0.email,
enabled: update.0.enabled.is_some(),
two_factor_exemption_after_successful_login: update
.0
.two_factor_exemption_after_successful_login
.is_some(),
is_admin: update.0.admin.is_some(),
};
let mut edited_user = edited_user.unwrap_or_default();
edited_user.update_general_settings(settings.clone());
let res = match is_creating {
true => {
match users
.send(users_actor::CreateAccount(settings))
.await
.unwrap()
{
Some(id) => {
edited_user.uid = id;
true
}
None => false,
}
}
false => users
.send(users_actor::UpdateUserSettings(settings))
.await
.unwrap(),
};
// Update the list of factors
let factors_to_keep = update.0.two_factor.split(';').collect::<Vec<_>>();
for factor in &edited_user.two_factor {
if !factors_to_keep.contains(&factor.id.0.as_str()) {
logger.log(Action::AdminRemoveUserFactor(&edited_user, factor));
users
.send(users_actor::Remove2FAFactor(
edited_user.uid.clone(),
factor.id.clone(),
))
.await
.unwrap();
}
}
// Update list of granted clients
let granted_clients = match update.0.grant_type.as_str() {
"all_clients" => GrantedClients::AllClients,
"custom_clients" if !update.0.granted_clients.is_empty() => {
GrantedClients::SomeClients(
update
.0
.granted_clients
.split(',')
.map(|c| ClientID(c.to_string()))
.collect::<Vec<_>>(),
)
}
_ => GrantedClients::NoClient,
};
if edited_user.granted_clients() != granted_clients {
logger.log(Action::AdminSetNewGrantedClientsList(
&edited_user,
&granted_clients,
));
users
.send(users_actor::SetGrantedClients(
edited_user.uid.clone(),
granted_clients,
))
.await
.unwrap();
}
// Clear user 2FA history if requested
if update.0.clear_2fa_history.is_some() {
logger.log(Action::AdminClear2FAHistory(&edited_user));
users
.send(users_actor::Clear2FALoginHistory(edited_user.uid.clone()))
.await
.unwrap();
}
// If the admin requested it, reset user password
let new_password = match update.0.gen_new_password.is_some() {
false => None,
true => {
logger.log(Action::AdminResetUserPassword(&edited_user));
let temp_pass = rand_str(TEMPORARY_PASSWORDS_LEN);
users
.send(users_actor::ChangePasswordRequest {
user_id: edited_user.uid.clone(),
new_password: temp_pass.clone(),
temporary: true,
})
.await
.unwrap();
Some(temp_pass)
}
};
if !res {
danger = Some(
match is_creating {
true => "Failed to create user!",
false => "Failed to update user!",
}
.to_string(),
)
} else {
success = Some(match is_creating {
true => {
logger.log(Action::AdminCreateUser(&edited_user));
format!("User {} was successfully created!", edited_user.full_name())
}
false => {
logger.log(Action::AdminUpdateUser(&edited_user));
format!("User {} was successfully updated!", edited_user.full_name())
}
});
if let Some(pass) = new_password {
danger = Some(format!(
"{}'s temporary password is {}",
edited_user.full_name(),
pass
));
}
}
}
let users = users.send(users_actor::GetAllUsers).await.unwrap();
HttpResponse::Ok().body(
UsersListTemplate {
_p: BaseSettingsPage::get("Users list", &admin, danger, success),
users,
}
.render()
.unwrap(),
)
}
pub async fn create_user(
admin: CurrentUser,
clients: web::Data<Arc<ClientManager>>,
) -> impl Responder {
let user = User {
authorized_clients: Some(
clients
.get_default_clients()
.iter()
.map(|u| u.id.clone())
.collect(),
),
..Default::default()
};
HttpResponse::Ok().body(
EditUserTemplate {
_p: BaseSettingsPage::get("Create a new user", admin.deref(), None, None),
u: user,
clients: clients.cloned(),
}
.render()
.unwrap(),
)
}
#[derive(serde::Deserialize)]
pub struct EditUserQuery {
id: UserID,
}
pub async fn edit_user(
admin: CurrentUser,
clients: web::Data<Arc<ClientManager>>,
users: web::Data<Addr<UsersActor>>,
query: web::Query<EditUserQuery>,
) -> impl Responder {
let edited_account = users
.send(users_actor::GetUserRequest(query.0.id))
.await
.unwrap()
.0;
HttpResponse::Ok().body(
EditUserTemplate {
_p: BaseSettingsPage::get(
"Edit user account",
admin.deref(),
match edited_account.is_none() {
true => Some("Could not find requested user!".to_string()),
false => None,
},
None,
),
u: edited_account.unwrap_or_default(),
clients: clients.cloned(),
}
.render()
.unwrap(),
)
}