Skip to main content

nextcloud_users/user/
profile.rs

1use std::fmt;
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5use super::Quota;
6use crate::wire::lossy_bool;
7
8/// Redacted placeholder for sensitive fields in debug representations.
9const REDACTED: &str = "<redacted>";
10
11/// Nextcloud user account details.
12///
13/// Decoded from the `data` field of user endpoints:
14/// - Current authenticated user: `/ocs/v1.php/cloud/user` or `/ocs/v2.php/cloud/user`
15/// - Provisioned user profile: `/ocs/v1.php/cloud/users/{userid}` or `/ocs/v2.php/cloud/users/{userid}`
16///
17/// Official documentation:
18/// - User Provisioning API: <https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_provisioning_api.html>
19/// - OCS API Overview: <https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#user-metadata>
20#[derive(Clone, Default, PartialEq, Serialize)]
21pub struct User {
22    /// Unique username / user identifier.
23    pub id: String,
24    /// User display name formatted for the user interface.
25    pub display_name: String,
26    /// Primary configured email address.
27    pub email: Option<String>,
28    /// Whether the user account is active and enabled.
29    pub enabled: bool,
30    /// Storage quota metrics and usage numbers.
31    pub quota: Quota,
32    /// Group memberships assigned to this user.
33    pub groups: Vec<String>,
34    /// Groups for which this user has delegated administrative rights.
35    pub subadmin: Vec<String>,
36    /// Server filesystem storage directory path.
37    pub storage_location: Option<String>,
38    /// Authentication backend powering this account (e.g. `"Database"` or `"LDAP"`).
39    pub backend: Option<String>,
40}
41
42#[derive(Deserialize)]
43struct UserWire {
44    #[serde(default)]
45    id: String,
46    #[serde(default)]
47    displayname: Option<String>,
48    #[serde(default, rename = "display-name")]
49    display_name_hyphen: Option<String>,
50    #[serde(default)]
51    display_name: Option<String>,
52    #[serde(default)]
53    email: Option<String>,
54    #[serde(default = "default_enabled", deserialize_with = "lossy_bool")]
55    enabled: bool,
56    #[serde(default)]
57    quota: Quota,
58    #[serde(default)]
59    groups: Vec<String>,
60    #[serde(default)]
61    subadmin: Vec<String>,
62    #[serde(default)]
63    storage_location: Option<String>,
64    #[serde(default, rename = "storageLocation")]
65    storage_location_camel: Option<String>,
66    #[serde(default, rename = "home")]
67    home: Option<String>,
68    #[serde(default)]
69    backend: Option<String>,
70}
71
72impl<'de> Deserialize<'de> for User {
73    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
74        let wire = UserWire::deserialize(deserializer)?;
75        let display_name = wire
76            .displayname
77            .or(wire.display_name_hyphen)
78            .or(wire.display_name)
79            .unwrap_or_default();
80        let storage_location = wire
81            .storage_location
82            .or(wire.storage_location_camel)
83            .or(wire.home);
84        Ok(Self {
85            id: wire.id,
86            display_name,
87            email: wire.email,
88            enabled: wire.enabled,
89            quota: wire.quota,
90            groups: wire.groups,
91            subadmin: wire.subadmin,
92            storage_location,
93            backend: wire.backend,
94        })
95    }
96}
97
98const fn default_enabled() -> bool {
99    true
100}
101
102impl User {
103    /// Unique user ID.
104    pub fn id(&self) -> &str {
105        &self.id
106    }
107
108    /// User's display name.
109    pub fn display_name(&self) -> &str {
110        &self.display_name
111    }
112
113    /// Primary email address, if set.
114    pub fn email(&self) -> Option<&str> {
115        self.email.as_deref()
116    }
117
118    /// Whether this user account is active.
119    pub const fn is_enabled(&self) -> bool {
120        self.enabled
121    }
122
123    /// Storage quota constraints and usage.
124    pub const fn quota(&self) -> &Quota {
125        &self.quota
126    }
127
128    /// Group memberships.
129    pub fn groups(&self) -> &[String] {
130        &self.groups
131    }
132
133    /// Groups for which this user is a subadmin.
134    pub fn subadmin(&self) -> &[String] {
135        &self.subadmin
136    }
137
138    /// Storage location path on the server, if returned.
139    pub fn storage_location(&self) -> Option<&str> {
140        self.storage_location.as_deref()
141    }
142
143    /// Authentication backend type.
144    pub fn backend(&self) -> Option<&str> {
145        self.backend.as_deref()
146    }
147}
148
149impl fmt::Debug for User {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        f.debug_struct("User")
152            .field("id", &self.id)
153            .field("display_name", &self.display_name)
154            .field("email", &self.email.as_ref().map(|_| REDACTED))
155            .field("enabled", &self.enabled)
156            .field("quota", &self.quota)
157            .field("groups", &self.groups)
158            .field("subadmin", &self.subadmin)
159            .field(
160                "storage_location",
161                &self.storage_location.as_ref().map(|_| REDACTED),
162            )
163            .field("backend", &self.backend)
164            .finish()
165    }
166}