Files
BasicOIDC/src/data/client.rs
Pierre HUBERT 91ef6c25d5
All checks were successful
continuous-integration/drone/push Build is passing
Can define additional claims on per-client basis
2024-03-31 18:37:08 +02:00

164 lines
5.0 KiB
Rust

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);
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum AuthenticationFlow {
AuthorizationCode,
Implicit,
}
pub type AdditionalClaims = HashMap<String, Value>;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Client {
/// The ID of the client
pub id: ClientID,
/// The human-readable name of the client
pub name: String,
/// A short description of the service provided by the client
pub description: String,
/// The secret used by the client to retrieve authenticated users information
/// This value is absent if implicit authentication flow is used
pub secret: Option<String>,
/// The URI where the users should be redirected once authenticated
pub redirect_uri: String,
/// Specify if the client must be allowed by default for new account
#[serde(default = "bool::default")]
pub default: bool,
/// Specify whether a client is granted to all users
#[serde(default = "bool::default")]
pub granted_to_all_users: bool,
/// 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 {
fn eq(&self, other: &Self) -> bool {
self.id.eq(&other.id)
}
}
impl Eq for Client {}
impl Client {
/// Get the client authentication flow
pub fn auth_flow(&self) -> AuthenticationFlow {
match self.secret {
None => AuthenticationFlow::Implicit,
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>;
impl EntityManager<Client> {
pub fn find_by_id(&self, u: &ClientID) -> Option<Client> {
for entry in self.iter() {
if entry.id.eq(u) {
return Some(entry.clone());
}
}
None
}
/// Get the list of default clients.
///
/// i.e. the clients that are granted to new accounts by default
pub fn get_default_clients(&self) -> Vec<&Client> {
self.iter().filter(|u| u.default).collect()
}
pub fn apply_environment_variables(&mut self) {
for c in self.iter_mut() {
c.id = ClientID(apply_env_vars(&c.id.0));
c.name = apply_env_vars(&c.name);
c.description = apply_env_vars(&c.description);
c.secret = c.secret.as_deref().map(apply_env_vars);
c.redirect_uri = apply_env_vars(&c.redirect_uri);
}
}
}