1 //! Types for user handling.
3 //! We have [`Username`]s and [`Realm`]s. To uniquely identify a user, they must be combined into a [`Userid`].
5 //! Since they're all string types, they're organized as follows:
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
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.
20 use std
::borrow
::Borrow
;
21 use std
::convert
::TryFrom
;
24 use anyhow
::{bail, format_err, Error}
;
25 use lazy_static
::lazy_static
;
26 use serde
::{Deserialize, Serialize}
;
28 use proxmox
::api
::api
;
29 use proxmox
::api
::schema
::{ApiStringFormat, Schema, StringSchema}
;
30 use proxmox
::const_regex
;
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!())) }
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
"$");
47 pub const PROXMOX_USER_NAME_FORMAT
: ApiStringFormat
=
48 ApiStringFormat
::Pattern(&PROXMOX_USER_NAME_REGEX
);
50 pub const PROXMOX_USER_ID_FORMAT
: ApiStringFormat
=
51 ApiStringFormat
::Pattern(&PROXMOX_USER_ID_REGEX
);
53 pub const PROXMOX_GROUP_ID_FORMAT
: ApiStringFormat
=
54 ApiStringFormat
::Pattern(&PROXMOX_GROUP_ID_REGEX
);
56 pub const PROXMOX_GROUP_ID_SCHEMA
: Schema
= StringSchema
::new("Group ID")
57 .format(&PROXMOX_GROUP_ID_FORMAT
)
62 pub const PROXMOX_AUTH_REALM_STRING_SCHEMA
: StringSchema
=
63 StringSchema
::new("Authentication domain ID")
64 .format(&super::PROXMOX_SAFE_ID_FORMAT
)
67 pub const PROXMOX_AUTH_REALM_SCHEMA
: Schema
= PROXMOX_AUTH_REALM_STRING_SCHEMA
.schema();
72 format
: &PROXMOX_USER_NAME_FORMAT
,
74 /// The user name part of a user id.
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
81 /// fn test(a: Username, b: Username) -> bool {
82 /// a == b // illegal and does not compile
85 #[derive(Clone, Debug, Hash, Deserialize, Serialize)]
86 pub struct Username(String
);
88 /// A reference to a user name part of a user id. This alone does NOT uniquely identify the user.
90 /// This is like a `str` to the `String` of a [`Username`].
91 #[derive(Debug, Hash)]
92 pub struct UsernameRef(str);
96 /// let a: Username = unsafe { std::mem::zeroed() };
97 /// let b: Username = unsafe { std::mem::zeroed() };
98 /// let _ = <Username as PartialEq>::eq(&a, &b);
102 /// let a: &UsernameRef = unsafe { std::mem::zeroed() };
103 /// let b: &UsernameRef = unsafe { std::mem::zeroed() };
104 /// let _ = <&UsernameRef as PartialEq>::eq(a, b);
108 /// let a: &UsernameRef = unsafe { std::mem::zeroed() };
109 /// let b: &UsernameRef = unsafe { std::mem::zeroed() };
110 /// let _ = <&UsernameRef as PartialEq>::eq(&a, &b);
112 struct _AssertNoEqImpl
;
115 fn new(s
: &str) -> &Self {
116 unsafe { &*(s as *const str as *const UsernameRef) }
119 pub fn as_str(&self) -> &str {
124 impl std
::ops
::Deref
for Username
{
125 type Target
= UsernameRef
;
127 fn deref(&self) -> &UsernameRef
{
132 impl Borrow
<UsernameRef
> for Username
{
133 fn borrow(&self) -> &UsernameRef
{
134 UsernameRef
::new(self.as_str())
138 impl AsRef
<UsernameRef
> for Username
{
139 fn as_ref(&self) -> &UsernameRef
{
140 UsernameRef
::new(self.as_str())
144 impl ToOwned
for UsernameRef
{
145 type Owned
= Username
;
147 fn to_owned(&self) -> Self::Owned
{
148 Username(self.0.to_owned())
152 impl TryFrom
<String
> for Username
{
155 fn try_from(s
: String
) -> Result
<Self, Error
> {
156 if !PROXMOX_USER_NAME_REGEX
.is_match(&s
) {
157 bail
!("invalid user name");
164 impl<'a
> TryFrom
<&'a
str> for &'a UsernameRef
{
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");
172 Ok(UsernameRef
::new(s
))
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
);
181 /// A reference to an authentication realm.
183 /// This is like a `str` to the `String` of a `Realm`.
184 #[derive(Debug, Hash, Eq, PartialEq)]
185 pub struct RealmRef(str);
188 fn new(s
: &str) -> &Self {
189 unsafe { &*(s as *const str as *const RealmRef) }
192 pub fn as_str(&self) -> &str {
197 impl std
::ops
::Deref
for Realm
{
198 type Target
= RealmRef
;
200 fn deref(&self) -> &RealmRef
{
205 impl Borrow
<RealmRef
> for Realm
{
206 fn borrow(&self) -> &RealmRef
{
207 RealmRef
::new(self.as_str())
211 impl AsRef
<RealmRef
> for Realm
{
212 fn as_ref(&self) -> &RealmRef
{
213 RealmRef
::new(self.as_str())
217 impl ToOwned
for RealmRef
{
220 fn to_owned(&self) -> Self::Owned
{
221 Realm(self.0.to_owned())
225 impl TryFrom
<String
> for Realm
{
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"))?
;
236 impl<'a
> TryFrom
<&'a
str> for &'a RealmRef
{
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"))?
;
247 impl PartialEq
<str> for Realm
{
248 fn eq(&self, rhs
: &str) -> bool
{
253 impl PartialEq
<&str> for Realm
{
254 fn eq(&self, rhs
: &&str) -> bool
{
259 impl PartialEq
<str> for RealmRef
{
260 fn eq(&self, rhs
: &str) -> bool
{
265 impl PartialEq
<&str> for RealmRef
{
266 fn eq(&self, rhs
: &&str) -> bool
{
271 impl PartialEq
<RealmRef
> for Realm
{
272 fn eq(&self, rhs
: &RealmRef
) -> bool
{
277 impl PartialEq
<Realm
> for RealmRef
{
278 fn eq(&self, rhs
: &Realm
) -> bool
{
283 impl PartialEq
<Realm
> for &RealmRef
{
284 fn eq(&self, rhs
: &Realm
) -> bool
{
289 /// A complete user id consting of a user name and a realm.
290 #[derive(Clone, Debug, Hash)]
299 pub const API_SCHEMA
: Schema
= StringSchema
::new("User ID")
300 .format(&PROXMOX_USER_ID_FORMAT
)
305 const fn new(data
: String
, name_len
: usize) -> Self {
306 Self { data, name_len }
309 pub fn name(&self) -> &UsernameRef
{
310 UsernameRef
::new(&self.data
[..self.name_len
])
313 pub fn realm(&self) -> &RealmRef
{
314 RealmRef
::new(&self.data
[(self.name_len
+ 1)..])
317 pub fn as_str(&self) -> &str {
321 /// Get the "backup@pam" user id.
322 pub fn backup_userid() -> &'
static Self {
326 /// Get the "root@pam" user id.
327 pub fn root_userid() -> &'
static Self {
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);
337 impl Eq
for Userid {}
339 impl PartialEq
for Userid
{
340 fn eq(&self, rhs
: &Self) -> bool
{
341 self.data
== rhs
.data
&& self.name_len
== rhs
.name_len
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()))
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 }
359 impl fmt
::Display
for Userid
{
360 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
365 impl std
::str::FromStr
for Userid
{
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"),
374 PROXMOX_AUTH_REALM_STRING_SCHEMA
.check_constraints(realm
)
375 .map_err(|_
| format_err
!("invalid realm in user id"))?
;
377 Ok(Self::from((UsernameRef
::new(name
), RealmRef
::new(realm
))))
381 impl TryFrom
<String
> for Userid
{
384 fn try_from(data
: String
) -> Result
<Self, Error
> {
388 .rposition(|&b
| b
== b'@'
)
389 .ok_or_else(|| format_err
!("not a valid user id"))?
;
391 PROXMOX_AUTH_REALM_STRING_SCHEMA
.check_constraints(&data
[(name_len
+ 1)..])
392 .map_err(|_
| format_err
!("invalid realm in user id"))?
;
394 Ok(Self { data, name_len }
)
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()
407 impl PartialEq
<&str> for Userid
{
408 fn eq(&self, rhs
: &&str) -> bool
{
413 impl PartialEq
<String
> for Userid
{
414 fn eq(&self, rhs
: &String
) -> bool
{
419 proxmox
::forward_deserialize_to_from_str
!(Userid
);
420 proxmox
::forward_serialize_to_display
!(Userid
);