1 //! Types for user handling.
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`].
6 //! Since they're all string types, they're organized as follows:
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).
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.
19 //! Note that `Username`s are not unique, therefore they do not implement `Eq` and cannot be
20 //! compared directly. If a direct comparison is really required, they can be compared as strings
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.
24 use std
::borrow
::Borrow
;
25 use std
::convert
::TryFrom
;
28 use anyhow
::{bail, format_err, Error}
;
29 use lazy_static
::lazy_static
;
30 use serde
::{Deserialize, Serialize}
;
33 api
, const_regex
, ApiStringFormat
, ApiType
, Schema
, StringSchema
, UpdaterType
,
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"
42 macro_rules
! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
44 macro_rules
! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) }
46 macro_rules
! TOKEN_NAME_REGEX_STR { () => (PROXMOX_SAFE_ID_REGEX_STR!()) }
48 macro_rules
! USER_ID_REGEX_STR { () => (concat!(USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!())) }
50 macro_rules
! APITOKEN_ID_REGEX_STR { () => (concat!(USER_ID_REGEX_STR!() , r"!", TOKEN_NAME_REGEX_STR!())) }
53 pub PROXMOX_USER_NAME_REGEX
= concat
!(r
"^", USER_NAME_REGEX_STR
!(), r
"$");
54 pub PROXMOX_TOKEN_NAME_REGEX
= concat
!(r
"^", TOKEN_NAME_REGEX_STR
!(), r
"$");
55 pub PROXMOX_USER_ID_REGEX
= concat
!(r
"^", USER_ID_REGEX_STR
!(), r
"$");
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
")$");
58 pub PROXMOX_GROUP_ID_REGEX
= concat
!(r
"^", GROUP_NAME_REGEX_STR
!(), r
"$");
61 pub const PROXMOX_USER_NAME_FORMAT
: ApiStringFormat
=
62 ApiStringFormat
::Pattern(&PROXMOX_USER_NAME_REGEX
);
63 pub const PROXMOX_TOKEN_NAME_FORMAT
: ApiStringFormat
=
64 ApiStringFormat
::Pattern(&PROXMOX_TOKEN_NAME_REGEX
);
66 pub const PROXMOX_USER_ID_FORMAT
: ApiStringFormat
=
67 ApiStringFormat
::Pattern(&PROXMOX_USER_ID_REGEX
);
68 pub const PROXMOX_TOKEN_ID_FORMAT
: ApiStringFormat
=
69 ApiStringFormat
::Pattern(&PROXMOX_APITOKEN_ID_REGEX
);
70 pub const PROXMOX_AUTH_ID_FORMAT
: ApiStringFormat
=
71 ApiStringFormat
::Pattern(&PROXMOX_AUTH_ID_REGEX
);
73 pub const PROXMOX_TOKEN_ID_SCHEMA
: Schema
= StringSchema
::new("API Token ID")
74 .format(&PROXMOX_TOKEN_ID_FORMAT
)
79 pub const PROXMOX_TOKEN_NAME_SCHEMA
: Schema
= StringSchema
::new("API Token name")
80 .format(&PROXMOX_TOKEN_NAME_FORMAT
)
85 pub const PROXMOX_GROUP_ID_FORMAT
: ApiStringFormat
=
86 ApiStringFormat
::Pattern(&PROXMOX_GROUP_ID_REGEX
);
88 pub const PROXMOX_GROUP_ID_SCHEMA
: Schema
= StringSchema
::new("Group ID")
89 .format(&PROXMOX_GROUP_ID_FORMAT
)
94 pub const PROXMOX_AUTH_REALM_STRING_SCHEMA
: StringSchema
=
95 StringSchema
::new("Authentication domain ID")
96 .format(&super::PROXMOX_SAFE_ID_FORMAT
)
99 pub const PROXMOX_AUTH_REALM_SCHEMA
: Schema
= PROXMOX_AUTH_REALM_STRING_SCHEMA
.schema();
103 format
: &PROXMOX_USER_NAME_FORMAT
,
105 /// The user name part of a user id.
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
112 /// fn test(a: Username, b: Username) -> bool {
113 /// a == b // illegal and does not compile
116 #[derive(Clone, Debug, Hash, Deserialize, Serialize)]
117 pub struct Username(String
);
119 /// A reference to a user name part of a user id. This alone does NOT uniquely identify the user.
121 /// This is like a `str` to the `String` of a [`Username`].
122 #[derive(Debug, Hash)]
123 pub struct UsernameRef(str);
126 fn new(s
: &str) -> &Self {
127 unsafe { &*(s as *const str as *const UsernameRef) }
130 pub fn as_str(&self) -> &str {
135 impl std
::ops
::Deref
for Username
{
136 type Target
= UsernameRef
;
138 fn deref(&self) -> &UsernameRef
{
143 impl Borrow
<UsernameRef
> for Username
{
144 fn borrow(&self) -> &UsernameRef
{
145 UsernameRef
::new(self.0.as_str())
149 impl AsRef
<UsernameRef
> for Username
{
150 fn as_ref(&self) -> &UsernameRef
{
155 impl ToOwned
for UsernameRef
{
156 type Owned
= Username
;
158 fn to_owned(&self) -> Self::Owned
{
159 Username(self.0.to_owned())
163 impl TryFrom
<String
> for Username
{
166 fn try_from(s
: String
) -> Result
<Self, Error
> {
167 if !PROXMOX_USER_NAME_REGEX
.is_match(&s
) {
168 bail
!("invalid user name");
175 impl<'a
> TryFrom
<&'a
str> for &'a UsernameRef
{
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");
183 Ok(UsernameRef
::new(s
))
187 #[api(schema: PROXMOX_AUTH_REALM_SCHEMA)]
188 /// An authentication realm.
189 #[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
190 pub struct Realm(String
);
192 /// A reference to an authentication realm.
194 /// This is like a `str` to the `String` of a `Realm`.
195 #[derive(Debug, Hash, Eq, PartialEq)]
196 pub struct RealmRef(str);
199 fn new(s
: &str) -> &Self {
200 unsafe { &*(s as *const str as *const RealmRef) }
203 pub fn as_str(&self) -> &str {
208 impl std
::ops
::Deref
for Realm
{
209 type Target
= RealmRef
;
211 fn deref(&self) -> &RealmRef
{
216 impl Borrow
<RealmRef
> for Realm
{
217 fn borrow(&self) -> &RealmRef
{
218 RealmRef
::new(self.0.as_str())
222 impl AsRef
<RealmRef
> for Realm
{
223 fn as_ref(&self) -> &RealmRef
{
228 impl ToOwned
for RealmRef
{
231 fn to_owned(&self) -> Self::Owned
{
232 Realm(self.0.to_owned())
236 impl TryFrom
<String
> for Realm
{
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"))?
;
247 impl<'a
> TryFrom
<&'a
str> for &'a RealmRef
{
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"))?
;
258 impl PartialEq
<str> for Realm
{
259 fn eq(&self, rhs
: &str) -> bool
{
264 impl PartialEq
<&str> for Realm
{
265 fn eq(&self, rhs
: &&str) -> bool
{
270 impl PartialEq
<str> for RealmRef
{
271 fn eq(&self, rhs
: &str) -> bool
{
276 impl PartialEq
<&str> for RealmRef
{
277 fn eq(&self, rhs
: &&str) -> bool
{
282 impl PartialEq
<RealmRef
> for Realm
{
283 fn eq(&self, rhs
: &RealmRef
) -> bool
{
288 impl PartialEq
<Realm
> for RealmRef
{
289 fn eq(&self, rhs
: &Realm
) -> bool
{
294 impl PartialEq
<Realm
> for &RealmRef
{
295 fn eq(&self, rhs
: &Realm
) -> bool
{
302 format
: &PROXMOX_TOKEN_NAME_FORMAT
,
304 /// The token ID part of an API token authentication id.
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)]
308 pub struct Tokenname(String
);
310 /// A reference to a token name part of an authentication id. This alone does NOT uniquely identify
313 /// This is like a `str` to the `String` of a [`Tokenname`].
314 #[derive(Debug, Hash)]
315 pub struct TokennameRef(str);
319 /// let a: Username = unsafe { std::mem::zeroed() };
320 /// let b: Username = unsafe { std::mem::zeroed() };
321 /// let _ = <Username as PartialEq>::eq(&a, &b);
325 /// let a: &UsernameRef = unsafe { std::mem::zeroed() };
326 /// let b: &UsernameRef = unsafe { std::mem::zeroed() };
327 /// let _ = <&UsernameRef as PartialEq>::eq(a, b);
331 /// let a: &UsernameRef = unsafe { std::mem::zeroed() };
332 /// let b: &UsernameRef = unsafe { std::mem::zeroed() };
333 /// let _ = <&UsernameRef as PartialEq>::eq(&a, &b);
335 struct _AssertNoEqImpl
;
338 fn new(s
: &str) -> &Self {
339 unsafe { &*(s as *const str as *const TokennameRef) }
342 pub fn as_str(&self) -> &str {
347 impl std
::ops
::Deref
for Tokenname
{
348 type Target
= TokennameRef
;
350 fn deref(&self) -> &TokennameRef
{
355 impl Borrow
<TokennameRef
> for Tokenname
{
356 fn borrow(&self) -> &TokennameRef
{
357 TokennameRef
::new(self.0.as_str())
361 impl AsRef
<TokennameRef
> for Tokenname
{
362 fn as_ref(&self) -> &TokennameRef
{
367 impl ToOwned
for TokennameRef
{
368 type Owned
= Tokenname
;
370 fn to_owned(&self) -> Self::Owned
{
371 Tokenname(self.0.to_owned())
375 impl TryFrom
<String
> for Tokenname
{
378 fn try_from(s
: String
) -> Result
<Self, Error
> {
379 if !PROXMOX_TOKEN_NAME_REGEX
.is_match(&s
) {
380 bail
!("invalid token name");
387 impl<'a
> TryFrom
<&'a
str> for &'a TokennameRef
{
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");
395 Ok(TokennameRef
::new(s
))
399 /// A complete user id consisting of a user name and a realm
400 #[derive(Clone, Debug, PartialEq, Eq, Hash, UpdaterType)]
406 impl ApiType
for Userid
{
407 const API_SCHEMA
: Schema
= StringSchema
::new("User ID")
408 .format(&PROXMOX_USER_ID_FORMAT
)
415 const fn new(data
: String
, name_len
: usize) -> Self {
416 Self { data, name_len }
419 pub fn name(&self) -> &UsernameRef
{
420 UsernameRef
::new(&self.data
[..self.name_len
])
423 pub fn realm(&self) -> &RealmRef
{
424 RealmRef
::new(&self.data
[(self.name_len
+ 1)..])
427 pub fn as_str(&self) -> &str {
431 /// Get the "root@pam" user id.
432 pub fn root_userid() -> &'
static Self {
438 pub static ref ROOT_USERID
: Userid
= Userid
::new("root@pam".to_string(), 4);
441 impl From
<Authid
> for Userid
{
442 fn from(authid
: Authid
) -> Self {
447 impl From
<(Username
, Realm
)> for Userid
{
448 fn from(parts
: (Username
, Realm
)) -> Self {
449 Self::from((parts
.0.as_ref(), parts
.1.as_ref()))
453 impl 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 }
461 impl fmt
::Display
for Userid
{
462 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
467 impl std
::str::FromStr
for Userid
{
470 fn from_str(id
: &str) -> Result
<Self, Error
> {
474 .rposition(|&b
| b
== b'@'
)
475 .ok_or_else(|| format_err
!("not a valid user id"))?
;
477 let name
= &id
[..name_len
];
478 let realm
= &id
[(name_len
+ 1)..];
480 if !PROXMOX_USER_NAME_REGEX
.is_match(name
) {
481 bail
!("invalid user name in user id");
484 PROXMOX_AUTH_REALM_STRING_SCHEMA
.check_constraints(realm
)
485 .map_err(|_
| format_err
!("invalid realm in user id"))?
;
487 Ok(Self::from((UsernameRef
::new(name
), RealmRef
::new(realm
))))
491 impl TryFrom
<String
> for Userid
{
494 fn try_from(data
: String
) -> Result
<Self, Error
> {
498 .rposition(|&b
| b
== b'@'
)
499 .ok_or_else(|| format_err
!("not a valid user id"))?
;
501 if !PROXMOX_USER_NAME_REGEX
.is_match(&data
[..name_len
]) {
502 bail
!("invalid user name in user id");
505 PROXMOX_AUTH_REALM_STRING_SCHEMA
.check_constraints(&data
[(name_len
+ 1)..])
506 .map_err(|_
| format_err
!("invalid realm in user id"))?
;
508 Ok(Self { data, name_len }
)
512 impl PartialEq
<str> for Userid
{
513 fn eq(&self, rhs
: &str) -> bool
{
518 impl PartialEq
<&str> for Userid
{
519 fn eq(&self, rhs
: &&str) -> bool
{
524 impl PartialEq
<String
> for Userid
{
525 fn eq(&self, rhs
: &String
) -> bool
{
530 /// A complete authentication id consisting of a user id and an optional token name.
531 #[derive(Clone, Debug, Eq, PartialEq, Hash, UpdaterType)]
534 tokenname
: Option
<Tokenname
>
537 impl ApiType
for Authid
{
538 const API_SCHEMA
: Schema
= StringSchema
::new("Authentication ID")
539 .format(&PROXMOX_AUTH_ID_FORMAT
)
546 const fn new(user
: Userid
, tokenname
: Option
<Tokenname
>) -> Self {
547 Self { user, tokenname }
550 pub fn user(&self) -> &Userid
{
554 pub fn is_token(&self) -> bool
{
555 self.tokenname
.is_some()
558 pub fn tokenname(&self) -> Option
<&TokennameRef
> {
559 match &self.tokenname
{
560 Some(name
) => Some(&name
),
565 /// Get the "root@pam" auth id.
566 pub fn root_auth_id() -> &'
static Self {
572 pub static ref ROOT_AUTHID
: Authid
= Authid
::from(Userid
::new("root@pam".to_string(), 4));
575 impl From
<Userid
> for Authid
{
576 fn from(parts
: Userid
) -> Self {
577 Self::new(parts
, None
)
581 impl From
<(Userid
, Option
<Tokenname
>)> for Authid
{
582 fn from(parts
: (Userid
, Option
<Tokenname
>)) -> Self {
583 Self::new(parts
.0, parts
.1)
587 impl 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
),
596 impl std
::str::FromStr
for Authid
{
599 fn from_str(id
: &str) -> Result
<Self, Error
> {
603 .rposition(|&b
| b
== b'@'
)
604 .ok_or_else(|| format_err
!("not a valid user id"))?
;
609 .rposition(|&b
| b
== b'
!'
)
610 .map(|pos
| if pos
< name_len { id.len() }
else { pos }
)
611 .unwrap_or_else(|| id
.len());
613 if realm_end
== id
.len() - 1 {
614 bail
!("empty token name in userid");
617 let user
= Userid
::from_str(&id
[..realm_end
])?
;
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
)))
623 Ok(Self::new(user
, None
))
628 impl TryFrom
<String
> for Authid
{
631 fn try_from(mut data
: String
) -> Result
<Self, Error
> {
635 .rposition(|&b
| b
== b'@'
)
636 .ok_or_else(|| format_err
!("not a valid user id"))?
;
641 .rposition(|&b
| b
== b'
!'
)
642 .map(|pos
| if pos
< name_len { data.len() }
else { pos }
)
643 .unwrap_or_else(|| data
.len());
645 if realm_end
== data
.len() - 1 {
646 bail
!("empty token name in userid");
649 let tokenname
= if data
.len() > realm_end
{
650 Some(Tokenname
::try_from(data
[(realm_end
+ 1)..].to_string())?
)
655 data
.truncate(realm_end
);
657 let user
:Userid
= data
.parse()?
;
659 Ok(Self { user, tokenname }
)
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");
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());
674 assert_eq
!(auth_id
.user(), &userid
);
676 let user_auth_id
= Authid
::from(userid
.clone());
677 assert_eq
!(user_auth_id
, auth_id
);
678 assert
!(!user_auth_id
.is_token());
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());
688 proxmox
::forward_deserialize_to_from_str
!(Userid
);
689 proxmox
::forward_serialize_to_display
!(Userid
);
691 proxmox
::forward_deserialize_to_from_str
!(Authid
);
692 proxmox
::forward_serialize_to_display
!(Authid
);