use crate::provider::OidcProvider;
use crate::rauthy_error::RauthyError;
use crate::tokens::claims::AccessToken;
use std::collections::HashMap;
use std::fmt::Display;
use std::ops::Deref;
#[cfg(feature = "actix-web")]
mod actix_web;
#[cfg(feature = "axum")]
mod axum;
#[cfg(feature = "userinfo")]
#[derive(Debug, Clone, serde::Deserialize)]
pub struct AddressClaim {
pub formatted: String,
pub street_address: Option<String>,
pub locality: Option<String>,
pub postal_code: Option<i32>,
pub country: Option<String>,
}
#[cfg(feature = "userinfo")]
#[derive(Debug, serde::Deserialize)]
pub struct Userinfo {
pub id: String,
pub sub: String,
pub name: String,
pub roles: Vec<String>,
pub mfa_enabled: bool,
pub address: Option<AddressClaim>,
pub email: Option<String>,
pub email_verified: Option<bool>,
pub groups: Option<Vec<String>>,
pub preferred_username: Option<String>,
pub given_name: Option<String>,
pub family_name: Option<String>,
pub birthdate: Option<String>,
pub locale: Option<String>,
pub phone: Option<String>,
pub webid: Option<String>,
}
#[derive(Debug)]
pub struct PrincipalOidc {
pub id: String,
pub expires_at_ts: i64,
pub roles: Vec<String>,
pub groups: Vec<String>,
pub scope: String,
pub is_admin: bool,
pub is_user: bool,
pub custom_claims: Option<HashMap<String, serde_json::Value>>,
#[cfg(feature = "userinfo")]
access_token: Option<String>,
}
impl PrincipalOidc {
pub async fn from_token_validated(token: &str) -> Result<Self, RauthyError> {
let claims = AccessToken::from_token_validated(token).await?;
let config = OidcProvider::config()?;
let id = claims
.common
.sub
.ok_or(RauthyError::InvalidClaims("'sub' claim is mandatory"))?;
let roles = claims.roles.unwrap_or_default();
let groups = claims.groups.unwrap_or_default();
let is_admin = config.admin_claim.matches(roles.deref(), groups.deref());
let is_user = is_admin || config.user_claim.matches(roles.deref(), groups.deref());
Ok(Self {
id,
expires_at_ts: claims.common.exp,
roles,
groups,
scope: claims.common.scope.unwrap_or_default(),
is_admin,
is_user,
custom_claims: claims.custom,
#[cfg(feature = "userinfo")]
access_token: Some(token.to_string()),
})
}
#[cfg(feature = "userinfo")]
pub async fn fetch_userinfo(&self) -> Result<Userinfo, RauthyError> {
use crate::provider::{HTTP_CLIENT, OIDC_CONFIG};
use std::borrow::Cow;
let token = match &self.access_token {
None => {
return Err(RauthyError::Init(
"Cannot fetch userinfo when Principal has not been \
initialized with an access_token",
));
}
Some(token) => token,
};
let url = match OIDC_CONFIG.get() {
None => {
return Err(RauthyError::Init(
"OidcProvider::setup_from_config has not been called",
));
}
Some(cfg) => &cfg.provider.userinfo_endpoint,
};
let client = match HTTP_CLIENT.get() {
None => {
return Err(RauthyError::Init(
"OidcProvider::init_client has not been called",
));
}
Some(c) => c,
};
let res = client
.get(url)
.header("Authorization", format!("Bearer {token}"))
.send()
.await?;
let status = res.status();
if status.is_success() {
let info = res.json::<Userinfo>().await?;
Ok(info)
} else {
let body = res.text().await?;
let err = format!("{status} {body}");
Err(RauthyError::Token(Cow::from(err)))
}
}
}
impl Display for PrincipalOidc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Id: {}\nRoles: {:?}\nGroups: {:?}\nScope: {}",
self.id, self.roles, self.groups, self.scope,
)
}
}