use crate::{PasswordEncoder, SecurityError, SecurityResult, User, UserDetails};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Authentication {
pub principal: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub credentials: Option<String>,
pub authorities: Vec<crate::Authority>,
pub authenticated: bool,
pub details: Option<AuthDetails>,
pub login_time: DateTime<Utc>,
}
impl Authentication {
pub fn new(principal: impl Into<String>, credentials: impl Into<String>) -> Self {
Self {
principal: principal.into(),
credentials: Some(credentials.into()),
authorities: Vec::new(),
authenticated: false,
details: None,
login_time: Utc::now(),
}
}
pub fn from_user(user: &User) -> Self {
Self {
principal: user.username.clone(),
credentials: None, authorities: user.authorities.clone(),
authenticated: true,
details: None,
login_time: Utc::now(),
}
}
pub fn from_user_details(user_details: &dyn UserDetails) -> Self {
Self {
principal: user_details.username().to_string(),
credentials: None,
authorities: user_details.authorities(),
authenticated: true,
details: None,
login_time: Utc::now(),
}
}
pub fn set_authenticated(mut self, authenticated: bool) -> Self {
self.authenticated = authenticated;
self
}
pub fn set_authorities(mut self, authorities: Vec<crate::Authority>) -> Self {
self.authorities = authorities;
self
}
pub fn set_details(mut self, details: AuthDetails) -> Self {
self.details = Some(details);
self
}
pub fn clear_credentials(&mut self) {
self.credentials = None;
}
pub fn name(&self) -> &str {
&self.principal
}
pub fn has_authority(&self, authority: &crate::Authority) -> bool {
self.authorities.contains(authority)
}
pub fn has_role(&self, role: &crate::Role) -> bool {
self.authorities
.contains(&crate::Authority::Role(role.clone()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AuthDetails {
pub remote_address: Option<String>,
pub session_id: Option<String>,
pub user_agent: Option<String>,
pub auth_type: Option<String>,
}
impl AuthDetails {
pub fn new() -> Self {
Self {
remote_address: None,
session_id: None,
user_agent: None,
auth_type: None,
}
}
pub fn remote_address(mut self, addr: impl Into<String>) -> Self {
self.remote_address = Some(addr.into());
self
}
pub fn session_id(mut self, id: impl Into<String>) -> Self {
self.session_id = Some(id.into());
self
}
pub fn user_agent(mut self, agent: impl Into<String>) -> Self {
self.user_agent = Some(agent.into());
self
}
pub fn auth_type(mut self, auth_type: impl Into<String>) -> Self {
self.auth_type = Some(auth_type.into());
self
}
}
impl Default for AuthDetails {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct UsernamePasswordAuthenticationToken {
pub username: String,
pub password: String,
}
impl UsernamePasswordAuthenticationToken {
pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
Self {
username: username.into(),
password: password.into(),
}
}
}
#[async_trait::async_trait]
pub trait AuthenticationManager: Send + Sync {
async fn authenticate(&self, auth: Authentication) -> SecurityResult<Authentication>;
fn supports(&self, auth: &Authentication) -> bool;
}
pub struct SimpleAuthenticationManager {
user_service: Arc<dyn crate::UserService>,
password_encoder: Arc<dyn PasswordEncoder>,
pub hide_user_not_found: bool,
}
impl SimpleAuthenticationManager {
pub fn new(
user_service: Arc<dyn crate::UserService>,
password_encoder: Arc<dyn PasswordEncoder>,
) -> Self {
Self {
user_service,
password_encoder,
hide_user_not_found: true,
}
}
pub fn hide_user_not_found(mut self, hide: bool) -> Self {
self.hide_user_not_found = hide;
self
}
}
#[async_trait::async_trait]
impl AuthenticationManager for SimpleAuthenticationManager {
async fn authenticate(&self, auth: Authentication) -> SecurityResult<Authentication> {
let username = &auth.principal;
let password = auth.credentials.as_ref().ok_or_else(|| {
SecurityError::InvalidCredentials("No credentials provided".to_string())
})?;
let user = match self.user_service.load_user_by_username(username).await {
Ok(u) => u,
Err(e) => {
if self.hide_user_not_found {
return Err(SecurityError::InvalidCredentials(
"Invalid credentials".to_string(),
));
}
return Err(e);
},
};
if !user.is_enabled() {
return Err(SecurityError::Disabled("User is disabled".to_string()));
}
if !user.is_account_non_expired() {
return Err(SecurityError::AccountExpired("Account expired".to_string()));
}
if !user.is_account_non_locked() {
return Err(SecurityError::Locked("Account is locked".to_string()));
}
if !user.is_credentials_non_expired() {
return Err(SecurityError::CredentialsExpired("Credentials expired".to_string()));
}
if !self.password_encoder.matches(password, user.password()) {
return Err(SecurityError::InvalidCredentials("Invalid credentials".to_string()));
}
Ok(Authentication::from_user_details(user.as_ref()))
}
fn supports(&self, auth: &Authentication) -> bool {
auth.credentials.is_some()
}
}
#[derive(Debug, Clone)]
pub struct AnonymousAuthentication;
impl AnonymousAuthentication {
pub fn new() -> Authentication {
Authentication {
principal: crate::ANONYMOUS_USER.to_string(),
credentials: None,
authorities: vec![crate::Authority::Role(crate::Role::Guest)],
authenticated: true,
details: Some(AuthDetails::new().auth_type("ANONYMOUS")),
login_time: Utc::now(),
}
}
pub fn is_anonymous(auth: &Authentication) -> bool {
auth.principal == crate::ANONYMOUS_USER
}
}
#[derive(Debug, Clone)]
pub struct RememberMeAuthentication {
pub key_hash: String,
}
impl RememberMeAuthentication {
pub fn new(key: &str) -> Self {
use md5::{Digest, Md5};
let hash = Md5::digest(key.as_bytes());
Self {
key_hash: hex::encode(hash),
}
}
pub fn verify(&self, key: &str) -> bool {
use md5::{Digest, Md5};
let hash = Md5::digest(key.as_bytes());
hex::encode(hash) == self.key_hash
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{PasswordEncoder, Role};
use std::sync::Arc;
struct MockPasswordEncoder;
impl PasswordEncoder for MockPasswordEncoder {
fn encode(&self, raw: &str) -> String {
format!("HASH:{}", raw)
}
fn matches(&self, raw: &str, encoded: &str) -> bool {
encoded == format!("HASH:{}", raw)
}
}
#[tokio::test]
async fn test_simple_auth_manager() {
let user_service = Arc::new(
crate::InMemoryUserService::with_users(vec![User::with_roles(
"john",
"HASH:secret123",
&[Role::User],
)])
.await,
);
let manager = SimpleAuthenticationManager::new(user_service, Arc::new(MockPasswordEncoder));
let auth = Authentication::new("john", "secret123");
let result = manager.authenticate(auth).await;
assert!(result.is_ok());
let authenticated = result.unwrap();
assert!(authenticated.authenticated);
assert_eq!(authenticated.principal, "john");
}
#[test]
fn test_anonymous_authentication() {
let auth = AnonymousAuthentication::new();
assert!(AnonymousAuthentication::is_anonymous(&auth));
assert!(auth.authenticated);
}
}