BasicOIDC/src/data/action_logger.rs
Pierre Hubert 0fdc8b2e4b
All checks were successful
continuous-integration/drone/push Build is passing
Map provider given email with local account
2023-04-27 10:04:35 +02:00

270 lines
11 KiB
Rust

use std::future::Future;
use std::net::IpAddr;
use std::pin::Pin;
use actix::Addr;
use actix_identity::Identity;
use actix_web::dev::Payload;
use actix_web::{web, Error, FromRequest, HttpRequest};
use crate::actors::providers_states_actor::ProviderLoginState;
use crate::actors::users_actor;
use crate::actors::users_actor::{AuthorizedAuthenticationSources, UsersActor};
use crate::data::client::Client;
use crate::data::provider::{Provider, ProviderID};
use crate::data::remote_ip::RemoteIP;
use crate::data::session_identity::SessionIdentity;
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),
AdminRemoveUserFactor(&'a User, &'a TwoFactor),
AdminSetAuthorizedAuthenticationSources(&'a User, &'a AuthorizedAuthenticationSources),
AdminSetNewGrantedClientsList(&'a User, &'a GrantedClients),
AdminClear2FAHistory(&'a User),
LoginWebauthnAttempt {
success: bool,
user_id: UserID,
},
StartLoginAttemptWithOpenIDProvider {
provider_id: &'a ProviderID,
state: &'a str,
},
ProviderError {
message: &'a str,
},
ProviderCBInvalidState {
state: &'a str,
},
ProviderRateLimited,
ProviderFailedGetToken {
state: &'a ProviderLoginState,
code: &'a str,
},
ProviderFailedGetUserInfo {
provider: &'a Provider,
},
ProviderEmailNotValidated {
provider: &'a Provider,
},
ProviderMissingEmailInResponse {
provider: &'a Provider,
},
ProviderAccountNotFound {
provider: &'a Provider,
email: &'a str,
},
ProviderAccountDisabled {
provider: &'a Provider,
email: &'a str,
},
ProviderAccountNotAllowedToLoginWithProvider {
provider: &'a Provider,
email: &'a str,
},
ProviderLoginFailed {
provider: &'a Provider,
email: &'a str,
},
Signout,
UserNeed2FAOnLogin(&'a User),
UserSuccessfullyAuthenticated(&'a User),
UserNeedNewPasswordOnLogin(&'a User),
TryLoginWithDisabledAccount(&'a str),
TryLocalLoginFromUnauthorizedAccount(&'a str),
FailedLoginWithBadCredentials(&'a str),
UserChangedPasswordOnLogin(&'a UserID),
OTPLoginAttempt {
user: &'a User,
success: bool,
},
NewOpenIDSession {
client: &'a Client,
},
ChangedHisPassword,
ClearedHisLoginHistory,
AddNewFactor(&'a TwoFactor),
Removed2FAFactor {
factor_id: &'a FactorID,
},
}
impl<'a> Action<'a> {
pub fn as_string(&self) -> String {
match self {
Action::AdminDeleteUser(user) => {
format!("deleted account of {}", user.quick_identity())
}
Action::AdminCreateUser(user) => {
format!("created account of {}", user.quick_identity())
}
Action::AdminUpdateUser(user) => {
format!("updated account of {}", user.quick_identity())
}
Action::AdminResetUserPassword(user) => {
format!(
"set a temporary password for the account of {}",
user.quick_identity()
)
}
Action::AdminRemoveUserFactor(user, factor) => format!(
"removed 2 factor ({}) of user ({})",
factor.quick_description(),
user.quick_identity()
),
Action::AdminClear2FAHistory(user) => {
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!(
"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 {user_id:?}"),
false => format!("performed FAILED webauthn attempt for user {user_id:?}"),
},
Action::StartLoginAttemptWithOpenIDProvider { provider_id, state } => format!(
"started new authentication attempt through an OpenID provider (prov={} / state={state})", provider_id.0
),
Action::ProviderError { message } =>
format!("failed provider authentication with message '{message}'"),
Action::ProviderCBInvalidState { state } =>
format!("provided invalid callback state after provider authentication: '{state}'"),
Action::ProviderRateLimited => "could not complete OpenID login because it has reached failed attempts rate limit!".to_string(),
Action::ProviderFailedGetToken {state, code} => format!("could not complete login from provider because the id_token could not be retrieved! (state={:?} code = {code})",state),
Action::ProviderFailedGetUserInfo {provider} => format!("could not get user information from userinfo endpoint of provider {}!", provider.id.0),
Action::ProviderEmailNotValidated {provider}=>format!("could not login using provider {} because its email was marked as not validated!", provider.id.0),
Action::ProviderMissingEmailInResponse {provider}=>format!("could not login using provider {} because the email was not provided by userinfo endpoint!", provider.id.0),
Action::ProviderAccountNotFound { provider, email } =>
format!("could not login using provider {} because the email {email} could not be associated to any account!", &provider.id.0),
Action::ProviderAccountDisabled { provider, email } =>
format!("could not login using provider {} because the account associated to the email {email} is disabled!", &provider.id.0),
Action::ProviderAccountNotAllowedToLoginWithProvider { provider, email } =>
format!("could not login using provider {} because the account associated to the email {email} is not allowed to authenticate using this provider!", &provider.id.0),
Action::ProviderLoginFailed { provider, email } =>
format!("could not login using provider {} with the email {email} for an unknown reason!", &provider.id.0),
Action::Signout => "signed out".to_string(),
Action::UserNeed2FAOnLogin(user) => {
format!(
"successfully authenticated as user {:?} but need to do 2FA authentication",
user.quick_identity()
)
}
Action::UserSuccessfullyAuthenticated(user) => {
format!("successfully authenticated as {}", user.quick_identity())
}
Action::UserNeedNewPasswordOnLogin(user) => format!(
"successfully authenticated as {}, but need to set a new password",
user.quick_identity()
),
Action::TryLoginWithDisabledAccount(login) => {
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) => {
format!("attempted to authenticate as {login} but with a WRONG PASSWORD")
}
Action::UserChangedPasswordOnLogin(user_id) => {
format!("set a new password at login as user {user_id:?}")
}
Action::OTPLoginAttempt { user, success } => match success {
true => format!(
"successfully performed OTP attempt for user {}",
user.quick_identity()
),
false => format!(
"performed FAILED OTP attempt for user {}",
user.quick_identity()
),
},
Action::NewOpenIDSession { client } => {
format!("opened a new OpenID session with {:?}", client.id)
}
Action::ChangedHisPassword => "changed his password".to_string(),
Action::ClearedHisLoginHistory => "cleared his login history".to_string(),
Action::AddNewFactor(factor) => format!(
"added a new factor to his account : {}",
factor.quick_description(),
),
Action::Removed2FAFactor { factor_id } => format!("Removed his factor {factor_id:?}"),
}
}
}
pub struct ActionLogger {
ip: IpAddr,
user: Option<User>,
}
impl ActionLogger {
pub fn log(&self, action: Action) {
log::info!(
"{} from {} has {}",
match &self.user {
None => "Anonymous user".to_string(),
Some(u) => u.quick_identity(),
},
self.ip.to_string(),
action.as_string()
)
}
}
impl FromRequest for ActionLogger {
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let req = req.clone();
Box::pin(async move {
let user_actor: &web::Data<Addr<UsersActor>> =
req.app_data().expect("UserActor undefined!");
let user_actor: Addr<UsersActor> = user_actor.as_ref().clone();
let user_id = Identity::from_request(&req, &mut Payload::None)
.into_inner()
.ok()
.and_then(|id| {
let sess = SessionIdentity(Some(&id));
match sess.is_authenticated() {
true => Some(sess.user_id()),
false => None,
}
});
Ok(Self {
ip: RemoteIP::from_request(&req, &mut Payload::None)
.await
.unwrap()
.0,
user: match user_id {
None => None,
Some(u) => {
user_actor
.send(users_actor::GetUserRequest(u))
.await
.unwrap()
.0
}
},
})
})
}
}