use super::Scope;
use crate::{
auth::claims::{WORKSPACE_ADMIN_SCOPE, WORKSPACE_CONTROL_SCOPE, WORKSPACE_MEMBER_SCOPE},
claims::common::ArrayOrValue,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use utoipa::ToSchema;
#[derive(Error, Debug, PartialEq)]
#[error("Role error: {0}")]
pub struct RoleError(String);
#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq, Hash, Serialize, ToSchema)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum Role {
#[serde(alias = "Admin", alias = "ADMIN")]
Admin,
#[serde(alias = "Control", alias = "CONTROL")]
Control,
#[serde(alias = "Member", alias = "MEMBER")]
#[default]
Member,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, Default)]
#[serde(rename_all = "lowercase")]
pub struct RoleSet(ArrayOrValue<Role>);
impl Role {
pub fn scope(&self) -> Scope {
match self {
Role::Admin => WORKSPACE_ADMIN_SCOPE,
Role::Control => WORKSPACE_CONTROL_SCOPE,
Role::Member => WORKSPACE_MEMBER_SCOPE,
}
}
pub fn is_superset_of(&self, other: Role) -> bool {
self.scope().has_all_permissions(other.scope())
}
}
impl RoleSet {
pub fn single(role: Role) -> Self {
Self(ArrayOrValue::single(role))
}
pub fn scope(&self) -> Scope {
self.0
.iter()
.map(|role| role.scope())
.fold(Scope::with_no_permissions(), |acc, scope| acc.merge(scope))
}
pub fn has_role(&self, role: Role) -> bool {
self.0.iter().any(|check| check == role)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn add_role(self, role: Role) -> Self {
Self(self.0.add(role))
}
}
impl PartialEq<String> for Role {
fn eq(&self, other: &String) -> bool {
Into::<&str>::into(*self) == other.as_str()
}
}
impl PartialEq<&str> for Role {
fn eq(&self, other: &&str) -> bool {
Into::<&str>::into(*self) == *other
}
}
impl TryFrom<String> for Role {
type Error = RoleError;
fn try_from(s: String) -> Result<Self, Self::Error> {
s.as_str().try_into()
}
}
impl TryFrom<&str> for Role {
type Error = RoleError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s.to_lowercase().as_str() {
"admin" => Ok(Role::Admin),
"control" => Ok(Role::Control),
"member" => Ok(Role::Member),
_ => Err(RoleError(format!("Invalid role: {s}"))),
}
}
}
impl From<Role> for &str {
fn from(role: Role) -> Self {
match role {
Role::Admin => "admin",
Role::Control => "control",
Role::Member => "member",
}
}
}
impl From<Role> for String {
fn from(role: Role) -> Self {
Into::<&str>::into(role).to_string()
}
}
#[cfg(feature = "test_utils")]
impl fake::Dummy<fake::Faker> for Role {
fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &fake::Faker, _: &mut R) -> Self {
Role::Admin
}
}
#[cfg(test)]
mod test {
use super::*;
use serde_json::json;
mod role_set {
use super::*;
#[test]
fn single() {
let role_set = RoleSet::single(Role::Admin);
assert_eq!(role_set.len(), 1);
assert!(role_set.has_role(Role::Admin));
assert!(!role_set.has_role(Role::Member));
}
#[test]
fn empty() {
let role_set: RoleSet = Default::default();
assert!(role_set.is_empty());
assert_eq!(role_set.len(), 0);
}
#[test]
fn multiple_roles() {
let role_set = RoleSet::default()
.add_role(Role::Admin)
.add_role(Role::Member);
assert_eq!(role_set.len(), 2);
assert!(role_set.has_role(Role::Admin));
assert!(role_set.has_role(Role::Member));
}
#[test]
fn scope_single() {
let role_set = RoleSet::single(Role::Admin);
assert_eq!(role_set.scope(), WORKSPACE_ADMIN_SCOPE);
}
#[test]
fn scope_multiple() {
let role_set = RoleSet::default()
.add_role(Role::Admin)
.add_role(Role::Member);
assert_eq!(
role_set.scope(),
WORKSPACE_ADMIN_SCOPE.merge(WORKSPACE_MEMBER_SCOPE)
);
}
}
mod scope {
use super::*;
#[test]
fn admin() {
assert_eq!(Role::Admin.scope(), WORKSPACE_ADMIN_SCOPE);
}
#[test]
fn control() {
assert_eq!(Role::Control.scope(), WORKSPACE_CONTROL_SCOPE);
}
#[test]
fn member() {
assert_eq!(Role::Member.scope(), WORKSPACE_MEMBER_SCOPE);
}
}
mod equality {
use super::*;
#[test]
fn with_string() {
assert_eq!(Role::Admin, "admin".to_string());
assert_eq!(Role::Control, "control".to_string());
assert_eq!(Role::Member, "member".to_string());
}
#[test]
fn with_str() {
assert_eq!(Role::Admin, "admin");
assert_eq!(Role::Control, "control");
assert_eq!(Role::Member, "member");
}
}
mod conversion {
use super::*;
#[test]
fn from_string() -> anyhow::Result<()> {
assert_eq!(Role::Admin, Role::try_from("admin".to_string())?);
assert_eq!(Role::Control, Role::try_from("control".to_string())?);
assert_eq!(Role::Member, Role::try_from("member".to_string())?);
assert!(Role::try_from("notarole".to_string()).is_err());
Ok(())
}
#[test]
fn from_str() -> anyhow::Result<()> {
assert_eq!(Role::Admin, Role::try_from("admin")?);
assert_eq!(Role::Control, Role::try_from("control")?);
assert_eq!(Role::Member, Role::try_from("member")?);
assert!(Role::try_from("notarole").is_err());
Ok(())
}
}
mod deserialize {
use super::*;
#[test]
fn lowercase() -> anyhow::Result<()> {
assert_eq!(Role::Admin, serde_json::from_value::<Role>(json!("admin"))?);
assert_eq!(
Role::Control,
serde_json::from_value::<Role>(json!("control"))?
);
assert_eq!(
Role::Member,
serde_json::from_value::<Role>(json!("member"))?
);
assert!(serde_json::from_value::<Role>(json!("notarole")).is_err());
Ok(())
}
#[test]
fn capitalized() -> anyhow::Result<()> {
assert_eq!(Role::Admin, serde_json::from_value::<Role>(json!("Admin"))?);
assert_eq!(
Role::Control,
serde_json::from_value::<Role>(json!("Control"))?
);
assert_eq!(
Role::Member,
serde_json::from_value::<Role>(json!("Member"))?
);
assert!(serde_json::from_value::<Role>(json!("Notarole")).is_err());
Ok(())
}
#[test]
fn allcaps() -> anyhow::Result<()> {
assert_eq!(Role::Admin, serde_json::from_value::<Role>(json!("ADMIN"))?);
assert_eq!(
Role::Control,
serde_json::from_value::<Role>(json!("CONTROL"))?
);
assert_eq!(
Role::Member,
serde_json::from_value::<Role>(json!("MEMBER"))?
);
assert!(serde_json::from_value::<Role>(json!("NOTAROLE")).is_err());
Ok(())
}
}
mod serialize {
use super::*;
#[test]
fn capitalisation() {
assert_eq!(serde_json::to_value(Role::Admin).unwrap(), json!("admin"));
assert_eq!(
serde_json::to_value(Role::Control).unwrap(),
json!("control")
);
assert_eq!(serde_json::to_value(Role::Member).unwrap(), json!("member"));
}
}
mod privilege_escalation {
use super::*;
#[test]
fn admin_is_superset() {
assert!(Role::Admin.is_superset_of(Role::Admin));
assert!(Role::Admin.is_superset_of(Role::Control));
assert!(Role::Admin.is_superset_of(Role::Member));
}
#[test]
fn control_is_not_superset_of_admin_or_member() {
assert!(!Role::Control.is_superset_of(Role::Admin));
assert!(!Role::Control.is_superset_of(Role::Member));
}
#[test]
fn member_is_not_superset() {
assert!(!Role::Member.is_superset_of(Role::Admin));
assert!(!Role::Member.is_superset_of(Role::Control));
assert!(Role::Member.is_superset_of(Role::Member));
}
}
}