introduce Username, Realm and Userid api types
and begin splitting up types.rs as it has grown quite large already Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
4
src/api2/types/macros.rs
Normal file
4
src/api2/types/macros.rs
Normal file
@ -0,0 +1,4 @@
|
||||
//! Macros exported from api2::types.
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") }
|
@ -1,5 +1,5 @@
|
||||
use anyhow::{bail};
|
||||
use ::serde::{Deserialize, Serialize};
|
||||
use anyhow::bail;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use proxmox::api::{api, schema::*};
|
||||
use proxmox::const_regex;
|
||||
@ -7,6 +7,16 @@ use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
|
||||
|
||||
use crate::backup::CryptMode;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
#[macro_use]
|
||||
mod userid;
|
||||
pub use userid::{Realm, RealmRef};
|
||||
pub use userid::{Username, UsernameRef};
|
||||
pub use userid::Userid;
|
||||
pub use userid::PROXMOX_GROUP_ID_SCHEMA;
|
||||
|
||||
// File names: may not contain slashes, may not start with "."
|
||||
pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
|
||||
if name.starts_with('.') {
|
||||
@ -21,19 +31,6 @@ pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
|
||||
macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
|
||||
macro_rules! DNS_NAME { () => (concat!(r"(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!())) }
|
||||
|
||||
// we only allow a limited set of characters
|
||||
// colon is not allowed, because we store usernames in
|
||||
// colon separated lists)!
|
||||
// slash is not allowed because it is used as pve API delimiter
|
||||
// also see "man useradd"
|
||||
macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
|
||||
macro_rules! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) }
|
||||
|
||||
macro_rules! USER_ID_REGEX_STR { () => (concat!(USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!())) }
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") }
|
||||
|
||||
macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
|
||||
macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) }
|
||||
|
||||
@ -67,12 +64,8 @@ const_regex!{
|
||||
|
||||
pub DNS_NAME_OR_IP_REGEX = concat!(r"^", DNS_NAME!(), "|", IPRE!(), r"$");
|
||||
|
||||
pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_ID_REGEX_STR!(), r"$");
|
||||
|
||||
pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|", IPRE!() ,"):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$");
|
||||
|
||||
pub PROXMOX_GROUP_ID_REGEX = concat!(r"^", GROUP_NAME_REGEX_STR!(), r"$");
|
||||
|
||||
pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
|
||||
|
||||
pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
|
||||
@ -115,12 +108,6 @@ pub const DNS_NAME_FORMAT: ApiStringFormat =
|
||||
pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
|
||||
ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
|
||||
|
||||
pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
|
||||
ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX);
|
||||
|
||||
pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat =
|
||||
ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX);
|
||||
|
||||
pub const PASSWORD_FORMAT: ApiStringFormat =
|
||||
ApiStringFormat::Pattern(&PASSWORD_REGEX);
|
||||
|
||||
@ -343,24 +330,6 @@ pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP addr
|
||||
.format(&DNS_NAME_OR_IP_FORMAT)
|
||||
.schema();
|
||||
|
||||
pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = StringSchema::new("Authentication domain ID")
|
||||
.format(&PROXMOX_SAFE_ID_FORMAT)
|
||||
.min_length(3)
|
||||
.max_length(32)
|
||||
.schema();
|
||||
|
||||
pub const PROXMOX_USER_ID_SCHEMA: Schema = StringSchema::new("User ID")
|
||||
.format(&PROXMOX_USER_ID_FORMAT)
|
||||
.min_length(3)
|
||||
.max_length(64)
|
||||
.schema();
|
||||
|
||||
pub const PROXMOX_GROUP_ID_SCHEMA: Schema = StringSchema::new("Group ID")
|
||||
.format(&PROXMOX_GROUP_ID_FORMAT)
|
||||
.min_length(3)
|
||||
.max_length(64)
|
||||
.schema();
|
||||
|
||||
pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).")
|
||||
.format(&BLOCKDEVICE_NAME_FORMAT)
|
||||
.min_length(3)
|
||||
@ -388,6 +357,10 @@ pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name
|
||||
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
||||
},
|
||||
},
|
||||
owner: {
|
||||
type: Userid,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -403,7 +376,7 @@ pub struct GroupListItem {
|
||||
pub files: Vec<String>,
|
||||
/// The owner of group
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
pub owner: Option<String>,
|
||||
pub owner: Option<Userid>,
|
||||
}
|
||||
|
||||
#[api(
|
||||
@ -422,6 +395,10 @@ pub struct GroupListItem {
|
||||
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
||||
},
|
||||
},
|
||||
owner: {
|
||||
type: Userid,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -441,7 +418,7 @@ pub struct SnapshotListItem {
|
||||
pub size: Option<u64>,
|
||||
/// The owner of the snapshots group
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
pub owner: Option<String>,
|
||||
pub owner: Option<Userid>,
|
||||
}
|
||||
|
||||
#[api(
|
||||
@ -584,7 +561,8 @@ pub struct StorageStatus {
|
||||
|
||||
#[api(
|
||||
properties: {
|
||||
"upid": { schema: UPID_SCHEMA },
|
||||
upid: { schema: UPID_SCHEMA },
|
||||
user: { type: Userid },
|
||||
},
|
||||
)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -604,7 +582,7 @@ pub struct TaskListItem {
|
||||
/// Worker ID (arbitrary ASCII string)
|
||||
pub worker_id: Option<String>,
|
||||
/// The user who started the task
|
||||
pub user: String,
|
||||
pub user: Userid,
|
||||
/// The task end time (Epoch)
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
pub endtime: Option<i64>,
|
||||
@ -627,7 +605,7 @@ impl From<crate::server::TaskListInfo> for TaskListItem {
|
||||
starttime: info.upid.starttime,
|
||||
worker_type: info.upid.worker_type,
|
||||
worker_id: info.upid.worker_id,
|
||||
user: info.upid.username,
|
||||
user: info.upid.userid,
|
||||
endtime,
|
||||
status,
|
||||
}
|
||||
@ -893,9 +871,6 @@ fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
|
||||
|
||||
#[test]
|
||||
fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
|
||||
|
||||
let schema = PROXMOX_USER_ID_SCHEMA;
|
||||
|
||||
let invalid_user_ids = [
|
||||
"x", // too short
|
||||
"xx", // too short
|
||||
@ -909,7 +884,7 @@ fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
|
||||
];
|
||||
|
||||
for name in invalid_user_ids.iter() {
|
||||
if let Ok(_) = parse_simple_value(name, &schema) {
|
||||
if let Ok(_) = parse_simple_value(name, &Userid::API_SCHEMA) {
|
||||
bail!("test userid '{}' failed - got Ok() while exception an error.", name);
|
||||
}
|
||||
}
|
||||
@ -923,7 +898,7 @@ fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
|
||||
];
|
||||
|
||||
for name in valid_user_ids.iter() {
|
||||
let v = match parse_simple_value(name, &schema) {
|
||||
let v = match parse_simple_value(name, &Userid::API_SCHEMA) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bail!("unable to parse userid '{}' - {}", name, err);
|
||||
|
376
src/api2/types/userid.rs
Normal file
376
src/api2/types/userid.rs
Normal file
@ -0,0 +1,376 @@
|
||||
//! Types for user handling.
|
||||
//!
|
||||
//! We have [`Username`]s and [`Realm`]s. To uniquely identify a user, they must be combined into a [`Userid`].
|
||||
//!
|
||||
//! Since they're all string types, they're organized as follows:
|
||||
//!
|
||||
//! * [`Username`]: an owned user name. Internally a `String`.
|
||||
//! * [`UsernameRef`]: a borrowed user name. Pairs with a `Username` the same way a `str` pairs
|
||||
//! with `String`, meaning you can only make references to it.
|
||||
//! * [`Realm`]: an owned realm (`String` equivalent).
|
||||
//! * [`RealmRef`]: a borrowed realm (`str` equivalent).
|
||||
//! * [`Userid`]: an owned user id (`"user@realm"`). Note that this does not have a separte
|
||||
//! borrowed type.
|
||||
//!
|
||||
//! Note that `Username`s are not unique, therefore they do not implement `Eq` and cannot be
|
||||
//! compared directly. If a direct comparison is really required, they can be compared as strings
|
||||
//! via the `as_str()` method. [`Realm`]s and [`Userid`]s on the other hand can be compared with
|
||||
//! each other, as in those two cases the comparison has meaning.
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use proxmox::api::api;
|
||||
use proxmox::api::schema::{ApiStringFormat, Schema, StringSchema};
|
||||
use proxmox::const_regex;
|
||||
|
||||
// we only allow a limited set of characters
|
||||
// colon is not allowed, because we store usernames in
|
||||
// colon separated lists)!
|
||||
// slash is not allowed because it is used as pve API delimiter
|
||||
// also see "man useradd"
|
||||
macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
|
||||
macro_rules! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) }
|
||||
macro_rules! USER_ID_REGEX_STR { () => (concat!(USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!())) }
|
||||
|
||||
const_regex! {
|
||||
pub PROXMOX_USER_NAME_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"$");
|
||||
pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_ID_REGEX_STR!(), r"$");
|
||||
pub PROXMOX_GROUP_ID_REGEX = concat!(r"^", GROUP_NAME_REGEX_STR!(), r"$");
|
||||
}
|
||||
|
||||
pub const PROXMOX_USER_NAME_FORMAT: ApiStringFormat =
|
||||
ApiStringFormat::Pattern(&PROXMOX_USER_NAME_REGEX);
|
||||
|
||||
pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
|
||||
ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX);
|
||||
|
||||
pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat =
|
||||
ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX);
|
||||
|
||||
pub const PROXMOX_GROUP_ID_SCHEMA: Schema = StringSchema::new("Group ID")
|
||||
.format(&PROXMOX_GROUP_ID_FORMAT)
|
||||
.min_length(3)
|
||||
.max_length(64)
|
||||
.schema();
|
||||
|
||||
pub const PROXMOX_AUTH_REALM_STRING_SCHEMA: StringSchema =
|
||||
StringSchema::new("Authentication domain ID")
|
||||
.format(&super::PROXMOX_SAFE_ID_FORMAT)
|
||||
.min_length(3)
|
||||
.max_length(32);
|
||||
pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = PROXMOX_AUTH_REALM_STRING_SCHEMA.schema();
|
||||
|
||||
|
||||
#[api(
|
||||
type: String,
|
||||
format: &PROXMOX_USER_NAME_FORMAT,
|
||||
)]
|
||||
/// The user name part of a user id.
|
||||
///
|
||||
/// This alone does NOT uniquely identify the user and therefore does not implement `Eq`. In order
|
||||
/// to compare user names directly, they need to be explicitly compared as strings by calling
|
||||
/// `.as_str()`.
|
||||
#[derive(Clone, Debug, Hash, Deserialize, Serialize)]
|
||||
pub struct Username(String);
|
||||
|
||||
/// A reference to a user name part of a user id. This alone does NOT uniquely identify the user.
|
||||
///
|
||||
/// This is like a `str` to the `String` of a [`Username`].
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct UsernameRef(str);
|
||||
|
||||
impl UsernameRef {
|
||||
fn new(s: &str) -> &Self {
|
||||
unsafe { &*(s as *const str as *const UsernameRef) }
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Username {
|
||||
type Target = UsernameRef;
|
||||
|
||||
fn deref(&self) -> &UsernameRef {
|
||||
self.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<UsernameRef> for Username {
|
||||
fn borrow(&self) -> &UsernameRef {
|
||||
UsernameRef::new(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<UsernameRef> for Username {
|
||||
fn as_ref(&self) -> &UsernameRef {
|
||||
UsernameRef::new(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOwned for UsernameRef {
|
||||
type Owned = Username;
|
||||
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
Username(self.0.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Username {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Error> {
|
||||
if !PROXMOX_USER_NAME_REGEX.is_match(&s) {
|
||||
bail!("invalid user name");
|
||||
}
|
||||
|
||||
Ok(Self(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for &'a UsernameRef {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(s: &'a str) -> Result<&'a UsernameRef, Error> {
|
||||
if !PROXMOX_USER_NAME_REGEX.is_match(s) {
|
||||
bail!("invalid name in user id");
|
||||
}
|
||||
|
||||
Ok(UsernameRef::new(s))
|
||||
}
|
||||
}
|
||||
|
||||
#[api(schema: PROXMOX_AUTH_REALM_SCHEMA)]
|
||||
/// An authentication realm.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
|
||||
pub struct Realm(String);
|
||||
|
||||
/// A reference to an authentication realm.
|
||||
///
|
||||
/// This is like a `str` to the `String` of a `Realm`.
|
||||
#[derive(Debug, Hash, Eq, PartialEq)]
|
||||
pub struct RealmRef(str);
|
||||
|
||||
impl RealmRef {
|
||||
fn new(s: &str) -> &Self {
|
||||
unsafe { &*(s as *const str as *const RealmRef) }
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Realm {
|
||||
type Target = RealmRef;
|
||||
|
||||
fn deref(&self) -> &RealmRef {
|
||||
self.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<RealmRef> for Realm {
|
||||
fn borrow(&self) -> &RealmRef {
|
||||
RealmRef::new(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<RealmRef> for Realm {
|
||||
fn as_ref(&self) -> &RealmRef {
|
||||
RealmRef::new(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOwned for RealmRef {
|
||||
type Owned = Realm;
|
||||
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
Realm(self.0.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Realm {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Error> {
|
||||
PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(&s)
|
||||
.map_err(|_| format_err!("invalid realm"))?;
|
||||
|
||||
Ok(Self(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for &'a RealmRef {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(s: &'a str) -> Result<&'a RealmRef, Error> {
|
||||
PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(s)
|
||||
.map_err(|_| format_err!("invalid realm"))?;
|
||||
|
||||
Ok(RealmRef::new(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for Realm {
|
||||
fn eq(&self, rhs: &str) -> bool {
|
||||
self.0 == rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for Realm {
|
||||
fn eq(&self, rhs: &&str) -> bool {
|
||||
self.0 == *rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for RealmRef {
|
||||
fn eq(&self, rhs: &str) -> bool {
|
||||
self.0 == *rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for RealmRef {
|
||||
fn eq(&self, rhs: &&str) -> bool {
|
||||
self.0 == **rhs
|
||||
}
|
||||
}
|
||||
|
||||
/// A complete user id consting of a user name and a realm.
|
||||
#[derive(Clone, Debug, Hash)]
|
||||
pub struct Userid {
|
||||
data: String,
|
||||
name_len: usize,
|
||||
//name: Username,
|
||||
//realm: Realm,
|
||||
}
|
||||
|
||||
impl Userid {
|
||||
pub const API_SCHEMA: Schema = StringSchema::new("User ID")
|
||||
.format(&PROXMOX_USER_ID_FORMAT)
|
||||
.min_length(3)
|
||||
.max_length(64)
|
||||
.schema();
|
||||
|
||||
const fn new(data: String, name_len: usize) -> Self {
|
||||
Self { data, name_len }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &UsernameRef {
|
||||
UsernameRef::new(&self.data[..self.name_len])
|
||||
}
|
||||
|
||||
pub fn realm(&self) -> &RealmRef {
|
||||
RealmRef::new(&self.data[(self.name_len + 1)..])
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Get the "backup@pam" user id.
|
||||
pub fn backup_userid() -> &'static Self {
|
||||
&*BACKUP_USERID
|
||||
}
|
||||
|
||||
/// Get the "root@pam" user id.
|
||||
pub fn root_userid() -> &'static Self {
|
||||
&*ROOT_USERID
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref BACKUP_USERID: Userid = Userid::new("backup@pam".to_string(), 6);
|
||||
pub static ref ROOT_USERID: Userid = Userid::new("root@pam".to_string(), 4);
|
||||
}
|
||||
|
||||
impl Eq for Userid {}
|
||||
|
||||
impl PartialEq for Userid {
|
||||
fn eq(&self, rhs: &Self) -> bool {
|
||||
self.data == rhs.data && self.name_len == rhs.name_len
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Username, Realm)> for Userid {
|
||||
fn from(parts: (Username, Realm)) -> Self {
|
||||
Self::from((parts.0.as_ref(), parts.1.as_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&UsernameRef, &RealmRef)> for Userid {
|
||||
fn from(parts: (&UsernameRef, &RealmRef)) -> Self {
|
||||
let data = format!("{}@{}", parts.0.as_str(), parts.1.as_str());
|
||||
let name_len = parts.0.as_str().len();
|
||||
Self { data, name_len }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Userid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.data.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Userid {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(id: &str) -> Result<Self, Error> {
|
||||
let (name, realm) = match id.as_bytes().iter().rposition(|&b| b == b'@') {
|
||||
Some(pos) => (&id[..pos], &id[(pos + 1)..]),
|
||||
None => bail!("not a valid user id"),
|
||||
};
|
||||
|
||||
PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(realm)
|
||||
.map_err(|_| format_err!("invalid realm in user id"))?;
|
||||
|
||||
Ok(Self::from((UsernameRef::new(name), RealmRef::new(realm))))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Userid {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(data: String) -> Result<Self, Error> {
|
||||
let name_len = data
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.rposition(|&b| b == b'@')
|
||||
.ok_or_else(|| format_err!("not a valid user id"))?;
|
||||
|
||||
PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(&data[(name_len + 1)..])
|
||||
.map_err(|_| format_err!("invalid realm in user id"))?;
|
||||
|
||||
Ok(Self { data, name_len })
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for Userid {
|
||||
fn eq(&self, rhs: &str) -> bool {
|
||||
rhs.len() > self.name_len + 2 // make sure range access below is allowed
|
||||
&& rhs.starts_with(self.name().as_str())
|
||||
&& rhs.as_bytes()[self.name_len] == b'@'
|
||||
&& &rhs[(self.name_len + 1)..] == self.realm().as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for Userid {
|
||||
fn eq(&self, rhs: &&str) -> bool {
|
||||
*self == **rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for Userid {
|
||||
fn eq(&self, rhs: &String) -> bool {
|
||||
self == rhs.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
proxmox::forward_deserialize_to_from_str!(Userid);
|
||||
proxmox::forward_serialize_to_display!(Userid);
|
Reference in New Issue
Block a user