All checks were successful
continuous-integration/drone/push Build is passing
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
301 lines
8.9 KiB
Rust
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(),
|
|
)
|
|
}
|