]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-api-types/src/userid.rs
update to proxmox-sys 0.2 crate
[proxmox-backup.git] / pbs-api-types / src / userid.rs
CommitLineData
e7cb4dc5
WB
1//! Types for user handling.
2//!
e0538349
FG
3//! We have [`Username`]s, [`Realm`]s and [`Tokenname`]s. To uniquely identify a user/API token, they
4//! must be combined into a [`Userid`] or [`Authid`].
e7cb4dc5
WB
5//!
6//! Since they're all string types, they're organized as follows:
7//!
8//! * [`Username`]: an owned user name. Internally a `String`.
9//! * [`UsernameRef`]: a borrowed user name. Pairs with a `Username` the same way a `str` pairs
10//! with `String`, meaning you can only make references to it.
11//! * [`Realm`]: an owned realm (`String` equivalent).
12//! * [`RealmRef`]: a borrowed realm (`str` equivalent).
e0538349
FG
13//! * [`Tokenname`]: an owned API token name (`String` equivalent)
14//! * [`TokennameRef`]: a borrowed `Tokenname` (`str` equivalent).
15//! * [`Userid`]: an owned user id (`"user@realm"`).
16//! * [`Authid`]: an owned Authentication ID (a `Userid` with an optional `Tokenname`).
17//! Note that `Userid` and `Authid` do not have a separate borrowed type.
e7cb4dc5 18//!
f4e52bb2 19//! Note that `Username`s are not unique, therefore they do not implement `Eq` and cannot be
e7cb4dc5 20//! compared directly. If a direct comparison is really required, they can be compared as strings
f4e52bb2
FG
21//! via the `as_str()` method. [`Realm`]s, [`Userid`]s and [`Authid`]s on the other hand can be
22//! compared with each other, as in those cases the comparison has meaning.
e7cb4dc5
WB
23
24use std::borrow::Borrow;
25use std::convert::TryFrom;
26use std::fmt;
27
28use anyhow::{bail, format_err, Error};
29use lazy_static::lazy_static;
30use serde::{Deserialize, Serialize};
31
6ef1b649
WB
32use proxmox_schema::{
33 api, const_regex, ApiStringFormat, ApiType, Schema, StringSchema, UpdaterType,
34};
e7cb4dc5
WB
35
36// we only allow a limited set of characters
37// colon is not allowed, because we store usernames in
38// colon separated lists)!
39// slash is not allowed because it is used as pve API delimiter
40// also see "man useradd"
751f6b61 41#[macro_export]
e7cb4dc5 42macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
751f6b61 43#[macro_export]
e7cb4dc5 44macro_rules! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) }
751f6b61 45#[macro_export]
e0538349 46macro_rules! TOKEN_NAME_REGEX_STR { () => (PROXMOX_SAFE_ID_REGEX_STR!()) }
751f6b61 47#[macro_export]
e7cb4dc5 48macro_rules! USER_ID_REGEX_STR { () => (concat!(USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!())) }
751f6b61 49#[macro_export]
e0538349 50macro_rules! APITOKEN_ID_REGEX_STR { () => (concat!(USER_ID_REGEX_STR!() , r"!", TOKEN_NAME_REGEX_STR!())) }
e7cb4dc5
WB
51
52const_regex! {
53 pub PROXMOX_USER_NAME_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"$");
e0538349 54 pub PROXMOX_TOKEN_NAME_REGEX = concat!(r"^", TOKEN_NAME_REGEX_STR!(), r"$");
e7cb4dc5 55 pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_ID_REGEX_STR!(), r"$");
e0538349
FG
56 pub PROXMOX_APITOKEN_ID_REGEX = concat!(r"^", APITOKEN_ID_REGEX_STR!(), r"$");
57 pub PROXMOX_AUTH_ID_REGEX = concat!(r"^", r"(?:", USER_ID_REGEX_STR!(), r"|", APITOKEN_ID_REGEX_STR!(), r")$");
e7cb4dc5
WB
58 pub PROXMOX_GROUP_ID_REGEX = concat!(r"^", GROUP_NAME_REGEX_STR!(), r"$");
59}
60
61pub const PROXMOX_USER_NAME_FORMAT: ApiStringFormat =
62 ApiStringFormat::Pattern(&PROXMOX_USER_NAME_REGEX);
e0538349
FG
63pub const PROXMOX_TOKEN_NAME_FORMAT: ApiStringFormat =
64 ApiStringFormat::Pattern(&PROXMOX_TOKEN_NAME_REGEX);
e7cb4dc5
WB
65
66pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
67 ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX);
e0538349
FG
68pub const PROXMOX_TOKEN_ID_FORMAT: ApiStringFormat =
69 ApiStringFormat::Pattern(&PROXMOX_APITOKEN_ID_REGEX);
70pub const PROXMOX_AUTH_ID_FORMAT: ApiStringFormat =
71 ApiStringFormat::Pattern(&PROXMOX_AUTH_ID_REGEX);
72
73pub const PROXMOX_TOKEN_ID_SCHEMA: Schema = StringSchema::new("API Token ID")
74 .format(&PROXMOX_TOKEN_ID_FORMAT)
75 .min_length(3)
76 .max_length(64)
77 .schema();
78
79pub const PROXMOX_TOKEN_NAME_SCHEMA: Schema = StringSchema::new("API Token name")
80 .format(&PROXMOX_TOKEN_NAME_FORMAT)
81 .min_length(3)
82 .max_length(64)
83 .schema();
e7cb4dc5
WB
84
85pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat =
86 ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX);
87
88pub const PROXMOX_GROUP_ID_SCHEMA: Schema = StringSchema::new("Group ID")
89 .format(&PROXMOX_GROUP_ID_FORMAT)
90 .min_length(3)
91 .max_length(64)
92 .schema();
93
94pub const PROXMOX_AUTH_REALM_STRING_SCHEMA: StringSchema =
95 StringSchema::new("Authentication domain ID")
96 .format(&super::PROXMOX_SAFE_ID_FORMAT)
97 .min_length(3)
98 .max_length(32);
99pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = PROXMOX_AUTH_REALM_STRING_SCHEMA.schema();
100
e7cb4dc5
WB
101#[api(
102 type: String,
103 format: &PROXMOX_USER_NAME_FORMAT,
104)]
105/// The user name part of a user id.
106///
107/// This alone does NOT uniquely identify the user and therefore does not implement `Eq`. In order
108/// to compare user names directly, they need to be explicitly compared as strings by calling
109/// `.as_str()`.
14263ef9
WB
110///
111/// ```compile_fail
112/// fn test(a: Username, b: Username) -> bool {
113/// a == b // illegal and does not compile
114/// }
115/// ```
e7cb4dc5
WB
116#[derive(Clone, Debug, Hash, Deserialize, Serialize)]
117pub struct Username(String);
118
119/// A reference to a user name part of a user id. This alone does NOT uniquely identify the user.
120///
121/// This is like a `str` to the `String` of a [`Username`].
122#[derive(Debug, Hash)]
123pub struct UsernameRef(str);
124
125impl UsernameRef {
126 fn new(s: &str) -> &Self {
127 unsafe { &*(s as *const str as *const UsernameRef) }
128 }
129
130 pub fn as_str(&self) -> &str {
131 &self.0
132 }
133}
134
135impl std::ops::Deref for Username {
136 type Target = UsernameRef;
137
138 fn deref(&self) -> &UsernameRef {
139 self.borrow()
140 }
141}
142
143impl Borrow<UsernameRef> for Username {
144 fn borrow(&self) -> &UsernameRef {
96c3d982 145 UsernameRef::new(self.0.as_str())
e7cb4dc5
WB
146 }
147}
148
149impl AsRef<UsernameRef> for Username {
150 fn as_ref(&self) -> &UsernameRef {
96c3d982 151 self.borrow()
e7cb4dc5
WB
152 }
153}
154
155impl ToOwned for UsernameRef {
156 type Owned = Username;
157
158 fn to_owned(&self) -> Self::Owned {
159 Username(self.0.to_owned())
160 }
161}
162
163impl TryFrom<String> for Username {
164 type Error = Error;
165
166 fn try_from(s: String) -> Result<Self, Error> {
167 if !PROXMOX_USER_NAME_REGEX.is_match(&s) {
168 bail!("invalid user name");
169 }
170
171 Ok(Self(s))
172 }
173}
174
175impl<'a> TryFrom<&'a str> for &'a UsernameRef {
176 type Error = Error;
177
178 fn try_from(s: &'a str) -> Result<&'a UsernameRef, Error> {
179 if !PROXMOX_USER_NAME_REGEX.is_match(s) {
180 bail!("invalid name in user id");
181 }
182
183 Ok(UsernameRef::new(s))
184 }
185}
186
187#[api(schema: PROXMOX_AUTH_REALM_SCHEMA)]
188/// An authentication realm.
189#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
190pub struct Realm(String);
191
192/// A reference to an authentication realm.
193///
194/// This is like a `str` to the `String` of a `Realm`.
195#[derive(Debug, Hash, Eq, PartialEq)]
196pub struct RealmRef(str);
197
198impl RealmRef {
199 fn new(s: &str) -> &Self {
200 unsafe { &*(s as *const str as *const RealmRef) }
201 }
202
203 pub fn as_str(&self) -> &str {
204 &self.0
205 }
206}
207
208impl std::ops::Deref for Realm {
209 type Target = RealmRef;
210
211 fn deref(&self) -> &RealmRef {
212 self.borrow()
213 }
214}
215
216impl Borrow<RealmRef> for Realm {
217 fn borrow(&self) -> &RealmRef {
96c3d982 218 RealmRef::new(self.0.as_str())
e7cb4dc5
WB
219 }
220}
221
222impl AsRef<RealmRef> for Realm {
223 fn as_ref(&self) -> &RealmRef {
96c3d982 224 self.borrow()
e7cb4dc5
WB
225 }
226}
227
228impl ToOwned for RealmRef {
229 type Owned = Realm;
230
231 fn to_owned(&self) -> Self::Owned {
232 Realm(self.0.to_owned())
233 }
234}
235
236impl TryFrom<String> for Realm {
237 type Error = Error;
238
239 fn try_from(s: String) -> Result<Self, Error> {
240 PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(&s)
241 .map_err(|_| format_err!("invalid realm"))?;
242
243 Ok(Self(s))
244 }
245}
246
247impl<'a> TryFrom<&'a str> for &'a RealmRef {
248 type Error = Error;
249
250 fn try_from(s: &'a str) -> Result<&'a RealmRef, Error> {
251 PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(s)
252 .map_err(|_| format_err!("invalid realm"))?;
253
254 Ok(RealmRef::new(s))
255 }
256}
257
258impl PartialEq<str> for Realm {
259 fn eq(&self, rhs: &str) -> bool {
260 self.0 == rhs
261 }
262}
263
264impl PartialEq<&str> for Realm {
265 fn eq(&self, rhs: &&str) -> bool {
266 self.0 == *rhs
267 }
268}
269
270impl PartialEq<str> for RealmRef {
271 fn eq(&self, rhs: &str) -> bool {
272 self.0 == *rhs
273 }
274}
275
276impl PartialEq<&str> for RealmRef {
277 fn eq(&self, rhs: &&str) -> bool {
278 self.0 == **rhs
279 }
280}
281
5d30f038
WB
282impl PartialEq<RealmRef> for Realm {
283 fn eq(&self, rhs: &RealmRef) -> bool {
1d928b25 284 self.0 == rhs.0
5d30f038
WB
285 }
286}
287
288impl PartialEq<Realm> for RealmRef {
289 fn eq(&self, rhs: &Realm) -> bool {
290 self.0 == rhs.0
291 }
292}
293
294impl PartialEq<Realm> for &RealmRef {
295 fn eq(&self, rhs: &Realm) -> bool {
296 (*self).0 == rhs.0
297 }
298}
299
e0538349
FG
300#[api(
301 type: String,
302 format: &PROXMOX_TOKEN_NAME_FORMAT,
303)]
304/// The token ID part of an API token authentication id.
305///
f4e52bb2
FG
306/// This alone does NOT uniquely identify the API token - use a full `Authid` for such use cases.
307#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
e0538349
FG
308pub struct Tokenname(String);
309
310/// A reference to a token name part of an authentication id. This alone does NOT uniquely identify
311/// the user.
312///
313/// This is like a `str` to the `String` of a [`Tokenname`].
314#[derive(Debug, Hash)]
315pub struct TokennameRef(str);
316
317#[doc(hidden)]
318/// ```compile_fail
319/// let a: Username = unsafe { std::mem::zeroed() };
320/// let b: Username = unsafe { std::mem::zeroed() };
321/// let _ = <Username as PartialEq>::eq(&a, &b);
322/// ```
323///
324/// ```compile_fail
325/// let a: &UsernameRef = unsafe { std::mem::zeroed() };
326/// let b: &UsernameRef = unsafe { std::mem::zeroed() };
327/// let _ = <&UsernameRef as PartialEq>::eq(a, b);
328/// ```
329///
330/// ```compile_fail
331/// let a: &UsernameRef = unsafe { std::mem::zeroed() };
332/// let b: &UsernameRef = unsafe { std::mem::zeroed() };
333/// let _ = <&UsernameRef as PartialEq>::eq(&a, &b);
334/// ```
e0538349
FG
335struct _AssertNoEqImpl;
336
337impl TokennameRef {
338 fn new(s: &str) -> &Self {
339 unsafe { &*(s as *const str as *const TokennameRef) }
340 }
341
342 pub fn as_str(&self) -> &str {
343 &self.0
344 }
345}
346
347impl std::ops::Deref for Tokenname {
348 type Target = TokennameRef;
349
350 fn deref(&self) -> &TokennameRef {
351 self.borrow()
352 }
353}
354
355impl Borrow<TokennameRef> for Tokenname {
356 fn borrow(&self) -> &TokennameRef {
357 TokennameRef::new(self.0.as_str())
358 }
359}
360
361impl AsRef<TokennameRef> for Tokenname {
362 fn as_ref(&self) -> &TokennameRef {
363 self.borrow()
364 }
365}
366
367impl ToOwned for TokennameRef {
368 type Owned = Tokenname;
369
370 fn to_owned(&self) -> Self::Owned {
371 Tokenname(self.0.to_owned())
372 }
373}
374
375impl TryFrom<String> for Tokenname {
376 type Error = Error;
377
378 fn try_from(s: String) -> Result<Self, Error> {
379 if !PROXMOX_TOKEN_NAME_REGEX.is_match(&s) {
380 bail!("invalid token name");
381 }
382
383 Ok(Self(s))
384 }
385}
386
387impl<'a> TryFrom<&'a str> for &'a TokennameRef {
388 type Error = Error;
389
390 fn try_from(s: &'a str) -> Result<&'a TokennameRef, Error> {
391 if !PROXMOX_TOKEN_NAME_REGEX.is_match(s) {
392 bail!("invalid token name in user id");
393 }
394
395 Ok(TokennameRef::new(s))
396 }
397}
398
399/// A complete user id consisting of a user name and a realm
80f950c0 400#[derive(Clone, Debug, PartialEq, Eq, Hash, UpdaterType)]
e7cb4dc5
WB
401pub struct Userid {
402 data: String,
403 name_len: usize,
e7cb4dc5
WB
404}
405
a37c8d24
WB
406impl ApiType for Userid {
407 const API_SCHEMA: Schema = StringSchema::new("User ID")
e7cb4dc5
WB
408 .format(&PROXMOX_USER_ID_FORMAT)
409 .min_length(3)
410 .max_length(64)
411 .schema();
a37c8d24 412}
e7cb4dc5 413
a37c8d24 414impl Userid {
e7cb4dc5
WB
415 const fn new(data: String, name_len: usize) -> Self {
416 Self { data, name_len }
417 }
418
419 pub fn name(&self) -> &UsernameRef {
420 UsernameRef::new(&self.data[..self.name_len])
421 }
422
423 pub fn realm(&self) -> &RealmRef {
424 RealmRef::new(&self.data[(self.name_len + 1)..])
425 }
426
427 pub fn as_str(&self) -> &str {
428 &self.data
429 }
430
e7cb4dc5
WB
431 /// Get the "root@pam" user id.
432 pub fn root_userid() -> &'static Self {
433 &*ROOT_USERID
434 }
435}
436
437lazy_static! {
e7cb4dc5
WB
438 pub static ref ROOT_USERID: Userid = Userid::new("root@pam".to_string(), 4);
439}
440
e4a864bd
WB
441impl From<Authid> for Userid {
442 fn from(authid: Authid) -> Self {
443 authid.user
444 }
445}
446
e7cb4dc5
WB
447impl From<(Username, Realm)> for Userid {
448 fn from(parts: (Username, Realm)) -> Self {
449 Self::from((parts.0.as_ref(), parts.1.as_ref()))
450 }
451}
452
453impl From<(&UsernameRef, &RealmRef)> for Userid {
454 fn from(parts: (&UsernameRef, &RealmRef)) -> Self {
455 let data = format!("{}@{}", parts.0.as_str(), parts.1.as_str());
456 let name_len = parts.0.as_str().len();
457 Self { data, name_len }
458 }
459}
460
461impl fmt::Display for Userid {
462 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
463 self.data.fmt(f)
464 }
465}
466
467impl std::str::FromStr for Userid {
468 type Err = Error;
469
470 fn from_str(id: &str) -> Result<Self, Error> {
e0538349
FG
471 let name_len = id
472 .as_bytes()
473 .iter()
474 .rposition(|&b| b == b'@')
475 .ok_or_else(|| format_err!("not a valid user id"))?;
476
477 let name = &id[..name_len];
478 let realm = &id[(name_len + 1)..];
479
480 if !PROXMOX_USER_NAME_REGEX.is_match(name) {
481 bail!("invalid user name in user id");
482 }
e7cb4dc5
WB
483
484 PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(realm)
485 .map_err(|_| format_err!("invalid realm in user id"))?;
486
487 Ok(Self::from((UsernameRef::new(name), RealmRef::new(realm))))
488 }
489}
490
491impl TryFrom<String> for Userid {
492 type Error = Error;
493
494 fn try_from(data: String) -> Result<Self, Error> {
495 let name_len = data
496 .as_bytes()
497 .iter()
498 .rposition(|&b| b == b'@')
499 .ok_or_else(|| format_err!("not a valid user id"))?;
500
e0538349
FG
501 if !PROXMOX_USER_NAME_REGEX.is_match(&data[..name_len]) {
502 bail!("invalid user name in user id");
503 }
504
e7cb4dc5
WB
505 PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(&data[(name_len + 1)..])
506 .map_err(|_| format_err!("invalid realm in user id"))?;
507
508 Ok(Self { data, name_len })
509 }
510}
511
512impl PartialEq<str> for Userid {
513 fn eq(&self, rhs: &str) -> bool {
0b3dc8ed 514 self.data == *rhs
e7cb4dc5
WB
515 }
516}
517
518impl PartialEq<&str> for Userid {
519 fn eq(&self, rhs: &&str) -> bool {
520 *self == **rhs
521 }
522}
523
524impl PartialEq<String> for Userid {
525 fn eq(&self, rhs: &String) -> bool {
526 self == rhs.as_str()
527 }
528}
529
e0538349 530/// A complete authentication id consisting of a user id and an optional token name.
80f950c0 531#[derive(Clone, Debug, Eq, PartialEq, Hash, UpdaterType)]
e0538349
FG
532pub struct Authid {
533 user: Userid,
534 tokenname: Option<Tokenname>
535}
536
a37c8d24
WB
537impl ApiType for Authid {
538 const API_SCHEMA: Schema = StringSchema::new("Authentication ID")
e0538349
FG
539 .format(&PROXMOX_AUTH_ID_FORMAT)
540 .min_length(3)
541 .max_length(64)
542 .schema();
a37c8d24 543}
e0538349 544
a37c8d24 545impl Authid {
e0538349
FG
546 const fn new(user: Userid, tokenname: Option<Tokenname>) -> Self {
547 Self { user, tokenname }
548 }
549
550 pub fn user(&self) -> &Userid {
551 &self.user
552 }
553
554 pub fn is_token(&self) -> bool {
555 self.tokenname.is_some()
556 }
557
558 pub fn tokenname(&self) -> Option<&TokennameRef> {
559 match &self.tokenname {
560 Some(name) => Some(&name),
561 None => None,
562 }
563 }
564
e0538349
FG
565 /// Get the "root@pam" auth id.
566 pub fn root_auth_id() -> &'static Self {
567 &*ROOT_AUTHID
568 }
569}
570
571lazy_static! {
e0538349
FG
572 pub static ref ROOT_AUTHID: Authid = Authid::from(Userid::new("root@pam".to_string(), 4));
573}
574
e0538349
FG
575impl From<Userid> for Authid {
576 fn from(parts: Userid) -> Self {
577 Self::new(parts, None)
578 }
579}
580
581impl From<(Userid, Option<Tokenname>)> for Authid {
582 fn from(parts: (Userid, Option<Tokenname>)) -> Self {
583 Self::new(parts.0, parts.1)
584 }
585}
586
587impl fmt::Display for Authid {
588 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
589 match &self.tokenname {
590 Some(token) => write!(f, "{}!{}", self.user, token.as_str()),
591 None => self.user.fmt(f),
592 }
593 }
594}
595
596impl std::str::FromStr for Authid {
597 type Err = Error;
598
599 fn from_str(id: &str) -> Result<Self, Error> {
600 let name_len = id
601 .as_bytes()
602 .iter()
603 .rposition(|&b| b == b'@')
604 .ok_or_else(|| format_err!("not a valid user id"))?;
605
606 let realm_end = id
607 .as_bytes()
608 .iter()
609 .rposition(|&b| b == b'!')
610 .map(|pos| if pos < name_len { id.len() } else { pos })
e062ebbc 611 .unwrap_or_else(|| id.len());
e0538349
FG
612
613 if realm_end == id.len() - 1 {
614 bail!("empty token name in userid");
615 }
616
617 let user = Userid::from_str(&id[..realm_end])?;
618
619 if id.len() > realm_end {
620 let token = Tokenname::try_from(id[(realm_end + 1)..].to_string())?;
621 Ok(Self::new(user, Some(token)))
622 } else {
623 Ok(Self::new(user, None))
624 }
625 }
626}
627
628impl TryFrom<String> for Authid {
629 type Error = Error;
630
631 fn try_from(mut data: String) -> Result<Self, Error> {
632 let name_len = data
633 .as_bytes()
634 .iter()
635 .rposition(|&b| b == b'@')
636 .ok_or_else(|| format_err!("not a valid user id"))?;
637
638 let realm_end = data
639 .as_bytes()
640 .iter()
641 .rposition(|&b| b == b'!')
642 .map(|pos| if pos < name_len { data.len() } else { pos })
e062ebbc 643 .unwrap_or_else(|| data.len());
e0538349
FG
644
645 if realm_end == data.len() - 1 {
646 bail!("empty token name in userid");
647 }
648
649 let tokenname = if data.len() > realm_end {
650 Some(Tokenname::try_from(data[(realm_end + 1)..].to_string())?)
651 } else {
652 None
653 };
654
655 data.truncate(realm_end);
656
657 let user:Userid = data.parse()?;
658
659 Ok(Self { user, tokenname })
660 }
661}
662
663#[test]
664fn test_token_id() {
665 let userid: Userid = "test@pam".parse().expect("parsing Userid failed");
666 assert_eq!(userid.name().as_str(), "test");
667 assert_eq!(userid.realm(), "pam");
668 assert_eq!(userid, "test@pam");
669
670 let auth_id: Authid = "test@pam".parse().expect("parsing user Authid failed");
671 assert_eq!(auth_id.to_string(), "test@pam".to_string());
672 assert!(!auth_id.is_token());
673
674 assert_eq!(auth_id.user(), &userid);
675
676 let user_auth_id = Authid::from(userid.clone());
677 assert_eq!(user_auth_id, auth_id);
678 assert!(!user_auth_id.is_token());
679
680 let auth_id: Authid = "test@pam!bar".parse().expect("parsing token Authid failed");
681 let token_userid = auth_id.user();
682 assert_eq!(&userid, token_userid);
683 assert!(auth_id.is_token());
684 assert_eq!(auth_id.tokenname().expect("Token has tokenname").as_str(), TokennameRef::new("bar").as_str());
685 assert_eq!(auth_id.to_string(), "test@pam!bar".to_string());
686}
687
25877d05
DM
688proxmox_serde::forward_deserialize_to_from_str!(Userid);
689proxmox_serde::forward_serialize_to_display!(Userid);
e0538349 690
25877d05
DM
691proxmox_serde::forward_deserialize_to_from_str!(Authid);
692proxmox_serde::forward_serialize_to_display!(Authid);