Refactor users management (#6)
* Use asynchronous interface to set authorized clients list
This commit is contained in:
parent
b68304c976
commit
f5ac7bf278
@ -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>;
|
||||||
|
|
||||||
|
@ -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,24 +98,41 @@ 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() {
|
|
||||||
"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
|
let res = users
|
||||||
.send(users_actor::UpdateUserRequest(user.clone()))
|
.send(users_actor::UpdateUserRequest(user.clone()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.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
|
// Clear user 2FA history if requested
|
||||||
if update.0.clear_2fa_history.is_some() {
|
if update.0.clear_2fa_history.is_some() {
|
||||||
logger.log(Action::AdminClear2FAHistory(&user));
|
logger.log(Action::AdminClear2FAHistory(&user));
|
||||||
|
@ -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 {:?}",
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user