use serde::{Deserialize, Serialize};
use crate::error::SecurityError;
pub trait IdentityBuilder: Send + Sync {
type Identity: Clone + Send + Sync;
fn build(
&self,
claims: serde_json::Value,
) -> impl std::future::Future<Output = Result<Self::Identity, SecurityError>> + Send;
}
pub struct DefaultIdentityBuilder {
role_extractor: Box<dyn RoleExtractor>,
}
impl DefaultIdentityBuilder {
pub fn new() -> Self {
Self {
role_extractor: Box::new(DefaultRoleExtractor),
}
}
pub fn with_extractor(role_extractor: Box<dyn RoleExtractor>) -> Self {
Self { role_extractor }
}
}
impl Default for DefaultIdentityBuilder {
fn default() -> Self {
Self::new()
}
}
impl IdentityBuilder for DefaultIdentityBuilder {
type Identity = AuthenticatedUser;
fn build(
&self,
claims: serde_json::Value,
) -> impl std::future::Future<Output = Result<AuthenticatedUser, SecurityError>> + Send {
let user = build_authenticated_user(claims, &*self.role_extractor);
std::future::ready(Ok(user))
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthenticatedUser {
pub sub: String,
pub email: Option<String>,
pub roles: Vec<String>,
pub claims: serde_json::Value,
}
impl quarlus_core::Identity for AuthenticatedUser {
fn sub(&self) -> &str {
&self.sub
}
fn roles(&self) -> &[String] {
&self.roles
}
}
impl AuthenticatedUser {
pub fn has_role(&self, role: &str) -> bool {
self.roles.iter().any(|r| r == role)
}
pub fn has_any_role(&self, roles: &[&str]) -> bool {
roles.iter().any(|role| self.has_role(role))
}
}
pub trait RoleExtractor: Send + Sync {
fn extract_roles(&self, claims: &serde_json::Value) -> Vec<String>;
}
pub struct DefaultRoleExtractor;
impl RoleExtractor for DefaultRoleExtractor {
fn extract_roles(&self, claims: &serde_json::Value) -> Vec<String> {
if let Some(roles) = claims.get("roles").and_then(|v| v.as_array()) {
let extracted: Vec<String> = roles
.iter()
.filter_map(|r| r.as_str().map(String::from))
.collect();
if !extracted.is_empty() {
return extracted;
}
}
if let Some(roles) = claims
.get("realm_access")
.and_then(|v| v.get("roles"))
.and_then(|v| v.as_array())
{
let extracted: Vec<String> = roles
.iter()
.filter_map(|r| r.as_str().map(String::from))
.collect();
if !extracted.is_empty() {
return extracted;
}
}
Vec::new()
}
}
pub fn build_authenticated_user(
claims: serde_json::Value,
role_extractor: &dyn RoleExtractor,
) -> AuthenticatedUser {
let sub = claims
.get("sub")
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string();
let email = claims
.get("email")
.and_then(|v| v.as_str())
.map(String::from);
let roles = role_extractor.extract_roles(&claims);
AuthenticatedUser {
sub,
email,
roles,
claims,
}
}