Can define additional claims on per-client basis
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2024-03-31 18:37:08 +02:00
parent d087c5629d
commit 91ef6c25d5
5 changed files with 162 additions and 43 deletions

View File

@ -1,5 +1,8 @@
use crate::data::entity_manager::EntityManager;
use crate::data::user::User;
use crate::utils::string_utils::apply_env_vars;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
pub struct ClientID(pub String);
@ -10,6 +13,8 @@ pub enum AuthenticationFlow {
Implicit,
}
pub type AdditionalClaims = HashMap<String, Value>;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Client {
/// The ID of the client
@ -39,6 +44,12 @@ pub struct Client {
/// Specify whether recent Second Factor Authentication is required to access this client
#[serde(default = "bool::default")]
pub enforce_2fa_auth: bool,
/// Additional claims to return with the id token
claims_id_token: Option<AdditionalClaims>,
/// Additional claims to return through the user info endpoint
claims_user_info: Option<AdditionalClaims>,
}
impl PartialEq for Client {
@ -57,6 +68,68 @@ impl Client {
Some(_) => AuthenticationFlow::AuthorizationCode,
}
}
/// Process a single claim value
fn process_claim_string(&self, user: &User, str: &str) -> String {
str.replace("{username}", &user.username)
.replace("{mail}", &user.email)
.replace("{first_name}", &user.first_name)
.replace("{last_name}", &user.last_name)
.replace("{uid}", &user.uid.0)
}
/// Recurse claims processing
fn recurse_claims_processing(&self, user: &User, value: &Value) -> Value {
match value {
Value::String(s) => Value::String(self.process_claim_string(user, s)),
Value::Array(arr) => Value::Array(
arr.iter()
.map(|v| self.recurse_claims_processing(user, v))
.collect(),
),
Value::Object(obj) => obj
.iter()
.map(|(k, v)| {
(
self.process_claim_string(user, k),
self.recurse_claims_processing(user, v),
)
})
.collect(),
v => v.clone(),
}
}
/// Process additional claims, processing placeholders
fn process_additional_claims(
&self,
user: &User,
claims: &Option<AdditionalClaims>,
) -> Option<AdditionalClaims> {
let claims = claims.as_ref()?;
let res = claims
.iter()
.map(|(k, v)| {
(
self.process_claim_string(user, k),
self.recurse_claims_processing(user, v),
)
})
.collect();
Some(res)
}
/// Get additional claims for id_token for a successful authentication
pub fn claims_id_token(&self, user: &User) -> Option<AdditionalClaims> {
self.process_additional_claims(user, &self.claims_id_token)
}
/// Get additional claims for user info for a successful authentication
pub fn claims_user_info(&self, user: &User) -> Option<AdditionalClaims> {
self.process_additional_claims(user, &self.claims_user_info)
}
}
pub type ClientManager = EntityManager<Client>;

View File

@ -1,3 +1,4 @@
use crate::data::client::AdditionalClaims;
use jwt_simple::claims::Audiences;
use jwt_simple::prelude::{Duration, JWTClaims};
@ -24,12 +25,19 @@ pub struct IdToken {
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<String>,
pub email: String,
/// Additional claims
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(flatten)]
pub additional_claims: Option<AdditionalClaims>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct CustomIdTokenClaims {
auth_time: u64,
email: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(flatten)]
additional_claims: Option<AdditionalClaims>,
}
impl IdToken {
@ -46,6 +54,7 @@ impl IdToken {
custom: CustomIdTokenClaims {
auth_time: self.auth_time,
email: self.email,
additional_claims: self.additional_claims,
},
}
}