Can block local login for an account
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
96ffc669d7
commit
f64f01a958
@ -19,6 +19,11 @@ pub trait UsersSyncBackend {
|
|||||||
fn save_new_successful_2fa_authentication(&mut self, id: &UserID, ip: IpAddr) -> Res;
|
fn save_new_successful_2fa_authentication(&mut self, id: &UserID, ip: IpAddr) -> Res;
|
||||||
fn clear_2fa_login_history(&mut self, id: &UserID) -> Res;
|
fn clear_2fa_login_history(&mut self, id: &UserID) -> Res;
|
||||||
fn delete_account(&mut self, id: &UserID) -> Res;
|
fn delete_account(&mut self, id: &UserID) -> Res;
|
||||||
|
fn set_authorized_authentication_sources(
|
||||||
|
&mut self,
|
||||||
|
id: &UserID,
|
||||||
|
sources: AuthorizedAuthenticationSources,
|
||||||
|
) -> Res;
|
||||||
fn set_granted_2fa_clients(&mut self, id: &UserID, clients: GrantedClients) -> Res;
|
fn set_granted_2fa_clients(&mut self, id: &UserID, clients: GrantedClients) -> Res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,12 +33,13 @@ pub enum LoginResult {
|
|||||||
AccountNotFound,
|
AccountNotFound,
|
||||||
InvalidPassword,
|
InvalidPassword,
|
||||||
AccountDisabled,
|
AccountDisabled,
|
||||||
|
LocalAuthForbidden,
|
||||||
Success(Box<User>),
|
Success(Box<User>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(LoginResult)]
|
#[rtype(LoginResult)]
|
||||||
pub struct LoginRequest {
|
pub struct LocalLoginRequest {
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
@ -88,6 +94,15 @@ pub struct AddSuccessful2FALogin(pub UserID, pub IpAddr);
|
|||||||
#[rtype(result = "bool")]
|
#[rtype(result = "bool")]
|
||||||
pub struct Clear2FALoginHistory(pub UserID);
|
pub struct Clear2FALoginHistory(pub UserID);
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||||
|
pub struct AuthorizedAuthenticationSources {
|
||||||
|
pub local: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "bool")]
|
||||||
|
pub struct SetAuthorizedAuthenticationSources(pub UserID, pub AuthorizedAuthenticationSources);
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "bool")]
|
#[rtype(result = "bool")]
|
||||||
pub struct SetGrantedClients(pub UserID, pub GrantedClients);
|
pub struct SetGrantedClients(pub UserID, pub GrantedClients);
|
||||||
@ -119,10 +134,10 @@ impl Actor for UsersActor {
|
|||||||
type Context = Context<Self>;
|
type Context = Context<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler<LoginRequest> for UsersActor {
|
impl Handler<LocalLoginRequest> for UsersActor {
|
||||||
type Result = MessageResult<LoginRequest>;
|
type Result = MessageResult<LocalLoginRequest>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LoginRequest, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LocalLoginRequest, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
match self.manager.find_by_username_or_email(&msg.login) {
|
match self.manager.find_by_username_or_email(&msg.login) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to find user! {}", e);
|
log::error!("Failed to find user! {}", e);
|
||||||
@ -142,6 +157,10 @@ impl Handler<LoginRequest> for UsersActor {
|
|||||||
return MessageResult(LoginResult::AccountDisabled);
|
return MessageResult(LoginResult::AccountDisabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !user.allow_local_login {
|
||||||
|
return MessageResult(LoginResult::LocalAuthForbidden);
|
||||||
|
}
|
||||||
|
|
||||||
MessageResult(LoginResult::Success(Box::new(user)))
|
MessageResult(LoginResult::Success(Box::new(user)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,6 +260,29 @@ impl Handler<Clear2FALoginHistory> for UsersActor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Handler<SetAuthorizedAuthenticationSources> for UsersActor {
|
||||||
|
type Result = <SetAuthorizedAuthenticationSources as actix::Message>::Result;
|
||||||
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
msg: SetAuthorizedAuthenticationSources,
|
||||||
|
_ctx: &mut Self::Context,
|
||||||
|
) -> Self::Result {
|
||||||
|
match self
|
||||||
|
.manager
|
||||||
|
.set_authorized_authentication_sources(&msg.0, msg.1)
|
||||||
|
{
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to set authorized authentication sources for user! {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Handler<SetGrantedClients> for UsersActor {
|
impl Handler<SetGrantedClients> for UsersActor {
|
||||||
type Result = <SetGrantedClients as actix::Message>::Result;
|
type Result = <SetGrantedClients as actix::Message>::Result;
|
||||||
fn handle(&mut self, msg: SetGrantedClients, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: SetGrantedClients, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
@ -6,7 +6,7 @@ use actix_web::{web, HttpResponse, Responder};
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
|
||||||
use crate::actors::users_actor;
|
use crate::actors::users_actor;
|
||||||
use crate::actors::users_actor::UsersActor;
|
use crate::actors::users_actor::{AuthorizedAuthenticationSources, UsersActor};
|
||||||
use crate::constants::TEMPORARY_PASSWORDS_LEN;
|
use crate::constants::TEMPORARY_PASSWORDS_LEN;
|
||||||
use crate::controllers::settings_controller::BaseSettingsPage;
|
use crate::controllers::settings_controller::BaseSettingsPage;
|
||||||
use crate::data::action_logger::{Action, ActionLogger};
|
use crate::data::action_logger::{Action, ActionLogger};
|
||||||
@ -84,6 +84,7 @@ pub struct UpdateUserQuery {
|
|||||||
enabled: Option<String>,
|
enabled: Option<String>,
|
||||||
two_factor_exemption_after_successful_login: Option<String>,
|
two_factor_exemption_after_successful_login: Option<String>,
|
||||||
admin: Option<String>,
|
admin: Option<String>,
|
||||||
|
allow_local_login: Option<String>,
|
||||||
grant_type: String,
|
grant_type: String,
|
||||||
granted_clients: String,
|
granted_clients: String,
|
||||||
two_factor: String,
|
two_factor: String,
|
||||||
@ -158,6 +159,25 @@ pub async fn users_route(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the list of authorized authentication sources
|
||||||
|
let auth_sources = AuthorizedAuthenticationSources {
|
||||||
|
local: update.0.allow_local_login.is_some(),
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
// Update list of granted clients
|
||||||
let granted_clients = match update.0.grant_type.as_str() {
|
let granted_clients = match update.0.grant_type.as_str() {
|
||||||
"all_clients" => GrantedClients::AllClients,
|
"all_clients" => GrantedClients::AllClients,
|
||||||
|
@ -132,7 +132,7 @@ pub async fn login_route(
|
|||||||
else if let Some(req) = &req {
|
else if let Some(req) = &req {
|
||||||
login = req.login.clone();
|
login = req.login.clone();
|
||||||
let response: LoginResult = users
|
let response: LoginResult = users
|
||||||
.send(users_actor::LoginRequest {
|
.send(users_actor::LocalLoginRequest {
|
||||||
login: login.clone(),
|
login: login.clone(),
|
||||||
password: req.password.clone(),
|
password: req.password.clone(),
|
||||||
})
|
})
|
||||||
@ -163,6 +163,12 @@ pub async fn login_route(
|
|||||||
danger = Some("Your account is disabled!".to_string());
|
danger = Some("Your account is disabled!".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoginResult::LocalAuthForbidden => {
|
||||||
|
log::warn!("Failed login for username {} : attempted to use local auth, but it is forbidden", &login);
|
||||||
|
logger.log(Action::TryLocalLoginFromUnauthorizedAccount(&login));
|
||||||
|
danger = Some("You cannot login from local auth with your account!".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
LoginResult::Error => {
|
LoginResult::Error => {
|
||||||
danger = Some("An unkown error occured while trying to sign you in!".to_string());
|
danger = Some("An unkown error occured while trying to sign you in!".to_string());
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use actix_web::dev::Payload;
|
|||||||
use actix_web::{web, Error, FromRequest, HttpRequest};
|
use actix_web::{web, Error, FromRequest, HttpRequest};
|
||||||
|
|
||||||
use crate::actors::users_actor;
|
use crate::actors::users_actor;
|
||||||
use crate::actors::users_actor::UsersActor;
|
use crate::actors::users_actor::{AuthorizedAuthenticationSources, 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;
|
||||||
@ -20,6 +20,7 @@ pub enum Action<'a> {
|
|||||||
AdminDeleteUser(&'a User),
|
AdminDeleteUser(&'a User),
|
||||||
AdminResetUserPassword(&'a User),
|
AdminResetUserPassword(&'a User),
|
||||||
AdminRemoveUserFactor(&'a User, &'a TwoFactor),
|
AdminRemoveUserFactor(&'a User, &'a TwoFactor),
|
||||||
|
AdminSetAuthorizedAuthenticationSources(&'a User, &'a AuthorizedAuthenticationSources),
|
||||||
AdminSetNewGrantedClientsList(&'a User, &'a GrantedClients),
|
AdminSetNewGrantedClientsList(&'a User, &'a GrantedClients),
|
||||||
AdminClear2FAHistory(&'a User),
|
AdminClear2FAHistory(&'a User),
|
||||||
LoginWebauthnAttempt { success: bool, user_id: UserID },
|
LoginWebauthnAttempt { success: bool, user_id: UserID },
|
||||||
@ -28,6 +29,7 @@ pub enum Action<'a> {
|
|||||||
UserSuccessfullyAuthenticated(&'a User),
|
UserSuccessfullyAuthenticated(&'a User),
|
||||||
UserNeedNewPasswordOnLogin(&'a User),
|
UserNeedNewPasswordOnLogin(&'a User),
|
||||||
TryLoginWithDisabledAccount(&'a str),
|
TryLoginWithDisabledAccount(&'a str),
|
||||||
|
TryLocalLoginFromUnauthorizedAccount(&'a str),
|
||||||
FailedLoginWithBadCredentials(&'a str),
|
FailedLoginWithBadCredentials(&'a str),
|
||||||
UserChangedPasswordOnLogin(&'a UserID),
|
UserChangedPasswordOnLogin(&'a UserID),
|
||||||
OTPLoginAttempt { user: &'a User, success: bool },
|
OTPLoginAttempt { user: &'a User, success: bool },
|
||||||
@ -64,6 +66,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::AdminSetAuthorizedAuthenticationSources(user, sources) => format!(
|
||||||
|
"update authorized authentication sources ({:?}) for user ({})",
|
||||||
|
sources,
|
||||||
|
user.quick_identity()
|
||||||
|
),
|
||||||
Action::AdminSetNewGrantedClientsList(user, clients) => format!(
|
Action::AdminSetNewGrantedClientsList(user, clients) => format!(
|
||||||
"set new granted clients list ({:?}) for user ({})",
|
"set new granted clients list ({:?}) for user ({})",
|
||||||
clients,
|
clients,
|
||||||
@ -90,6 +97,9 @@ impl<'a> Action<'a> {
|
|||||||
Action::TryLoginWithDisabledAccount(login) => {
|
Action::TryLoginWithDisabledAccount(login) => {
|
||||||
format!("successfully authenticated as {login}, but this is a DISABLED ACCOUNT")
|
format!("successfully authenticated as {login}, but this is a DISABLED ACCOUNT")
|
||||||
}
|
}
|
||||||
|
Action::TryLocalLoginFromUnauthorizedAccount(login) => {
|
||||||
|
format!("successfully locally authenticated as {login}, but this is a FORBIDDEN for this account!")
|
||||||
|
}
|
||||||
Action::FailedLoginWithBadCredentials(login) => {
|
Action::FailedLoginWithBadCredentials(login) => {
|
||||||
format!("attempted to authenticate as {login} but with a WRONG PASSWORD")
|
format!("attempted to authenticate as {login} but with a WRONG PASSWORD")
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::actors::users_actor::AuthorizedAuthenticationSources;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
@ -114,6 +115,10 @@ impl Successful2FALogin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub uid: UserID,
|
pub uid: UserID,
|
||||||
@ -142,6 +147,10 @@ pub struct User {
|
|||||||
/// None = all services
|
/// None = all services
|
||||||
/// Some([]) = no service
|
/// Some([]) = no service
|
||||||
pub authorized_clients: Option<Vec<ClientID>>,
|
pub authorized_clients: Option<Vec<ClientID>>,
|
||||||
|
|
||||||
|
/// Authorize connection through local login
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub allow_local_login: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
@ -162,6 +171,13 @@ impl User {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the list of sources from which a user can authenticate from
|
||||||
|
pub fn authorized_authentication_sources(&self) -> AuthorizedAuthenticationSources {
|
||||||
|
AuthorizedAuthenticationSources {
|
||||||
|
local: self.allow_local_login,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn granted_clients(&self) -> GrantedClients {
|
pub fn granted_clients(&self) -> GrantedClients {
|
||||||
match self.authorized_clients.as_deref() {
|
match self.authorized_clients.as_deref() {
|
||||||
None => GrantedClients::AllClients,
|
None => GrantedClients::AllClients,
|
||||||
@ -296,6 +312,7 @@ impl Default for User {
|
|||||||
two_factor_exemption_after_successful_login: false,
|
two_factor_exemption_after_successful_login: false,
|
||||||
last_successful_2fa: Default::default(),
|
last_successful_2fa: Default::default(),
|
||||||
authorized_clients: Some(Vec::new()),
|
authorized_clients: Some(Vec::new()),
|
||||||
|
allow_local_login: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use crate::actors::users_actor::UsersSyncBackend;
|
use crate::actors::users_actor::{AuthorizedAuthenticationSources, UsersSyncBackend};
|
||||||
use crate::data::entity_manager::EntityManager;
|
use crate::data::entity_manager::EntityManager;
|
||||||
use crate::data::user::{FactorID, GeneralSettings, GrantedClients, TwoFactor, User, UserID};
|
use crate::data::user::{FactorID, GeneralSettings, GrantedClients, TwoFactor, User, UserID};
|
||||||
use crate::utils::err::{new_error, Res};
|
use crate::utils::err::{new_error, Res};
|
||||||
@ -143,6 +143,17 @@ impl UsersSyncBackend for EntityManager<User> {
|
|||||||
self.remove(&user)
|
self.remove(&user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_authorized_authentication_sources(
|
||||||
|
&mut self,
|
||||||
|
id: &UserID,
|
||||||
|
sources: AuthorizedAuthenticationSources,
|
||||||
|
) -> Res {
|
||||||
|
self.update_user(id, |mut user| {
|
||||||
|
user.allow_local_login = sources.local;
|
||||||
|
user
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn set_granted_2fa_clients(&mut self, id: &UserID, clients: GrantedClients) -> Res {
|
fn set_granted_2fa_clients(&mut self, id: &UserID, clients: GrantedClients) -> Res {
|
||||||
self.update_user(id, |mut user| {
|
self.update_user(id, |mut user| {
|
||||||
user.authorized_clients = clients.to_user();
|
user.authorized_clients = clients.to_user();
|
||||||
|
@ -113,27 +113,44 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{% for e in u.get_formatted_2fa_successful_logins() %}
|
{% for e in u.get_formatted_2fa_successful_logins() %}
|
||||||
{% if e.can_bypass_2fa %}<li style="font-weight: bold;">{{ e.ip }} - {{ e.fmt_time() }} - BYPASS 2FA</li>
|
{% if e.can_bypass_2fa %}
|
||||||
{% else %}<li>{{ e.ip }} - {{ e.fmt_time() }}</li>{% endif %}
|
<li style="font-weight: bold;">{{ e.ip }} - {{ e.fmt_time() }} - BYPASS 2FA</li>
|
||||||
|
{% else %}
|
||||||
|
<li>{{ e.ip }} - {{ e.fmt_time() }}</li>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Authorized authentication sources -->
|
||||||
|
<fieldset class="form-group">
|
||||||
|
<legend class="mt-4">Authorized authentication sources</legend>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="allow_local_login" id="allow_local_login"
|
||||||
|
{% if u.allow_local_login %} checked="" {% endif %}>
|
||||||
|
<label class="form-check-label" for="allow_local_login">
|
||||||
|
Allow local login
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<!-- Granted clients -->
|
<!-- Granted clients -->
|
||||||
<fieldset class="form-group">
|
<fieldset class="form-group">
|
||||||
<legend class="mt-4">Granted clients</legend>
|
<legend class="mt-4">Granted clients</legend>
|
||||||
<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.granted_clients() == GrantedClients::AllClients %} 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 matches!(self.u.granted_clients(), GrantedClients::SomeClients(_)) %} checked="checked" {% endif %}>
|
value="custom_clients" {% if matches!(self.u.granted_clients(), GrantedClients::SomeClients(_))
|
||||||
|
%} checked="checked" {% endif %}>
|
||||||
Manually specify allowed clients
|
Manually specify allowed clients
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -155,7 +172,8 @@
|
|||||||
<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="no_client" {% if u.granted_clients() == GrantedClients::NoClient %} checked="checked" {% endif %}>
|
value="no_client" {% if u.granted_clients()== GrantedClients::NoClient %} checked="checked" {%
|
||||||
|
endif %}>
|
||||||
Do not grant any client
|
Do not grant any client
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -231,6 +249,7 @@
|
|||||||
form.submit();
|
form.submit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
Loading…
Reference in New Issue
Block a user