feat(user): map family/given name to OIDC & cardav

This commit is contained in:
Edouard Vanbelle
2026-06-02 16:22:09 +02:00
parent 54c1989b44
commit 803849fe2e
4 changed files with 33 additions and 3 deletions
+7
View File
@@ -164,6 +164,13 @@ pub struct OidcIdClaims {
pub email_verified: Option<bool>,
pub preferred_username: Option<String>,
pub name: Option<String>,
/// Standard OpenID claim `given_name` (first name). Populated on the
/// `User` row at JIT provisioning so the share-modal autocomplete and
/// the system address book can surface real names instead of just the
/// (often-cryptic) `preferred_username`.
pub given_name: Option<String>,
/// Standard OpenID claim `family_name` (last name). See `given_name`.
pub family_name: Option<String>,
pub groups: Vec<String>,
pub picture: Option<String>,
}
@@ -1641,6 +1641,8 @@ impl AuthApplicationService {
email: user_info.email.or(claims.email),
preferred_username: user_info.preferred_username.or(claims.preferred_username),
name: user_info.name.or(claims.name),
given_name: user_info.given_name.or(claims.given_name),
family_name: user_info.family_name.or(claims.family_name),
email_verified: user_info.email_verified.or(claims.email_verified),
groups: if user_info.groups.is_empty() {
claims.groups
@@ -1797,6 +1799,8 @@ impl AuthApplicationService {
)
})?;
new_user.set_image(claims.picture.clone());
new_user.set_given_name(claims.given_name.clone());
new_user.set_family_name(claims.family_name.clone());
let created_user = self.user_storage.create_user(new_user).await?;
@@ -65,6 +65,8 @@ struct IdTokenClaims {
email_verified: Option<bool>,
preferred_username: Option<String>,
name: Option<String>,
given_name: Option<String>,
family_name: Option<String>,
groups: Option<Vec<String>>,
nonce: Option<String>,
picture: Option<String>,
@@ -90,6 +92,8 @@ struct UserInfoResponse {
email_verified: Option<bool>,
preferred_username: Option<String>,
name: Option<String>,
given_name: Option<String>,
family_name: Option<String>,
groups: Option<Vec<String>>,
picture: Option<String>,
}
@@ -461,6 +465,8 @@ impl OidcServicePort for OidcService {
email_verified: claims.email_verified,
preferred_username: claims.preferred_username,
name: claims.name,
given_name: claims.given_name,
family_name: claims.family_name,
groups: claims.groups.unwrap_or_default(),
picture: claims.picture,
})
@@ -513,6 +519,8 @@ impl OidcServicePort for OidcService {
email_verified: info.email_verified,
preferred_username: info.preferred_username,
name: info.name,
given_name: info.given_name,
family_name: info.family_name,
groups: info.groups.unwrap_or_default(),
picture: info.picture,
})
@@ -182,14 +182,25 @@ fn if_match_passes(if_match: Option<&str>, stored_etag: &str) -> bool {
/// Map a `UserDto` to a `ContactDto` so OxiCloud users appear as contacts
/// inside the virtual system address book.
///
/// `given_name`/`family_name` come from OIDC standard claims at JIT
/// provisioning (or NULL for password-only or pre-OIDC users). When
/// they're present, prefer a "First Last" full name; otherwise fall
/// back to the username (which is always present).
fn user_to_contact(user: UserDto) -> ContactDto {
let full_name = match (user.given_name.as_deref(), user.family_name.as_deref()) {
(Some(g), Some(f)) => format!("{g} {f}"),
(Some(g), None) => g.to_string(),
(None, Some(f)) => f.to_string(),
(None, None) => user.username.clone(),
};
ContactDto {
id: user.id.clone(),
address_book_id: SYSTEM_BOOK_ID.to_string(),
uid: format!("{}@oxicloud", user.id),
full_name: Some(user.username.clone()),
first_name: None,
last_name: None,
full_name: Some(full_name),
first_name: user.given_name.clone(),
last_name: user.family_name.clone(),
nickname: None,
email: vec![EmailDto {
email: user.email,