pub mod audit;
pub mod homerealm;
mod idtoken;
pub mod slo;
#[cfg(feature = "saml")]
pub mod saml;
#[cfg(feature = "scim")]
pub mod scim;
pub use audit::AuditPipelineFederationSink;
pub use homerealm::{IdpConfig, IdpKind, IdpRegistry};
pub use idtoken::{IdTokenVerifier, JwtIdTokenVerifier, VerifiedIdToken};
pub use slo::BackChannelLogout;
use std::collections::HashMap;
use std::sync::RwLock;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::model::TokenResponse;
#[derive(Debug, Clone)]
pub struct FederatedProfile {
pub provider: String,
pub provider_id: String,
pub email: Option<String>,
pub email_verified: bool,
pub name: Option<String>,
pub attributes: serde_json::Map<String, serde_json::Value>,
}
impl FederatedProfile {
pub fn from_oauth(
provider: impl Into<String>,
provider_id: impl Into<String>,
email: Option<String>,
email_verified: bool,
) -> Self {
Self {
provider: provider.into(),
provider_id: provider_id.into(),
email,
email_verified,
name: None,
attributes: Default::default(),
}
}
pub fn from_id_token(provider: impl Into<String>, tok: &VerifiedIdToken) -> Self {
Self {
provider: provider.into(),
provider_id: tok.sub.clone(),
email: tok.email.clone(),
email_verified: tok.email_verified,
name: tok.name.clone(),
attributes: tok.extra.clone(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FederatedIdentity {
pub provider: String,
pub provider_id: String,
pub user_id: String,
pub linked_at: u64,
}
#[async_trait]
pub trait FederatedIdentityStore: Send + Sync + 'static {
async fn find_user(&self, provider: &str, provider_id: &str) -> Result<Option<String>>;
async fn link(&self, link: FederatedIdentity) -> Result<()>;
async fn list_for_user(&self, user_id: &str) -> Result<Vec<FederatedIdentity>>;
async fn unlink(&self, provider: &str, provider_id: &str) -> Result<()>;
}
#[derive(Debug, Clone)]
pub struct LinkingConfig {
pub jit_provisioning: bool,
pub auto_link_verified_email: bool,
}
impl Default for LinkingConfig {
fn default() -> Self {
Self {
jit_provisioning: true,
auto_link_verified_email: false,
}
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum FederatedOutcome {
Authenticated(TokenResponse),
MfaChallenge {
mfa_required: bool,
challenge_id: String,
methods: Vec<String>,
},
LinkRequired { email: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FederationDecision {
ReturningLogin,
Provisioned,
AutoLinked,
LinkRequired,
Denied,
}
#[derive(Debug, Clone)]
pub struct FederationEvent {
pub provider: String,
pub provider_id: String,
pub user_id: Option<String>,
pub email: Option<String>,
pub tenant: Option<String>,
pub decision: FederationDecision,
pub ip: Option<String>,
pub trace_id: Option<String>,
}
#[async_trait]
pub trait FederationAudit: Send + Sync + 'static {
async fn record(&self, event: FederationEvent);
}
#[derive(Default)]
pub struct MemoryFederatedStore {
inner: RwLock<HashMap<(String, String), FederatedIdentity>>,
}
impl MemoryFederatedStore {
pub fn new() -> Self {
Self::default()
}
}
#[async_trait]
impl FederatedIdentityStore for MemoryFederatedStore {
async fn find_user(&self, provider: &str, provider_id: &str) -> Result<Option<String>> {
Ok(self
.inner
.read()
.unwrap()
.get(&(provider.to_owned(), provider_id.to_owned()))
.map(|l| l.user_id.clone()))
}
async fn link(&self, link: FederatedIdentity) -> Result<()> {
self.inner
.write()
.unwrap()
.insert((link.provider.clone(), link.provider_id.clone()), link);
Ok(())
}
async fn list_for_user(&self, user_id: &str) -> Result<Vec<FederatedIdentity>> {
Ok(self
.inner
.read()
.unwrap()
.values()
.filter(|l| l.user_id == user_id)
.cloned()
.collect())
}
async fn unlink(&self, provider: &str, provider_id: &str) -> Result<()> {
self.inner
.write()
.unwrap()
.remove(&(provider.to_owned(), provider_id.to_owned()));
Ok(())
}
}