All checks were successful
continuous-integration/drone/push Build is passing
370 lines
11 KiB
Rust
370 lines
11 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::{AuthorizedAuthenticationSources, UsersActor};
|
|
use crate::constants::TEMPORARY_PASSWORDS_LEN;
|
|
use crate::controllers::settings_controller::BaseSettingsPage;
|
|
use crate::data::action_logger::{Action, ActionLogger};
|
|
use crate::data::app_config::AppConfig;
|
|
use crate::data::client::{Client, ClientID, ClientManager};
|
|
use crate::data::critical_route::CriticalRoute;
|
|
use crate::data::current_user::CurrentUser;
|
|
use crate::data::provider::{Provider, ProviderID, ProvidersManager};
|
|
use crate::data::user::{GeneralSettings, GrantedClients, User, UserID};
|
|
use crate::utils::string_utils;
|
|
use crate::utils::string_utils::rand_str;
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "settings/clients_list.html")]
|
|
struct ClientsListTemplate<'a> {
|
|
p: BaseSettingsPage<'a>,
|
|
clients: Vec<Client>,
|
|
}
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "settings/providers_list.html")]
|
|
struct ProvidersListTemplate<'a> {
|
|
p: BaseSettingsPage<'a>,
|
|
providers: Vec<Provider>,
|
|
redirect_url: String,
|
|
}
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "settings/users_list.html")]
|
|
struct UsersListTemplate<'a> {
|
|
p: BaseSettingsPage<'a>,
|
|
users: Vec<User>,
|
|
}
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "settings/edit_user.html")]
|
|
struct EditUserTemplate<'a> {
|
|
p: BaseSettingsPage<'a>,
|
|
u: User,
|
|
clients: Vec<Client>,
|
|
providers: Vec<Provider>,
|
|
}
|
|
|
|
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(),
|
|
)
|
|
}
|
|
|
|
pub async fn providers_route(
|
|
user: CurrentUser,
|
|
providers: web::Data<Arc<ProvidersManager>>,
|
|
) -> impl Responder {
|
|
HttpResponse::Ok().body(
|
|
ProvidersListTemplate {
|
|
p: BaseSettingsPage::get("OpenID Providers list", &user, None, None),
|
|
providers: providers.cloned(),
|
|
redirect_url: AppConfig::get().oidc_provider_redirect_url(),
|
|
}
|
|
.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>,
|
|
allow_local_login: Option<String>,
|
|
authorized_sources: String,
|
|
grant_type: String,
|
|
granted_clients: String,
|
|
two_factor: String,
|
|
clear_2fa_history: Option<String>,
|
|
}
|
|
|
|
pub async fn users_route(
|
|
_critical: CriticalRoute,
|
|
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;
|
|
|
|
// Check update query for invalid input
|
|
if update_query
|
|
.as_ref()
|
|
.map(|l| string_utils::is_acceptable_login(&l.username))
|
|
== Some(false)
|
|
{
|
|
danger = Some("Invalid login provided, the modifications could not be saved!".to_string());
|
|
}
|
|
// Perform request (if any)
|
|
else 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 the list of authorized authentication sources
|
|
let auth_sources = AuthorizedAuthenticationSources {
|
|
local: update.0.allow_local_login.is_some(),
|
|
upstream: match update.0.authorized_sources.as_str() {
|
|
"" => vec![],
|
|
s => s.split(',').map(|s| ProviderID(s.to_string())).collect(),
|
|
},
|
|
};
|
|
|
|
if edited_user.authorized_authentication_sources() != auth_sources {
|
|
logger.log(Action::AdminSetAuthorizedAuthenticationSources(
|
|
&edited_user,
|
|
&auth_sources,
|
|
));
|
|
users
|
|
.send(users_actor::SetAuthorizedAuthenticationSources(
|
|
edited_user.uid.clone(),
|
|
auth_sources,
|
|
))
|
|
.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(
|
|
_critical: CriticalRoute,
|
|
admin: CurrentUser,
|
|
clients: web::Data<Arc<ClientManager>>,
|
|
providers: web::Data<Arc<ProvidersManager>>,
|
|
) -> 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(),
|
|
providers: providers.cloned(),
|
|
}
|
|
.render()
|
|
.unwrap(),
|
|
)
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct EditUserQuery {
|
|
id: UserID,
|
|
}
|
|
|
|
pub async fn edit_user(
|
|
_critical: CriticalRoute,
|
|
admin: CurrentUser,
|
|
clients: web::Data<Arc<ClientManager>>,
|
|
providers: web::Data<Arc<ProvidersManager>>,
|
|
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(),
|
|
providers: providers.cloned(),
|
|
}
|
|
.render()
|
|
.unwrap(),
|
|
)
|
|
}
|