Refactor users management (#6)
* Use asynchronous interface to set authorized clients list
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
			
		||||
use actix::{Actor, Context, Handler, Message, MessageResult};
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
/// User storage interface
 | 
			
		||||
@@ -16,6 +16,7 @@ pub trait UsersBackend {
 | 
			
		||||
    fn save_new_successful_2fa_authentication(&mut self, id: &UserID, ip: IpAddr) -> bool;
 | 
			
		||||
    fn clear_2fa_login_history(&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
 | 
			
		||||
    fn update_or_insert_user(&mut self, user: User) -> Res;
 | 
			
		||||
@@ -85,6 +86,10 @@ pub struct AddSuccessful2FALogin(pub UserID, pub IpAddr);
 | 
			
		||||
#[rtype(result = "bool")]
 | 
			
		||||
pub struct Clear2FALoginHistory(pub UserID);
 | 
			
		||||
 | 
			
		||||
#[derive(Message)]
 | 
			
		||||
#[rtype(result = "bool")]
 | 
			
		||||
pub struct SetGrantedClients(pub UserID, pub GrantedClients);
 | 
			
		||||
 | 
			
		||||
#[derive(Message)]
 | 
			
		||||
#[rtype(result = "bool")]
 | 
			
		||||
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 {
 | 
			
		||||
    type Result = MessageResult<GetUserRequest>;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ 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::{User, UserID};
 | 
			
		||||
use crate::data::user::{GrantedClients, User, UserID};
 | 
			
		||||
use crate::utils::string_utils::rand_str;
 | 
			
		||||
 | 
			
		||||
#[derive(Template)]
 | 
			
		||||
@@ -98,24 +98,41 @@ pub async fn users_route(
 | 
			
		||||
        user.two_factor
 | 
			
		||||
            .retain(|f| factors_to_keep.contains(&f.id.0.as_str()));
 | 
			
		||||
 | 
			
		||||
        user.authorized_clients = match update.0.grant_type.as_str() {
 | 
			
		||||
            "all_clients" => None,
 | 
			
		||||
            "custom_clients" => Some(
 | 
			
		||||
                update
 | 
			
		||||
                    .0
 | 
			
		||||
                    .granted_clients
 | 
			
		||||
                    .split(',')
 | 
			
		||||
                    .map(|c| ClientID(c.to_string()))
 | 
			
		||||
                    .collect::<Vec<_>>(),
 | 
			
		||||
            ),
 | 
			
		||||
            _ => Some(Vec::new()),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let res = users
 | 
			
		||||
            .send(users_actor::UpdateUserRequest(user.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 user.granted_clients() != granted_clients {
 | 
			
		||||
            logger.log(Action::AdminSetNewGrantedClientsList(
 | 
			
		||||
                &user,
 | 
			
		||||
                &granted_clients,
 | 
			
		||||
            ));
 | 
			
		||||
            users
 | 
			
		||||
                .send(users_actor::SetGrantedClients(
 | 
			
		||||
                    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(&user));
 | 
			
		||||
 
 | 
			
		||||
@@ -12,13 +12,14 @@ use crate::actors::users_actor::UsersActor;
 | 
			
		||||
use crate::data::client::Client;
 | 
			
		||||
use crate::data::remote_ip::RemoteIP;
 | 
			
		||||
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> {
 | 
			
		||||
    AdminCreateUser(&'a User),
 | 
			
		||||
    AdminUpdateUser(&'a User),
 | 
			
		||||
    AdminDeleteUser(&'a User),
 | 
			
		||||
    AdminResetUserPassword(&'a User),
 | 
			
		||||
    AdminSetNewGrantedClientsList(&'a User, &'a GrantedClients),
 | 
			
		||||
    AdminClear2FAHistory(&'a User),
 | 
			
		||||
    LoginWebauthnAttempt { success: bool, user_id: UserID },
 | 
			
		||||
    Signout,
 | 
			
		||||
@@ -57,6 +58,11 @@ impl<'a> Action<'a> {
 | 
			
		||||
            Action::AdminClear2FAHistory(user) => {
 | 
			
		||||
                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 {
 | 
			
		||||
                true => format!(
 | 
			
		||||
                    "successfully performed webauthn attempt for user {:?}",
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,23 @@ use crate::utils::time::{fmt_time, time};
 | 
			
		||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
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)]
 | 
			
		||||
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 {
 | 
			
		||||
        match &self.authorized_clients {
 | 
			
		||||
            None => true,
 | 
			
		||||
            Some(c) => c.contains(id),
 | 
			
		||||
        match self.granted_clients() {
 | 
			
		||||
            GrantedClients::AllClients => true,
 | 
			
		||||
            GrantedClients::SomeClients(c) => c.contains(id),
 | 
			
		||||
            GrantedClients::NoClient => false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use crate::actors::users_actor::UsersBackend;
 | 
			
		||||
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::time::time;
 | 
			
		||||
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 {
 | 
			
		||||
        self.update_or_push(user)
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user