From: "Fabian Grünbichler" <f.gruenbichler@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [RFC proxmox-backup 04/15] Userid: extend schema with token name
Date: Mon, 19 Oct 2020 09:39:08 +0200 [thread overview]
Message-ID: <20201019073919.588521-5-f.gruenbichler@proxmox.com> (raw)
In-Reply-To: <20201019073919.588521-1-f.gruenbichler@proxmox.com>
similar to PVE, allow adding a !TOKENNAME suffix for API tokens
belonging to a specifc user.
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
Notes:
not too happy with the schema names here, suggestion welcome
src/api2/access.rs | 4 +-
src/api2/access/acl.rs | 2 +-
src/api2/access/user.rs | 8 +-
src/api2/admin/datastore.rs | 2 +-
src/api2/config/remote.rs | 4 +-
src/api2/types/mod.rs | 13 +-
src/api2/types/userid.rs | 367 +++++++++++++++++++++++++++----
src/bin/proxmox-backup-client.rs | 2 +-
src/config/remote.rs | 2 +-
src/config/user.rs | 4 +-
10 files changed, 343 insertions(+), 65 deletions(-)
diff --git a/src/api2/access.rs b/src/api2/access.rs
index c302e0c7..0c19dab6 100644
--- a/src/api2/access.rs
+++ b/src/api2/access.rs
@@ -87,7 +87,7 @@ fn authenticate_user(
input: {
properties: {
username: {
- type: Userid,
+ schema: PROXMOX_USER_ID_SCHEMA,
},
password: {
schema: PASSWORD_SCHEMA,
@@ -189,7 +189,7 @@ fn create_ticket(
input: {
properties: {
userid: {
- type: Userid,
+ schema: PROXMOX_USER_ID_SCHEMA,
},
password: {
schema: PASSWORD_SCHEMA,
diff --git a/src/api2/access/acl.rs b/src/api2/access/acl.rs
index 3282c66e..cf9671c9 100644
--- a/src/api2/access/acl.rs
+++ b/src/api2/access/acl.rs
@@ -142,7 +142,7 @@ pub fn read_acl(
},
userid: {
optional: true,
- type: Userid,
+ schema: PROXMOX_USER_OR_TOKEN_ID_SCHEMA,
},
group: {
optional: true,
diff --git a/src/api2/access/user.rs b/src/api2/access/user.rs
index c041d804..6c292c2d 100644
--- a/src/api2/access/user.rs
+++ b/src/api2/access/user.rs
@@ -61,7 +61,7 @@ pub fn list_users(
input: {
properties: {
userid: {
- type: Userid,
+ schema: PROXMOX_USER_ID_SCHEMA,
},
comment: {
schema: SINGLE_LINE_COMMENT_SCHEMA,
@@ -127,7 +127,7 @@ pub fn create_user(password: Option<String>, param: Value) -> Result<(), Error>
input: {
properties: {
userid: {
- type: Userid,
+ schema: PROXMOX_USER_ID_SCHEMA,
},
},
},
@@ -155,7 +155,7 @@ pub fn read_user(userid: Userid, mut rpcenv: &mut dyn RpcEnvironment) -> Result<
input: {
properties: {
userid: {
- type: Userid,
+ schema: PROXMOX_USER_ID_SCHEMA,
},
comment: {
optional: true,
@@ -267,7 +267,7 @@ pub fn update_user(
input: {
properties: {
userid: {
- type: Userid,
+ schema: PROXMOX_USER_ID_SCHEMA,
},
digest: {
optional: true,
diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index 75e6d32b..5c9902e1 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -1501,7 +1501,7 @@ fn set_notes(
schema: BACKUP_ID_SCHEMA,
},
"new-owner": {
- type: Userid,
+ schema: PROXMOX_USER_OR_TOKEN_ID_SCHEMA,
},
},
},
diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs
index d419be2b..00a5de73 100644
--- a/src/api2/config/remote.rs
+++ b/src/api2/config/remote.rs
@@ -66,7 +66,7 @@ pub fn list_remotes(
default: 8007,
},
userid: {
- type: Userid,
+ schema: PROXMOX_USER_OR_TOKEN_ID_SCHEMA,
},
password: {
schema: remote::REMOTE_PASSWORD_SCHEMA,
@@ -167,7 +167,7 @@ pub enum DeletableProperty {
},
userid: {
optional: true,
- type: Userid,
+ schema: PROXMOX_USER_OR_TOKEN_ID_SCHEMA,
},
password: {
optional: true,
diff --git a/src/api2/types/mod.rs b/src/api2/types/mod.rs
index 75b68879..65411f73 100644
--- a/src/api2/types/mod.rs
+++ b/src/api2/types/mod.rs
@@ -14,9 +14,10 @@ mod macros;
#[macro_use]
mod userid;
pub use userid::{Realm, RealmRef};
+pub use userid::{Tokenname, TokennameRef};
pub use userid::{Username, UsernameRef};
pub use userid::Userid;
-pub use userid::PROXMOX_GROUP_ID_SCHEMA;
+pub use userid::{PROXMOX_USER_ID_SCHEMA, PROXMOX_TOKEN_ID_SCHEMA, PROXMOX_TOKEN_NAME_SCHEMA, PROXMOX_USER_OR_TOKEN_ID_SCHEMA, PROXMOX_GROUP_ID_SCHEMA};
// File names: may not contain slashes, may not start with "."
pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
@@ -364,7 +365,7 @@ pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name
},
},
owner: {
- type: Userid,
+ schema: PROXMOX_USER_OR_TOKEN_ID_SCHEMA,
optional: true,
},
},
@@ -440,7 +441,7 @@ pub struct SnapshotVerifyState {
},
},
owner: {
- type: Userid,
+ schema: PROXMOX_USER_OR_TOKEN_ID_SCHEMA,
optional: true,
},
},
@@ -612,7 +613,7 @@ pub struct StorageStatus {
#[api(
properties: {
upid: { schema: UPID_SCHEMA },
- user: { type: Userid },
+ user: { schema: PROXMOX_USER_OR_TOKEN_ID_SCHEMA },
},
)]
#[derive(Serialize, Deserialize)]
@@ -977,7 +978,7 @@ fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
];
for name in invalid_user_ids.iter() {
- if let Ok(_) = parse_simple_value(name, &Userid::API_SCHEMA) {
+ if let Ok(_) = parse_simple_value(name, &PROXMOX_USER_ID_SCHEMA) {
bail!("test userid '{}' failed - got Ok() while exception an error.", name);
}
}
@@ -991,7 +992,7 @@ fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
];
for name in valid_user_ids.iter() {
- let v = match parse_simple_value(name, &Userid::API_SCHEMA) {
+ let v = match parse_simple_value(name, &PROXMOX_USER_ID_SCHEMA) {
Ok(v) => v,
Err(err) => {
bail!("unable to parse userid '{}' - {}", name, err);
diff --git a/src/api2/types/userid.rs b/src/api2/types/userid.rs
index 44cd10b7..591c7d26 100644
--- a/src/api2/types/userid.rs
+++ b/src/api2/types/userid.rs
@@ -1,6 +1,7 @@
//! Types for user handling.
//!
-//! We have [`Username`]s and [`Realm`]s. To uniquely identify a user, they must be combined into a [`Userid`].
+//! We have [`Username`]s, [`Realm`]s and [`Tokenname`]s. To uniquely identify a user/API token, they
+//! must be combined into a [`Userid`].
//!
//! Since they're all string types, they're organized as follows:
//!
@@ -9,10 +10,12 @@
//! 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 separate
-//! borrowed type.
+//! * [`Tokenname`]: an owned API token ID (`String` equivalent)
+//! * [`TokennameRef`]: a borrowed `Tokenname` (`str` equivalent).
+//! * [`Userid`]: an owned user id (`"user@realm"`), or API token ID (`"user@realm!tokenid"`). Note
+//! that this does not have a separate borrowed type.
//!
-//! Note that `Username`s are not unique, therefore they do not implement `Eq` and cannot be
+//! Note that `Username`s and `Tokenname`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.
@@ -36,19 +39,54 @@ use proxmox::const_regex;
// 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! TOKEN_NAME_REGEX_STR { () => (PROXMOX_SAFE_ID_REGEX_STR!()) }
macro_rules! USER_ID_REGEX_STR { () => (concat!(USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!())) }
+macro_rules! APITOKEN_ID_REGEX_STR { () => (concat!(USER_ID_REGEX_STR!() , r"!", TOKEN_NAME_REGEX_STR!())) }
const_regex! {
pub PROXMOX_USER_NAME_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"$");
+ pub PROXMOX_TOKEN_NAME_REGEX = concat!(r"^", TOKEN_NAME_REGEX_STR!(), r"$");
pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_ID_REGEX_STR!(), r"$");
+ pub PROXMOX_APITOKEN_ID_REGEX = concat!(r"^", APITOKEN_ID_REGEX_STR!(), r"$");
+ pub PROXMOX_USER_OR_APITOKEN_ID_REGEX = concat!(r"^", r"(?:", USER_ID_REGEX_STR!(), r"|", APITOKEN_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_TOKEN_NAME_FORMAT: ApiStringFormat =
+ ApiStringFormat::Pattern(&PROXMOX_TOKEN_NAME_REGEX);
pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX);
+pub const PROXMOX_TOKEN_ID_FORMAT: ApiStringFormat =
+ ApiStringFormat::Pattern(&PROXMOX_APITOKEN_ID_REGEX);
+pub const PROXMOX_USER_OR_TOKEN_ID_FORMAT: ApiStringFormat =
+ ApiStringFormat::Pattern(&PROXMOX_USER_OR_APITOKEN_ID_REGEX);
+
+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_TOKEN_ID_SCHEMA: Schema = StringSchema::new("API Token ID")
+ .format(&PROXMOX_TOKEN_ID_FORMAT)
+ .min_length(3)
+ .max_length(64)
+ .schema();
+
+pub const PROXMOX_USER_OR_TOKEN_ID_SCHEMA: Schema = StringSchema::new("User ID (with optional API token subid)")
+ .format(&PROXMOX_USER_OR_TOKEN_ID_FORMAT)
+ .min_length(3)
+ .max_length(64)
+ .schema();
+
+pub const PROXMOX_TOKEN_NAME_SCHEMA: Schema = StringSchema::new("API Token name")
+ .format(&PROXMOX_TOKEN_NAME_FORMAT)
+ .min_length(3)
+ .max_length(64)
+ .schema();
pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat =
ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX);
@@ -91,26 +129,6 @@ pub struct Username(String);
#[derive(Debug, Hash)]
pub struct UsernameRef(str);
-#[doc(hidden)]
-/// ```compile_fail
-/// let a: Username = unsafe { std::mem::zeroed() };
-/// let b: Username = unsafe { std::mem::zeroed() };
-/// let _ = <Username as PartialEq>::eq(&a, &b);
-/// ```
-///
-/// ```compile_fail
-/// let a: &UsernameRef = unsafe { std::mem::zeroed() };
-/// let b: &UsernameRef = unsafe { std::mem::zeroed() };
-/// let _ = <&UsernameRef as PartialEq>::eq(a, b);
-/// ```
-///
-/// ```compile_fail
-/// let a: &UsernameRef = unsafe { std::mem::zeroed() };
-/// let b: &UsernameRef = unsafe { std::mem::zeroed() };
-/// let _ = <&UsernameRef as PartialEq>::eq(&a, &b);
-/// ```
-struct _AssertNoEqImpl;
-
impl UsernameRef {
fn new(s: &str) -> &Self {
unsafe { &*(s as *const str as *const UsernameRef) }
@@ -286,24 +304,143 @@ impl PartialEq<Realm> for &RealmRef {
}
}
-/// A complete user id consting of a user name and a realm.
+#[api(
+ type: String,
+ format: &PROXMOX_TOKEN_NAME_FORMAT,
+)]
+/// The token ID part of an API token user id.
+///
+/// This alone does NOT uniquely identify the API token and therefore does not implement `Eq`. In
+/// order to compare token IDs directly, they need to be explicitly compared as strings by calling
+/// `.as_str()`.
+///
+/// ```compile_fail
+/// fn test(a: Tokenname, b: Tokenname) -> bool {
+/// a == b // illegal and does not compile
+/// }
+/// ```
+#[derive(Clone, Debug, Hash, Deserialize, Serialize)]
+pub struct Tokenname(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 TokennameRef(str);
+
+#[doc(hidden)]
+/// ```compile_fail
+/// let a: Username = unsafe { std::mem::zeroed() };
+/// let b: Username = unsafe { std::mem::zeroed() };
+/// let _ = <Username as PartialEq>::eq(&a, &b);
+/// ```
+///
+/// ```compile_fail
+/// let a: &UsernameRef = unsafe { std::mem::zeroed() };
+/// let b: &UsernameRef = unsafe { std::mem::zeroed() };
+/// let _ = <&UsernameRef as PartialEq>::eq(a, b);
+/// ```
+///
+/// ```compile_fail
+/// let a: &UsernameRef = unsafe { std::mem::zeroed() };
+/// let b: &UsernameRef = unsafe { std::mem::zeroed() };
+/// let _ = <&UsernameRef as PartialEq>::eq(&a, &b);
+/// ```
+///
+/// ```compile_fail
+/// let a: Tokenname = unsafe { std::mem::zeroed() };
+/// let b: Tokenname = unsafe { std::mem::zeroed() };
+/// let _ = <Tokenname as PartialEq>::eq(&a, &b);
+/// ```
+///
+/// ```compile_fail
+/// let a: &TokennameRef = unsafe { std::mem::zeroed() };
+/// let b: &TokennameRef = unsafe { std::mem::zeroed() };
+/// let _ = <&TokennameRef as PartialEq>::eq(a, b);
+/// ```
+///
+/// ```compile_fail
+/// let a: &TokennameRef = unsafe { std::mem::zeroed() };
+/// let b: &TokennameRef = unsafe { std::mem::zeroed() };
+/// let _ = <&TokennameRef as PartialEq>::eq(&a, &b);
+/// ```
+struct _AssertNoEqImpl;
+
+impl TokennameRef {
+ fn new(s: &str) -> &Self {
+ unsafe { &*(s as *const str as *const TokennameRef) }
+ }
+
+ pub fn as_str(&self) -> &str {
+ &self.0
+ }
+}
+
+impl std::ops::Deref for Tokenname {
+ type Target = TokennameRef;
+
+ fn deref(&self) -> &TokennameRef {
+ self.borrow()
+ }
+}
+
+impl Borrow<TokennameRef> for Tokenname {
+ fn borrow(&self) -> &TokennameRef {
+ TokennameRef::new(self.0.as_str())
+ }
+}
+
+impl AsRef<TokennameRef> for Tokenname {
+ fn as_ref(&self) -> &TokennameRef {
+ self.borrow()
+ }
+}
+
+impl ToOwned for TokennameRef {
+ type Owned = Tokenname;
+
+ fn to_owned(&self) -> Self::Owned {
+ Tokenname(self.0.to_owned())
+ }
+}
+
+impl TryFrom<String> for Tokenname {
+ type Error = Error;
+
+ fn try_from(s: String) -> Result<Self, Error> {
+ if !PROXMOX_TOKEN_NAME_REGEX.is_match(&s) {
+ bail!("invalid token name");
+ }
+
+ Ok(Self(s))
+ }
+}
+
+impl<'a> TryFrom<&'a str> for &'a TokennameRef {
+ type Error = Error;
+
+ fn try_from(s: &'a str) -> Result<&'a TokennameRef, Error> {
+ if !PROXMOX_TOKEN_NAME_REGEX.is_match(s) {
+ bail!("invalid token name in user id");
+ }
+
+ Ok(TokennameRef::new(s))
+ }
+}
+
+/// A complete user id consisting of a user name and a realm, and optional token name.
#[derive(Clone, Debug, Hash)]
pub struct Userid {
data: String,
name_len: usize,
+ token_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 }
+ const fn new(data: String, name_len: usize, token_len: usize) -> Self {
+ Self { data, name_len, token_len }
}
pub fn name(&self) -> &UsernameRef {
@@ -311,7 +448,33 @@ impl Userid {
}
pub fn realm(&self) -> &RealmRef {
- RealmRef::new(&self.data[(self.name_len + 1)..])
+ if self.token_len > 0 {
+ RealmRef::new(&self.data[(self.name_len + 1)..(self.data.len() - self.token_len - 1)])
+ } else {
+ RealmRef::new(&self.data[(self.name_len + 1)..])
+ }
+ }
+
+ pub fn tokenname(&self) -> Option<&TokennameRef> {
+ if self.token_len > 0 {
+ Some(TokennameRef::new(&self.data[(self.data.len() - self.token_len)..]))
+ } else {
+ None
+ }
+ }
+
+ pub fn is_tokenid(&self) -> bool {
+ self.token_len > 0
+ }
+
+ pub fn owner(&self) -> Result<Userid, Error> {
+ if !self.is_tokenid() {
+ bail!("userid is a regular user, not a token - can't determine owner");
+ }
+
+ let owner_str = &self.data.clone()[..self.data.len() - 1 - self.token_len];
+
+ Ok(Userid::new(owner_str.to_string(), self.name_len, 0))
}
pub fn as_str(&self) -> &str {
@@ -330,15 +493,17 @@ impl 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);
+ pub static ref BACKUP_USERID: Userid = Userid::new("backup@pam".to_string(), 6, 0);
+ pub static ref ROOT_USERID: Userid = Userid::new("root@pam".to_string(), 4, 0);
}
impl Eq for Userid {}
impl PartialEq for Userid {
fn eq(&self, rhs: &Self) -> bool {
- self.data == rhs.data && self.name_len == rhs.name_len
+ self.data == rhs.data
+ && self.name_len == rhs.name_len
+ && self.token_len == rhs.token_len
}
}
@@ -352,7 +517,23 @@ 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 }
+ let token_len = 0;
+ Self { data, name_len, token_len }
+ }
+}
+
+impl From<(Username, Realm, Tokenname)> for Userid {
+ fn from(parts: (Username, Realm, Tokenname)) -> Self {
+ Self::from((parts.0.as_ref(), parts.1.as_ref(), parts.2.as_ref()))
+ }
+}
+
+impl From<(&UsernameRef, &RealmRef, &TokennameRef)> for Userid {
+ fn from(parts: (&UsernameRef, &RealmRef, &TokennameRef)) -> Self {
+ let data = format!("{}@{}!{}", parts.0.as_str(), parts.1.as_str(), parts.2.as_str());
+ let name_len = parts.0.as_str().len();
+ let token_len = parts.2.as_str().len();
+ Self { data, name_len, token_len }
}
}
@@ -366,15 +547,42 @@ 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"),
- };
+ let name_len = id
+ .as_bytes()
+ .iter()
+ .rposition(|&b| b == b'@')
+ .ok_or_else(|| format_err!("not a valid user id"))?;
+
+ let realm_end = id
+ .as_bytes()
+ .iter()
+ .rposition(|&b| b == b'!')
+ .map(|pos| if pos < name_len { id.len() } else { pos })
+ .unwrap_or(id.len());
+
+ if realm_end == id.len() - 1 {
+ bail!("empty token name in userid");
+ }
+
+ let name = &id[..name_len];
+ let realm = &id[(name_len + 1)..realm_end];
+ if !PROXMOX_USER_NAME_REGEX.is_match(name) {
+ bail!("invalid user name in 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))))
+ if id.len() > realm_end {
+ let token = &id[(realm_end + 1)..];
+ if !PROXMOX_TOKEN_NAME_REGEX.is_match(token) {
+ bail!("invalid token name in user id");
+ }
+ Ok(Self::from((UsernameRef::new(name), RealmRef::new(realm), TokennameRef::new(token))))
+ } else {
+ Ok(Self::from((UsernameRef::new(name), RealmRef::new(realm))))
+ }
}
}
@@ -388,10 +596,34 @@ impl TryFrom<String> for Userid {
.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)..])
+ let realm_end = data
+ .as_bytes()
+ .iter()
+ .rposition(|&b| b == b'!')
+ .map(|pos| if pos < name_len { data.len() } else { pos })
+ .unwrap_or(data.len());
+
+ if realm_end == data.len() - 1 {
+ bail!("empty token name in userid");
+ }
+
+ if !PROXMOX_USER_NAME_REGEX.is_match(&data[..name_len]) {
+ bail!("invalid user name in user id");
+ }
+
+ PROXMOX_AUTH_REALM_STRING_SCHEMA.check_constraints(&data[(name_len + 1)..realm_end])
.map_err(|_| format_err!("invalid realm in user id"))?;
- Ok(Self { data, name_len })
+ let token_len = if realm_end == data.len() {
+ 0
+ } else {
+ if !PROXMOX_TOKEN_NAME_REGEX.is_match(&data[(realm_end + 1)..]) {
+ bail!("invalid token name in user id");
+ }
+ data.len() - realm_end - 1
+ };
+
+ Ok(Self { data, name_len, token_len })
}
}
@@ -413,5 +645,50 @@ impl PartialEq<String> for Userid {
}
}
+#[test]
+fn test_token_id() {
+ use std::str::FromStr;
+ use std::convert::TryFrom;
+
+ let userid = Userid::new("test@pam!bar".to_string(), 4, 3);
+ assert_eq!(userid.name().as_str(), "test");
+ assert_eq!(userid.realm(), "pam");
+ assert_eq!(userid.tokenname().expect("token should return tokenname").as_str(), "bar");
+ assert_eq!(userid, "test@pam!bar");
+
+ assert_eq!(userid, Userid::from_str("test@pam!bar").expect("parsing token from str failed"));
+ assert_eq!(userid, Userid::try_from("test@pam!bar".to_string()).expect("parsing token from String failed"));
+
+ let userid = Userid::new("test@pam".to_string(), 4, 0);
+ assert_eq!(userid.name().as_str(), "test");
+ assert_eq!(userid.realm(), "pam");
+ // replace with .expect_none once that is stable
+ assert_eq!(userid.tokenname().is_none(), true);
+ assert_eq!(userid, "test@pam");
+
+ assert_eq!(userid, Userid::from_str("test@pam").expect("parsing user from str failed"));
+ assert_eq!(userid, Userid::try_from("test@pam".to_string()).expect("parsing user from String failed"));
+
+ Userid::from_str("test@pam!bar@baz").expect("username with @ and ! failed");
+ Userid::try_from("test@pam!bar@baz".to_string()).expect("username with @ and ! failed");
+
+ Userid::from_str("test@pam!").expect_err("empty token should fail");
+ Userid::from_str("t€st@pam").expect("strange chars in username allowed");
+ Userid::from_str("tes/@pam").expect_err("slash in username not allowed");
+ Userid::from_str("tes:@pam").expect_err("colon in username not allowed");
+ Userid::from_str("tes\n@pam").expect_err("newline in username not allowed");
+ Userid::from_str("tes\0@pam").expect_err("\0 in username not allowed");
+ Userid::from_str("test@¶am").expect_err("strange chars in realm not allowed");
+
+ let userid = Userid::new("test@pam".to_string(), 4, 0);
+ let (name, realm) = (userid.name(), userid.realm());
+ assert_eq!(userid, Userid::from((name, realm)));
+
+ let userid = Userid::new("test@pam!test".to_string(), 4, 4);
+ let (name, realm, tokenname) = (userid.name(), userid.realm(), userid.tokenname().expect("tokenid should return tokennameref"));
+ assert_eq!(userid, Userid::from((name, realm, tokenname)));
+
+}
+
proxmox::forward_deserialize_to_from_str!(Userid);
proxmox::forward_serialize_to_display!(Userid);
diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs
index ffe5b3dd..1fbbca09 100644
--- a/src/bin/proxmox-backup-client.rs
+++ b/src/bin/proxmox-backup-client.rs
@@ -425,7 +425,7 @@ async fn list_backup_groups(param: Value) -> Result<Value, Error> {
description: "Backup group.",
},
"new-owner": {
- type: Userid,
+ schema: PROXMOX_USER_OR_TOKEN_ID_SCHEMA,
},
}
}
diff --git a/src/config/remote.rs b/src/config/remote.rs
index 9e597342..ac6079ac 100644
--- a/src/config/remote.rs
+++ b/src/config/remote.rs
@@ -45,7 +45,7 @@ pub const REMOTE_PASSWORD_SCHEMA: Schema = StringSchema::new("Password or auth t
type: u16,
},
userid: {
- type: Userid,
+ schema: PROXMOX_USER_OR_TOKEN_ID_SCHEMA,
},
password: {
schema: REMOTE_PASSWORD_SCHEMA,
diff --git a/src/config/user.rs b/src/config/user.rs
index b72fa40b..efb346d8 100644
--- a/src/config/user.rs
+++ b/src/config/user.rs
@@ -56,7 +56,7 @@ pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.")
#[api(
properties: {
userid: {
- type: Userid,
+ schema: PROXMOX_USER_ID_SCHEMA,
},
comment: {
optional: true,
@@ -109,7 +109,7 @@ fn init() -> SectionConfig {
};
let plugin = SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), obj_schema);
- let mut config = SectionConfig::new(&Userid::API_SCHEMA);
+ let mut config = SectionConfig::new(&PROXMOX_USER_ID_SCHEMA);
config.register_plugin(plugin);
--
2.20.1
next prev parent reply other threads:[~2020-10-19 7:40 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-10-19 7:39 [pbs-devel] [RFC proxmox-backup 00/15] API tokens Fabian Grünbichler
2020-10-19 7:39 ` [pbs-devel] [PATCH proxmox-backup 01/15] fix indentation Fabian Grünbichler
2020-10-19 12:00 ` [pbs-devel] applied: " Thomas Lamprecht
2020-10-19 7:39 ` [pbs-devel] [PATCH proxmox-backup 02/15] fix typos Fabian Grünbichler
2020-10-19 12:01 ` [pbs-devel] applied: " Thomas Lamprecht
2020-10-19 7:39 ` [pbs-devel] [PATCH proxmox-backup 03/15] REST: rename token to csrf_token Fabian Grünbichler
2020-10-19 12:02 ` [pbs-devel] applied: " Thomas Lamprecht
2020-10-19 7:39 ` Fabian Grünbichler [this message]
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 05/15] add ApiToken to user.cfg and CachedUserInfo Fabian Grünbichler
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 06/15] config: add token.shadow file Fabian Grünbichler
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 07/15] REST: extract and handle API tokens Fabian Grünbichler
2020-10-20 8:34 ` Wolfgang Bumiller
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 08/15] api: add API token endpoints Fabian Grünbichler
2020-10-20 9:42 ` Wolfgang Bumiller
2020-10-20 10:15 ` Wolfgang Bumiller
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 09/15] api: allow listing users + tokens Fabian Grünbichler
2020-10-20 10:10 ` Wolfgang Bumiller
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 10/15] api: add permissions endpoint Fabian Grünbichler
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 11/15] client: allow using ApiToken + secret Fabian Grünbichler
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 12/15] owner checks: handle backups owned by API tokens Fabian Grünbichler
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 13/15] tasks: allow unpriv users to read their tokens' tasks Fabian Grünbichler
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 14/15] manager: add token commands Fabian Grünbichler
2020-10-19 7:39 ` [pbs-devel] [RFC proxmox-backup 15/15] manager: add user permissions command Fabian Grünbichler
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20201019073919.588521-5-f.gruenbichler@proxmox.com \
--to=f.gruenbichler@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal