use derive_more::Display;
use std::{
any::type_name,
fmt::{self, Debug},
ops::{BitAnd, BitOr},
};
use super::Role;
use crate::claims::common::ScalarOrArray;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
#[display("{_0}")]
pub struct Scope(PermissionFlags);
impl Default for Scope {
fn default() -> Self {
Self::with_no_permissions()
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PermissionFlags(u64);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
pub enum Permission {
#[display("access_key:{_0}")]
AccessKey(AccessKeyPermission),
#[display("dataset:{_0}")]
Keyset(KeysetPermission),
#[display("client:{_0}")]
Client(ClientPermission),
#[display("data_key:{_0}")]
DataKey(DataKeyPermission),
#[display("cs_support:{_0}")]
Support(SupportPermission),
#[display("logging:{_0}")]
Logging(LoggingPermission),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
#[repr(u8)]
pub enum SupportPermission {
#[display("admin")]
Admin = 0b0000_0001,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
#[repr(u8)]
pub enum LoggingPermission {
#[display("append")]
Append = 0b0000_0001,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
#[repr(u8)]
pub enum AccessKeyPermission {
#[display("create")]
Create = 0b0000_0001,
#[display("list")]
List = 0b0000_0010,
#[display("delete")]
Delete = 0b0000_0100,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
#[repr(u8)]
pub enum KeysetPermission {
#[display("create")]
Create = 0b0000_0001,
#[display("list")]
List = 0b0000_0010,
#[display("disable")]
Disable = 0b0000_0100,
#[display("enable")]
Enable = 0b0000_1000,
#[display("grant")]
Grant = 0b0001_0000,
#[display("modify")]
Modify = 0b0010_0000,
#[display("revoke")]
Revoke = 0b0100_0000,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
#[repr(u8)]
pub enum ClientPermission {
#[display("create")]
Create = 0b0000_0001,
#[display("list")]
List = 0b0000_0010,
#[display("delete")]
Delete = 0b0000_0100,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
#[repr(u8)]
pub enum DataKeyPermission {
#[display("generate")]
Generate = 0b0000_0001,
#[display("retrieve")]
Retrieve = 0b0000_0010,
}
impl Permission {
fn parse(input: &str) -> Option<Self> {
match input {
s if s.starts_with("keyset:") => Some(Self::Keyset(KeysetPermission::parse(
&s["keyset:".len()..],
)?)),
s if s.starts_with("access_key:") => Some(Self::AccessKey(AccessKeyPermission::parse(
&s["access_key:".len()..],
)?)),
s if s.starts_with("dataset:") => Some(Self::Keyset(KeysetPermission::parse(
&s["dataset:".len()..],
)?)),
s if s.starts_with("data_key:") => Some(Self::DataKey(DataKeyPermission::parse(
&s["data_key:".len()..],
)?)),
s if s.starts_with("client:") => Some(Self::Client(ClientPermission::parse(
&s["client:".len()..],
)?)),
s if s.starts_with("cs_support:") => Some(Self::Support(SupportPermission::parse(
&s["cs_support:".len()..],
)?)),
s if s.starts_with("logging:") => Some(Self::Logging(LoggingPermission::parse(
&s["logging:".len()..],
)?)),
_ => None,
}
}
const fn to_flags(self) -> PermissionFlags {
const _: () = assert!(size_of::<KeysetPermission>() == 1);
const _: () = assert!(size_of::<ClientPermission>() == 1);
const _: () = assert!(size_of::<DataKeyPermission>() == 1);
const _: () = assert!(size_of::<SupportPermission>() == 1);
const _: () = assert!(size_of::<LoggingPermission>() == 1);
const _: () = assert!(size_of::<AccessKeyPermission>() == 1);
match self {
Permission::Keyset(permission) => PermissionFlags(permission as u64),
Permission::Client(permission) => PermissionFlags((permission as u64) << 8),
Permission::DataKey(permission) => PermissionFlags((permission as u64) << 16),
Permission::Support(permission) => PermissionFlags((permission as u64) << 24),
Permission::Logging(permission) => PermissionFlags((permission as u64) << 32),
Permission::AccessKey(permission) => PermissionFlags((permission as u64) << 40),
}
}
const ALL: &[Permission] = &[
Self::Keyset(KeysetPermission::Create),
Self::Keyset(KeysetPermission::List),
Self::Keyset(KeysetPermission::Enable),
Self::Keyset(KeysetPermission::Disable),
Self::Keyset(KeysetPermission::Grant),
Self::Keyset(KeysetPermission::Modify),
Self::Keyset(KeysetPermission::Revoke),
Self::DataKey(DataKeyPermission::Generate),
Self::DataKey(DataKeyPermission::Retrieve),
Self::Client(ClientPermission::Create),
Self::Client(ClientPermission::List),
Self::Client(ClientPermission::Delete),
Self::AccessKey(AccessKeyPermission::Create),
Self::AccessKey(AccessKeyPermission::List),
Self::AccessKey(AccessKeyPermission::Delete),
Self::Support(SupportPermission::Admin),
Self::Logging(LoggingPermission::Append),
];
}
macro_rules! impl_from {
($variant:ident, $ty:ty) => {
impl From<$ty> for Permission {
fn from(value: $ty) -> Self {
Self::$variant(value)
}
}
};
}
impl_from!(AccessKey, AccessKeyPermission);
impl_from!(Keyset, KeysetPermission);
impl_from!(DataKey, DataKeyPermission);
impl_from!(Client, ClientPermission);
impl_from!(Support, SupportPermission);
impl_from!(Logging, LoggingPermission);
impl PermissionFlags {
pub const fn empty() -> Self {
Self(0)
}
pub const fn with_flag(flag: Permission) -> Self {
Self::empty().add(flag)
}
pub const fn add(self, permission: Permission) -> Self {
Self(self.0 | permission.to_flags().0)
}
pub const fn merge(self, other: Self) -> Self {
Self(self.0 | other.0)
}
fn count_permissions(&self) -> u32 {
self.0.count_ones()
}
}
impl IntoIterator for PermissionFlags {
type IntoIter = PermissionsIter;
type Item = Permission;
fn into_iter(self) -> Self::IntoIter {
PermissionsIter(self, Permission::ALL)
}
}
impl FromIterator<Permission> for PermissionFlags {
fn from_iter<T: IntoIterator<Item = Permission>>(iter: T) -> Self {
let iter = iter.into_iter();
let mut flags = Self(0);
for permission in iter {
flags = flags.add(permission);
}
flags
}
}
pub struct PermissionsIter(PermissionFlags, &'static [Permission]);
impl Iterator for PermissionsIter {
type Item = Permission;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some((first, rest)) = self.1.split_first() {
self.1 = rest;
if self.0 & first.to_flags() == first.to_flags() {
return Some(*first);
}
} else {
return None;
}
}
}
}
impl BitAnd for PermissionFlags {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self(self.0 & rhs.0)
}
}
impl BitOr for PermissionFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl fmt::Display for PermissionFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let count = self.count_permissions() as usize;
for (idx, scope) in self.into_iter().enumerate() {
fmt::Display::fmt(&scope, f)?;
if idx < count - 1 {
fmt::Display::fmt(", ", f)?;
}
}
Ok(())
}
}
impl Debug for PermissionFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let count = self.count_permissions() as usize;
f.write_fmt(format_args!("{}(", type_name::<Self>()))?;
for (idx, scope) in self.into_iter().enumerate() {
fmt::Display::fmt(&scope, f)?;
if idx < count - 1 {
fmt::Display::fmt("|", f)?;
}
}
f.write_str(")")?;
Ok(())
}
}
impl KeysetPermission {
fn parse(input: &str) -> Option<Self> {
match input {
"create" => Some(Self::Create),
"list" => Some(Self::List),
"disable" => Some(Self::Disable),
"enable" => Some(Self::Enable),
"grant" => Some(Self::Grant),
"modify" => Some(Self::Modify),
"revoke" => Some(Self::Revoke),
_ => None,
}
}
}
impl ClientPermission {
fn parse(input: &str) -> Option<Self> {
match input {
"create" => Some(Self::Create),
"list" => Some(Self::List),
"delete" => Some(Self::Delete),
_ => None,
}
}
}
impl DataKeyPermission {
fn parse(input: &str) -> Option<Self> {
match input {
"generate" => Some(Self::Generate),
"retrieve" => Some(Self::Retrieve),
_ => None,
}
}
}
impl SupportPermission {
fn parse(input: &str) -> Option<Self> {
match input {
"admin" => Some(Self::Admin),
_ => None,
}
}
}
impl LoggingPermission {
fn parse(input: &str) -> Option<Self> {
match input {
"append" => Some(Self::Append),
_ => None,
}
}
}
impl AccessKeyPermission {
fn parse(input: &str) -> Option<Self> {
match input {
"create" => Some(Self::Create),
"list" => Some(Self::List),
"delete" => Some(Self::Delete),
_ => None,
}
}
}
impl Scope {
pub const fn with_permissions(permissions: PermissionFlags) -> Self {
Self(permissions)
}
pub const fn with_permission(permission: Permission) -> Self {
Self(PermissionFlags::empty().add(permission))
}
pub fn with_no_permissions() -> Self {
Self(PermissionFlags::empty())
}
pub fn merge(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub fn parse(input: &str) -> Self {
let permissions: PermissionFlags =
input
.split_whitespace()
.fold(PermissionFlags::empty(), |acc, s| {
if let Some(permission) = Permission::parse(s) {
acc.add(permission)
} else {
acc
}
});
Self(permissions)
}
pub fn has_permission(&self, permission: Permission) -> bool {
self.0 & permission.to_flags() == permission.to_flags()
}
pub fn has_all_permissions(&self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub fn count_permissions(&self) -> u32 {
self.0.count_permissions()
}
pub fn permissions(&self) -> impl Iterator<Item = Permission> {
self.0.into_iter()
}
}
pub const WORKSPACE_ADMIN_SCOPE: Scope = Scope(
PermissionFlags::empty()
.add(Permission::AccessKey(AccessKeyPermission::Create))
.add(Permission::AccessKey(AccessKeyPermission::List))
.add(Permission::AccessKey(AccessKeyPermission::Delete))
.add(Permission::Keyset(KeysetPermission::Create))
.add(Permission::Keyset(KeysetPermission::List))
.add(Permission::Keyset(KeysetPermission::Disable))
.add(Permission::Keyset(KeysetPermission::Enable))
.add(Permission::Keyset(KeysetPermission::Grant))
.add(Permission::Keyset(KeysetPermission::Modify))
.add(Permission::Keyset(KeysetPermission::Revoke))
.add(Permission::Client(ClientPermission::Create))
.add(Permission::Client(ClientPermission::List))
.add(Permission::Client(ClientPermission::Delete))
.add(Permission::DataKey(DataKeyPermission::Generate))
.add(Permission::DataKey(DataKeyPermission::Retrieve)),
);
pub const WORKSPACE_CONTROL_SCOPE: Scope = Scope(
PermissionFlags::empty()
.add(Permission::Keyset(KeysetPermission::Create))
.add(Permission::Keyset(KeysetPermission::List))
.add(Permission::Keyset(KeysetPermission::Disable))
.add(Permission::Keyset(KeysetPermission::Enable))
.add(Permission::Keyset(KeysetPermission::Grant))
.add(Permission::Keyset(KeysetPermission::Modify))
.add(Permission::Keyset(KeysetPermission::Revoke))
.add(Permission::Client(ClientPermission::List)),
);
pub const WORKSPACE_MEMBER_SCOPE: Scope = Scope(
PermissionFlags::empty()
.add(Permission::Keyset(KeysetPermission::List))
.add(Permission::Client(ClientPermission::Create))
.add(Permission::DataKey(DataKeyPermission::Generate))
.add(Permission::DataKey(DataKeyPermission::Retrieve)),
);
pub const LOGGING_SCOPE: Scope =
Scope(PermissionFlags::empty().add(Permission::Logging(LoggingPermission::Append)));
impl From<Option<Role>> for Scope {
fn from(role: Option<Role>) -> Self {
match role {
Some(role) => role.scope(),
None => Default::default(),
}
}
}
impl From<Vec<String>> for Scope {
fn from(scope_names: Vec<String>) -> Self {
Scope(
scope_names
.into_iter()
.fold(PermissionFlags::empty(), |acc, s| {
if let Some(permission) = Permission::parse(&s) {
acc.merge(permission.to_flags())
} else {
acc
}
}),
)
}
}
impl Serialize for Permission {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.to_string().serialize(serializer)
}
}
impl Serialize for Scope {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let flags: Vec<Permission> = self.permissions().collect();
match &flags[..] {
&[] => serializer.serialize_none(),
&[permission] => Some(ScalarOrArray::Scalar(permission)).serialize(serializer),
flags => Some(ScalarOrArray::Array(flags.iter().collect())).serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for Scope {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let items: Option<ScalarOrArray<String>> = Deserialize::deserialize(deserializer)?;
match items {
Some(ScalarOrArray::Array(items)) => Ok(Scope::from(items)),
Some(ScalarOrArray::Scalar(item)) => Ok(Scope(
Permission::parse(&item)
.map(|p| p.to_flags())
.unwrap_or(PermissionFlags::empty()),
)),
None => Ok(Scope::with_no_permissions()),
}
}
}
impl From<String> for Scope {
fn from(value: String) -> Self {
Scope::parse(&value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::{json, Value};
mod parse {
use super::*;
#[test]
fn empty_string_parses_to_empty_scope() {
let scope = Scope::parse("");
assert_eq!(scope, Scope::with_no_permissions());
}
#[test]
fn single_scope_parses_correctly() {
let scope = Scope::parse("dataset:create");
assert!(scope.has_permission(Permission::Keyset(KeysetPermission::Create)));
assert_eq!(scope.count_permissions(), 1);
}
#[test]
fn multiple_scopes_parse_correctly() {
let scope = Scope::parse("dataset:create client:list");
assert!(scope.has_permission(Permission::Keyset(KeysetPermission::Create)));
assert!(scope.has_permission(Permission::Client(ClientPermission::List)));
assert_eq!(scope.count_permissions(), 2);
}
}
mod serialize {
use super::*;
#[test]
fn empty_scope_serializes_as_null() {
let scope = Scope::default();
let serialized = serde_json::to_value(scope).unwrap();
assert_eq!(serialized, Value::Null);
}
#[test]
fn single_scope_serializes_as_string() {
let scope = Scope::parse("dataset:create");
let serialized = serde_json::to_value(scope).unwrap();
assert_eq!(serialized, json!("dataset:create"));
}
#[test]
fn multiple_scopes_serializes_as_array() {
let scope = Scope::parse("dataset:create client:list");
let serialized = serde_json::to_value(scope).unwrap();
assert!(
serialized == json!(["dataset:create", "client:list"])
|| serialized == json!(["client:list", "dataset:create"]),
"Expected serialized scope to be an array of scopes, got: {serialized:?}"
);
}
}
mod deserialize {
use super::*;
#[test]
fn empty_scope_deserializes_from_null() {
let json = Value::Null;
let scope: Scope = serde_json::from_value(json).unwrap();
assert_eq!(scope, Scope::default());
}
#[test]
fn empty_string_deserializes_to_empty_scope() {
let json = json!("");
let scope: Scope = serde_json::from_value(json).unwrap();
assert_eq!(scope, Scope::default());
}
#[test]
fn single_scope_deserializes_from_string() {
let json = json!("dataset:create");
let scope: Scope = serde_json::from_value(json).unwrap();
assert_eq!(scope, Scope::parse("dataset:create"));
}
#[test]
fn multiple_scopes_deserialize_from_array() {
let json = json!(["dataset:create", "client:list"]);
let scope: Scope = serde_json::from_value(json).unwrap();
assert!(scope.has_permission(Permission::Keyset(KeysetPermission::Create)));
assert!(scope.has_permission(Permission::Client(ClientPermission::List)));
assert_eq!(
scope.count_permissions(),
2,
"Expected scope to contain 2 items"
);
}
#[test]
fn invalid_scope_deserializes_from_invalid_value() {
let json = json!(12345);
let result: Result<Scope, _> = serde_json::from_value(json);
assert!(
result.is_err(),
"Expected deserialization to fail for invalid scope"
);
}
}
mod merge {
use super::*;
#[test]
fn non_overlapping() {
let scope1 = Scope::parse("dataset:create");
let scope2 = Scope::parse("client:list");
let merged = scope1.merge(scope2);
assert!(merged.has_permission(Permission::Keyset(KeysetPermission::Create)));
assert!(merged.has_permission(Permission::Client(ClientPermission::List)));
assert_eq!(merged.count_permissions(), 2);
}
#[test]
fn overlapping() {
let scope1 = Scope::parse("dataset:create client:list");
let scope2 = Scope::parse("client:list data_key:generate");
let merged = scope1.merge(scope2);
assert!(merged.has_permission(Permission::Keyset(KeysetPermission::Create)));
assert!(merged.has_permission(Permission::Client(ClientPermission::List)));
assert!(merged.has_permission(Permission::DataKey(DataKeyPermission::Generate)));
assert_eq!(merged.count_permissions(), 3);
}
}
mod ensure_bitflag_representation_has_no_collisions {
use super::*;
mod scope_with_all_permissions {
use super::*;
#[test]
fn for_each_permission_has_permission_returns_true() {
let mut scope = Scope::with_no_permissions();
for permission in Permission::ALL {
scope = scope.merge(Scope::with_permission(*permission));
}
for permission in Permission::ALL {
assert!(scope.has_permission(*permission))
}
}
}
mod scope_with_all_but_one_permssion {
use super::*;
#[test]
fn has_permission_returns_true_for_all_except_one_permission() {
for without_permission in Permission::ALL {
let permissions: Vec<_> = Permission::ALL
.iter()
.filter(|p| *p != without_permission)
.copied()
.collect();
let scope = Scope::with_permissions(PermissionFlags::from_iter(
permissions.into_iter(),
));
for permission in Permission::ALL {
if permission != without_permission {
assert!(scope.has_permission(*permission))
} else {
assert!(!scope.has_permission(*permission))
}
}
}
}
}
}
}