use crate::error::{Error, Result};
use crate::http::IHttpContext;
use std::collections::HashMap;
pub trait IClaims: Send + Sync {
fn subject(&self) -> &str;
fn roles(&self) -> &[String];
fn permissions(&self) -> &[String];
fn claims(&self) -> &HashMap<String, String>;
fn clone_box(&self) -> Box<dyn IClaims>;
fn has_role(&self, role: &str) -> bool {
self.roles().iter().any(|r| r == role)
}
fn get_userid(&self) -> &str {
self.subject()
}
fn get_username(&self) -> Option<&str> {
self.claims().get("name").map(|s| s.as_str())
}
fn get_tenantid(&self) -> Option<&str> {
self.claims()
.get("tenant_id")
.or_else(|| self.claims().get("tenant"))
.map(|s| s.as_str())
}
fn get_email(&self) -> Option<&str> {
self.claims().get("email").map(|s| s.as_str())
}
}
impl Clone for Box<dyn IClaims> {
fn clone(&self) -> Self {
self.clone_box()
}
}
#[async_trait::async_trait]
pub trait IAuthenticationHandler: Send + Sync {
async fn authenticate(&self, ctx: &mut dyn IHttpContext) -> Result<Option<Box<dyn IClaims>>>;
}
#[async_trait::async_trait]
pub trait IAuthorizationPolicy: Send + Sync {
async fn authorize(&self, claims: &dyn IClaims, resource_key: &str, method: &str)
-> Result<()>;
}
#[async_trait::async_trait]
pub trait IDynamicAuthorizer: Send + Sync {
async fn authorize(
&self,
claims: &dyn IClaims,
route_pattern: &str,
method: &str,
) -> Result<()>;
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct IdentityUser {
pub id: String,
pub name: String,
pub email: String,
pub tenant_id: String,
pub roles: Vec<String>,
pub permissions: Vec<String>,
}
impl IdentityUser {
pub fn from_claims(claims: &dyn IClaims) -> Self {
Self {
id: claims.subject().to_string(),
name: claims.get_username().unwrap_or_default().to_string(),
email: claims.get_email().unwrap_or_default().to_string(),
tenant_id: claims.get_tenantid().unwrap_or_default().to_string(),
roles: claims.roles().to_vec(),
permissions: claims.permissions().to_vec(),
}
}
pub fn is_in_role(&self, role: &str) -> bool {
self.roles.iter().any(|assigned| assigned == role)
}
pub fn has_permission(&self, permission: &str) -> bool {
self.permissions
.iter()
.any(|assigned| assigned == permission)
}
pub fn display_name(&self) -> &str {
if self.name.is_empty() {
&self.id
} else {
&self.name
}
}
}
pub trait RequestUser {
fn current_user(&self) -> Option<&IdentityUser>;
fn user_id(&self) -> Option<&str> {
self.current_user().map(|user| user.id.as_str())
}
fn user_name(&self) -> Option<&str> {
self.current_user().map(|user| user.name.as_str())
}
fn user_email(&self) -> Option<&str> {
self.current_user().map(|user| user.email.as_str())
}
fn tenant_id(&self) -> Option<&str> {
self.current_user().map(|user| user.tenant_id.as_str())
}
fn roles(&self) -> &[String] {
match self.current_user() {
Some(user) => &user.roles,
None => &[],
}
}
fn is_in_role(&self, role: &str) -> bool {
self.current_user()
.map(|user| user.is_in_role(role))
.unwrap_or(false)
}
fn require_user(&self) -> Result<&IdentityUser> {
self.current_user().ok_or_else(|| {
Error::Unauthorized("authentication required for this endpoint".to_string())
})
}
}