Refactor users management (#6)

* Use asynchronous interface to set authorized clients list
This commit is contained in:
Pierre HUBERT 2022-11-26 14:51:08 +01:00
parent b68304c976
commit f5ac7bf278
6 changed files with 90 additions and 22 deletions

View File

@ -1,7 +1,7 @@
use actix::{Actor, Context, Handler, Message, MessageResult}; use actix::{Actor, Context, Handler, Message, MessageResult};
use std::net::IpAddr; use std::net::IpAddr;
use crate::data::user::{FactorID, TwoFactor, User, UserID}; use crate::data::user::{FactorID, GrantedClients, TwoFactor, User, UserID};
use crate::utils::err::Res; use crate::utils::err::Res;
/// User storage interface /// User storage interface
@ -16,6 +16,7 @@ pub trait UsersBackend {
fn save_new_successful_2fa_authentication(&mut self, id: &UserID, ip: IpAddr) -> bool; fn save_new_successful_2fa_authentication(&mut self, id: &UserID, ip: IpAddr) -> bool;
fn clear_2fa_login_history(&mut self, id: &UserID) -> bool; fn clear_2fa_login_history(&mut self, id: &UserID) -> bool;
fn delete_account(&mut self, id: &UserID) -> bool; fn delete_account(&mut self, id: &UserID) -> bool;
fn set_granted_2fa_clients(&mut self, id: &UserID, clients: GrantedClients) -> bool;
// FIXME : remove this // FIXME : remove this
fn update_or_insert_user(&mut self, user: User) -> Res; fn update_or_insert_user(&mut self, user: User) -> Res;
@ -85,6 +86,10 @@ pub struct AddSuccessful2FALogin(pub UserID, pub IpAddr);
#[rtype(result = "bool")] #[rtype(result = "bool")]
pub struct Clear2FALoginHistory(pub UserID); pub struct Clear2FALoginHistory(pub UserID);
#[derive(Message)]
#[rtype(result = "bool")]
pub struct SetGrantedClients(pub UserID, pub GrantedClients);
#[derive(Message)] #[derive(Message)]
#[rtype(result = "bool")] #[rtype(result = "bool")]
pub struct UpdateUserRequest(pub User); pub struct UpdateUserRequest(pub User);
@ -174,6 +179,13 @@ impl Handler<Clear2FALoginHistory> for UsersActor {
} }
} }
impl Handler<SetGrantedClients> for UsersActor {
type Result = <SetGrantedClients as actix::Message>::Result;
fn handle(&mut self, msg: SetGrantedClients, _ctx: &mut Self::Context) -> Self::Result {
self.manager.set_granted_2fa_clients(&msg.0, msg.1)
}
}
impl Handler<GetUserRequest> for UsersActor { impl Handler<GetUserRequest> for UsersActor {
type Result = MessageResult<GetUserRequest>; type Result = MessageResult<GetUserRequest>;

View File

@ -11,7 +11,7 @@ use crate::controllers::settings_controller::BaseSettingsPage;
use crate::data::action_logger::{Action, ActionLogger}; use crate::data::action_logger::{Action, ActionLogger};
use crate::data::client::{Client, ClientID, ClientManager}; use crate::data::client::{Client, ClientID, ClientManager};
use crate::data::current_user::CurrentUser; use crate::data::current_user::CurrentUser;
use crate::data::user::{User, UserID}; use crate::data::user::{GrantedClients, User, UserID};
use crate::utils::string_utils::rand_str; use crate::utils::string_utils::rand_str;
#[derive(Template)] #[derive(Template)]
@ -98,23 +98,40 @@ pub async fn users_route(
user.two_factor user.two_factor
.retain(|f| factors_to_keep.contains(&f.id.0.as_str())); .retain(|f| factors_to_keep.contains(&f.id.0.as_str()));
user.authorized_clients = match update.0.grant_type.as_str() { let res = users
"all_clients" => None, .send(users_actor::UpdateUserRequest(user.clone()))
"custom_clients" => Some( .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 update
.0 .0
.granted_clients .granted_clients
.split(',') .split(',')
.map(|c| ClientID(c.to_string())) .map(|c| ClientID(c.to_string()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
), )
_ => Some(Vec::new()), }
_ => GrantedClients::NoClient,
}; };
let res = users if user.granted_clients() != granted_clients {
.send(users_actor::UpdateUserRequest(user.clone())) logger.log(Action::AdminSetNewGrantedClientsList(
&user,
&granted_clients,
));
users
.send(users_actor::SetGrantedClients(
user.uid.clone(),
granted_clients,
))
.await .await
.unwrap(); .unwrap();
}
// Clear user 2FA history if requested // Clear user 2FA history if requested
if update.0.clear_2fa_history.is_some() { if update.0.clear_2fa_history.is_some() {

View File

@ -12,13 +12,14 @@ use crate::actors::users_actor::UsersActor;
use crate::data::client::Client; use crate::data::client::Client;
use crate::data::remote_ip::RemoteIP; use crate::data::remote_ip::RemoteIP;
use crate::data::session_identity::SessionIdentity; use crate::data::session_identity::SessionIdentity;
use crate::data::user::{FactorID, TwoFactor, User, UserID}; use crate::data::user::{FactorID, GrantedClients, TwoFactor, User, UserID};
pub enum Action<'a> { pub enum Action<'a> {
AdminCreateUser(&'a User), AdminCreateUser(&'a User),
AdminUpdateUser(&'a User), AdminUpdateUser(&'a User),
AdminDeleteUser(&'a User), AdminDeleteUser(&'a User),
AdminResetUserPassword(&'a User), AdminResetUserPassword(&'a User),
AdminSetNewGrantedClientsList(&'a User, &'a GrantedClients),
AdminClear2FAHistory(&'a User), AdminClear2FAHistory(&'a User),
LoginWebauthnAttempt { success: bool, user_id: UserID }, LoginWebauthnAttempt { success: bool, user_id: UserID },
Signout, Signout,
@ -57,6 +58,11 @@ impl<'a> Action<'a> {
Action::AdminClear2FAHistory(user) => { Action::AdminClear2FAHistory(user) => {
format!("cleared 2FA history of {}", user.quick_identity()) format!("cleared 2FA history of {}", user.quick_identity())
} }
Action::AdminSetNewGrantedClientsList(user, clients) => format!(
"set new granted clients list ({:?}) for user ({})",
clients,
user.quick_identity()
),
Action::LoginWebauthnAttempt { success, user_id } => match success { Action::LoginWebauthnAttempt { success, user_id } => match success {
true => format!( true => format!(
"successfully performed webauthn attempt for user {:?}", "successfully performed webauthn attempt for user {:?}",

View File

@ -11,6 +11,23 @@ use crate::utils::time::{fmt_time, time};
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct UserID(pub String); pub struct UserID(pub String);
#[derive(Eq, PartialEq, Clone, Debug)]
pub enum GrantedClients {
AllClients,
SomeClients(Vec<ClientID>),
NoClient,
}
impl GrantedClients {
pub fn to_user(self) -> Option<Vec<ClientID>> {
match self {
GrantedClients::AllClients => None,
GrantedClients::SomeClients(users) => Some(users),
GrantedClients::NoClient => Some(vec![]),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FactorID(pub String); pub struct FactorID(pub String);
@ -124,10 +141,19 @@ impl User {
) )
} }
pub fn granted_clients(&self) -> GrantedClients {
match self.authorized_clients.as_deref() {
None => GrantedClients::AllClients,
Some(&[]) => GrantedClients::NoClient,
Some(clients) => GrantedClients::SomeClients(clients.to_vec()),
}
}
pub fn can_access_app(&self, id: &ClientID) -> bool { pub fn can_access_app(&self, id: &ClientID) -> bool {
match &self.authorized_clients { match self.granted_clients() {
None => true, GrantedClients::AllClients => true,
Some(c) => c.contains(id), GrantedClients::SomeClients(c) => c.contains(id),
GrantedClients::NoClient => false,
} }
} }

View File

@ -1,6 +1,6 @@
use crate::actors::users_actor::UsersBackend; use crate::actors::users_actor::UsersBackend;
use crate::data::entity_manager::EntityManager; use crate::data::entity_manager::EntityManager;
use crate::data::user::{FactorID, TwoFactor, User, UserID}; use crate::data::user::{FactorID, GrantedClients, TwoFactor, User, UserID};
use crate::utils::err::Res; use crate::utils::err::Res;
use crate::utils::time::time; use crate::utils::time::time;
use std::net::IpAddr; use std::net::IpAddr;
@ -138,6 +138,13 @@ impl UsersBackend for EntityManager<User> {
} }
} }
fn set_granted_2fa_clients(&mut self, id: &UserID, clients: GrantedClients) -> bool {
self.update_user(id, |mut user| {
user.authorized_clients = clients.to_user();
user
})
}
fn update_or_insert_user(&mut self, user: User) -> Res { fn update_or_insert_user(&mut self, user: User) -> Res {
self.update_or_push(user) self.update_or_push(user)
} }

View File

@ -126,14 +126,14 @@
<div class="form-check"> <div class="form-check">
<label class="form-check-label"> <label class="form-check-label">
<input type="radio" class="form-check-input" name="grant_type" <input type="radio" class="form-check-input" name="grant_type"
value="all_clients" {% if u.authorized_clients== None %} checked="" {% endif %}> value="all_clients" {% if u.granted_clients() == GrantedClients::AllClients %} checked="" {% endif %}>
Grant all clients Grant all clients
</label> </label>
</div> </div>
<div class="form-check"> <div class="form-check">
<label class="form-check-label"> <label class="form-check-label">
<input type="radio" class="form-check-input" name="grant_type" <input type="radio" class="form-check-input" name="grant_type"
value="custom_clients" {% if u.authorized_clients !=None %} checked="checked" {% endif %}> value="custom_clients" {% if u.granted_clients() != GrantedClients::AllClients %} checked="checked" {% endif %}>
Manually specify allowed clients Manually specify allowed clients
</label> </label>
</div> </div>