]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/types/userid.rs
bdf383de679af9f73054525b45e3e9b2e44263b3
[proxmox-backup.git] / src / api2 / types / userid.rs
1 //! Types for user handling.
2 //!
3 //! We have [`Username`]s and [`Realm`]s. To uniquely identify a user, they must be combined into a [`Userid`].
4 //!
5 //! Since they're all string types, they're organized as follows:
6 //!
7 //! * [`Username`]: an owned user name. Internally a `String`.
8 //! * [`UsernameRef`]: a borrowed user name. Pairs with a `Username` the same way a `str` pairs
9 //! with `String`, meaning you can only make references to it.
10 //! * [`Realm`]: an owned realm (`String` equivalent).
11 //! * [`RealmRef`]: a borrowed realm (`str` equivalent).
12 //! * [`Userid`]: an owned user id (`"user@realm"`). Note that this does not have a separte
13 //! borrowed type.
14 //!
15 //! Note that `Username`s are not unique, therefore they do not implement `Eq` and cannot be
16 //! compared directly. If a direct comparison is really required, they can be compared as strings
17 //! via the `as_str()` method. [`Realm`]s and [`Userid`]s on the other hand can be compared with
18 //! each other, as in those two cases the comparison has meaning.
19
20 use std::borrow::Borrow;
21 use std::convert::TryFrom;
22 use std::fmt;
23
24 use anyhow::{bail, format_err, Error};
25 use lazy_static::lazy_static;
26 use serde::{Deserialize, Serialize};
27
28 use proxmox::api::api;
29 use proxmox::api::schema::{ApiStringFormat, Schema, StringSchema};
30 use proxmox::const_regex;
31
32 // we only allow a limited set of characters
33 // colon is not allowed, because we store usernames in
34 // colon separated lists)!
35 // slash is not allowed because it is used as pve API delimiter
36 // also see "man useradd"
37 macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
38 macro_rules! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) }
39 macro_rules! USER_ID_REGEX_STR { () => (concat!(USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!())) }
40
41 const_regex! {
42 pub PROXMOX_USER_NAME_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"$");
43 pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_ID_REGEX_STR!(), r"$");
44 pub PROXMOX_GROUP_ID_REGEX = concat!(r"^", GROUP_NAME_REGEX_STR!(), r"$");
45 }
46
47 pub const PROXMOX_USER_NAME_FORMAT: ApiStringFormat =
48 ApiStringFormat::Pattern(&PROXMOX_USER_NAME_REGEX);
49
50 pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
51 ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX);
52
53 pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat =
54 ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX);
55
56 pub const PROXMOX_GROUP_ID_SCHEMA: Schema = StringSchema::new("Group ID")
57 .format(&PROXMOX_GROUP_ID_FORMAT)
58 .min_length(3)
59 .max_length(64)
60 .schema();
61
62 pub const PROXMOX_AUTH_REALM_STRING_SCHEMA: StringSchema =
63 StringSchema::new("Authentication domain ID")
64 .format(&super::PROXMOX_SAFE_ID_FORMAT)
65 .min_length(3)
66 .max_length(32);
67 pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = PROXMOX_AUTH_REALM_STRING_SCHEMA.schema();
68
69
70 #[api(
71 type: String,
72 format: &PROXMOX_USER_NAME_FORMAT,
73 )]
74 /// The user name part of a user id.
75 ///
76 /// This alone does NOT uniquely identify the user and therefore does not implement `Eq`. In order
77 /// to compare user names directly, they need to be explicitly compared as strings by calling
78 /// `.as_str()`.
79 ///
80 /// ```compile_fail
81 /// fn test(a: Username, b: Username) -> bool {
82 /// a == b // illegal and does not compile
83 /// }
84 /// ```
85 #[derive(Clone, Debug, Hash, Deserialize, Serialize)]
86 pub struct Username(String);
87
88 /// A reference to a user name part of a user id. This alone does NOT uniquely identify the user.
89 ///
90 /// This is like a `str` to the `String` of a [`Username`].
91 #[derive(Debug, Hash)]
92 pub struct UsernameRef(str);
93
94 #[doc(hidden)]
95 /// ```compile_fail
96 /// let a: Username = unsafe { std::mem::zeroed() };
97 /// let b: Username = unsafe { std::mem::zeroed() };
98 /// let _ = <Username as PartialEq>::eq(&a, &b);
99 /// ```
100 ///
101 /// ```compile_fail
102 /// let a: &UsernameRef = unsafe { std::mem::zeroed() };
103 /// let b: &UsernameRef = unsafe { std::mem::zeroed() };
104 /// let _ = <&UsernameRef as PartialEq>::eq(a, b);
105 /// ```
106 ///
107 /// ```compile_fail
108 /// let a: &UsernameRef = unsafe { std::mem::zeroed() };
109 /// let b: &UsernameRef = unsafe { std::mem::zeroed() };
110 /// let _ = <&UsernameRef as PartialEq>::eq(&a, &b);
111 /// ```
112 struct _AssertNoEqImpl;
113
114 impl UsernameRef {
115 fn new(s: &str) -> &Self {
116 unsafe { &*(s as *const str as *const UsernameRef) }
117 }
118
119 pub fn as_str(&self) -> &str {
120 &self.0
121 }
122 }
123
124 impl std::ops::Deref for Username {
125 type Target = UsernameRef;
126
127 fn deref(&self) -> &UsernameRef {
128 self.borrow()
129 }
130 }
131
132 impl Borrow<UsernameRef> for Username {
133 fn borrow(&self) -> &UsernameRef {
134 UsernameRef::new(self.as_str())
135 }
136 }
137
138 impl AsRef<UsernameRef> for Username {
139 fn as_ref(&self) -> &UsernameRef {
140 UsernameRef::new(self.as_str())
141 }
142 }
143
144 impl ToOwned for UsernameRef {
145 type Owned = Username;
146
147 fn to_owned(&self) -> Self::Owned {
148 Username(self.0.to_owned())
149 }
150 }
151
152 impl TryFrom<String> for Username {
153 type Error = Error;
154
155 fn try_from(s: String) -> Result<Self, Error> {
156 if !PROXMOX_USER_NAME_REGEX.is_match(&s) {
157 bail!("invalid user name");
158 }
159
160 Ok(Self(s))
161 }
162 }
163
164 impl<'a> TryFrom<&'a str> for &'a UsernameRef {
165 type Error = Error;
166
167 fn try_from(s: &'a str) -> Result<&'a UsernameRef, Error> {
168 if !PROXMOX_USER_NAME_REGEX.is_match(s) {
169 bail!("invalid name in user id");
170 }
171
172 Ok(UsernameRef::new(s))
173 }
174 }
175
176 #[api(schema: PROXMOX_AUTH_REALM_SCHEMA)]
177 /// An authentication realm.
178 #[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
179 pub struct Realm(String);
180
181 /// A reference to an authentication realm.
182 ///
183 /// This is like a `str` to the `String` of a `Realm`.
184 #[derive(Debug, Hash, Eq, PartialEq)]
185 pub struct RealmRef(str);
186
187 impl RealmRef {
188 fn new(s: &str) -> &Self {
189 unsafe { &*(s as *const str as *const RealmRef) }
190 }
191
192 pub fn as_str(&self) -> &str {
193 &self.0
194 }
195 }
196
197 impl std::ops::Deref for Realm {
198 type Target = RealmRef;
199
200 fn deref(&self) -> &RealmRef {
201 self.borrow()
202 }
203 }
204
205 impl Borrow<RealmRef> for Realm {
206 fn borrow(&self) -> &RealmRef {
207 RealmRef::new(self.as_str())
208 }
209 }
210
211 impl AsRef<RealmRef> for Realm {
212 fn as_ref(&self) -> &RealmRef {
213 RealmRef::new(self.as_str())
214 }
215 }
216
217 impl ToOwned for RealmRef {
218 type Owned = Realm;
219
220 fn to_owned(&self) -> Self::Owned {
221 Realm(self.0.to_owned())
222 }
223 }
224
225 impl TryFrom<String> for Realm {
226 type Error = Error;
227
228 fn try_from(s: String) -> Result<Self, Error> {
229 PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(&s)
230 .map_err(|_| format_err!("invalid realm"))?;
231
232 Ok(Self(s))
233 }
234 }
235
236 impl<'a> TryFrom<&'a str> for &'a RealmRef {
237 type Error = Error;
238
239 fn try_from(s: &'a str) -> Result<&'a RealmRef, Error> {
240 PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(s)
241 .map_err(|_| format_err!("invalid realm"))?;
242
243 Ok(RealmRef::new(s))
244 }
245 }
246
247 impl PartialEq<str> for Realm {
248 fn eq(&self, rhs: &str) -> bool {
249 self.0 == rhs
250 }
251 }
252
253 impl PartialEq<&str> for Realm {
254 fn eq(&self, rhs: &&str) -> bool {
255 self.0 == *rhs
256 }
257 }
258
259 impl PartialEq<str> for RealmRef {
260 fn eq(&self, rhs: &str) -> bool {
261 self.0 == *rhs
262 }
263 }
264
265 impl PartialEq<&str> for RealmRef {
266 fn eq(&self, rhs: &&str) -> bool {
267 self.0 == **rhs
268 }
269 }
270
271 impl PartialEq<RealmRef> for Realm {
272 fn eq(&self, rhs: &RealmRef) -> bool {
273 self.0 == &rhs.0
274 }
275 }
276
277 impl PartialEq<Realm> for RealmRef {
278 fn eq(&self, rhs: &Realm) -> bool {
279 self.0 == rhs.0
280 }
281 }
282
283 impl PartialEq<Realm> for &RealmRef {
284 fn eq(&self, rhs: &Realm) -> bool {
285 (*self).0 == rhs.0
286 }
287 }
288
289 /// A complete user id consting of a user name and a realm.
290 #[derive(Clone, Debug, Hash)]
291 pub struct Userid {
292 data: String,
293 name_len: usize,
294 //name: Username,
295 //realm: Realm,
296 }
297
298 impl Userid {
299 pub const API_SCHEMA: Schema = StringSchema::new("User ID")
300 .format(&PROXMOX_USER_ID_FORMAT)
301 .min_length(3)
302 .max_length(64)
303 .schema();
304
305 const fn new(data: String, name_len: usize) -> Self {
306 Self { data, name_len }
307 }
308
309 pub fn name(&self) -> &UsernameRef {
310 UsernameRef::new(&self.data[..self.name_len])
311 }
312
313 pub fn realm(&self) -> &RealmRef {
314 RealmRef::new(&self.data[(self.name_len + 1)..])
315 }
316
317 pub fn as_str(&self) -> &str {
318 &self.data
319 }
320
321 /// Get the "backup@pam" user id.
322 pub fn backup_userid() -> &'static Self {
323 &*BACKUP_USERID
324 }
325
326 /// Get the "root@pam" user id.
327 pub fn root_userid() -> &'static Self {
328 &*ROOT_USERID
329 }
330 }
331
332 lazy_static! {
333 pub static ref BACKUP_USERID: Userid = Userid::new("backup@pam".to_string(), 6);
334 pub static ref ROOT_USERID: Userid = Userid::new("root@pam".to_string(), 4);
335 }
336
337 impl Eq for Userid {}
338
339 impl PartialEq for Userid {
340 fn eq(&self, rhs: &Self) -> bool {
341 self.data == rhs.data && self.name_len == rhs.name_len
342 }
343 }
344
345 impl From<(Username, Realm)> for Userid {
346 fn from(parts: (Username, Realm)) -> Self {
347 Self::from((parts.0.as_ref(), parts.1.as_ref()))
348 }
349 }
350
351 impl From<(&UsernameRef, &RealmRef)> for Userid {
352 fn from(parts: (&UsernameRef, &RealmRef)) -> Self {
353 let data = format!("{}@{}", parts.0.as_str(), parts.1.as_str());
354 let name_len = parts.0.as_str().len();
355 Self { data, name_len }
356 }
357 }
358
359 impl fmt::Display for Userid {
360 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
361 self.data.fmt(f)
362 }
363 }
364
365 impl std::str::FromStr for Userid {
366 type Err = Error;
367
368 fn from_str(id: &str) -> Result<Self, Error> {
369 let (name, realm) = match id.as_bytes().iter().rposition(|&b| b == b'@') {
370 Some(pos) => (&id[..pos], &id[(pos + 1)..]),
371 None => bail!("not a valid user id"),
372 };
373
374 PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(realm)
375 .map_err(|_| format_err!("invalid realm in user id"))?;
376
377 Ok(Self::from((UsernameRef::new(name), RealmRef::new(realm))))
378 }
379 }
380
381 impl TryFrom<String> for Userid {
382 type Error = Error;
383
384 fn try_from(data: String) -> Result<Self, Error> {
385 let name_len = data
386 .as_bytes()
387 .iter()
388 .rposition(|&b| b == b'@')
389 .ok_or_else(|| format_err!("not a valid user id"))?;
390
391 PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(&data[(name_len + 1)..])
392 .map_err(|_| format_err!("invalid realm in user id"))?;
393
394 Ok(Self { data, name_len })
395 }
396 }
397
398 impl PartialEq<str> for Userid {
399 fn eq(&self, rhs: &str) -> bool {
400 rhs.len() > self.name_len + 2 // make sure range access below is allowed
401 && rhs.starts_with(self.name().as_str())
402 && rhs.as_bytes()[self.name_len] == b'@'
403 && &rhs[(self.name_len + 1)..] == self.realm().as_str()
404 }
405 }
406
407 impl PartialEq<&str> for Userid {
408 fn eq(&self, rhs: &&str) -> bool {
409 *self == **rhs
410 }
411 }
412
413 impl PartialEq<String> for Userid {
414 fn eq(&self, rhs: &String) -> bool {
415 self == rhs.as_str()
416 }
417 }
418
419 proxmox::forward_deserialize_to_from_str!(Userid);
420 proxmox::forward_serialize_to_display!(Userid);