use uuid::Uuid;
use wyrd_spec::DataTenantId;
use crate::permission::Permission;
pub const NS_BUILTIN_ROLE: Uuid = Uuid::from_u128(0x6ad8_2377_3a8f_5f42_9d17_a9f5_5b1c_5c63);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinRole {
pub name: &'static str,
pub permissions: &'static [Permission],
}
const VIEWER: &[Permission] = &[
Permission::card_read(),
Permission::artifact_read(),
Permission::audit_read(),
Permission::operators_read(),
Permission::gateway_read(),
Permission::gateway_payload_read(),
Permission::bifrost_table_read(),
Permission::bifrost_query_read(),
];
const WORKLOAD: &[Permission] = &[
Permission::card_read(),
Permission::artifact_read(),
Permission::audit_read(),
Permission::operators_read(),
Permission::gateway_read(),
Permission::gateway_payload_read(),
Permission::bifrost_table_read(),
Permission::bifrost_query_read(),
Permission::bifrost_table_write(),
Permission::bifrost_record_write(),
Permission::verifier_run_any(),
Permission::trigger_write(),
Permission::operator_invoke(),
Permission::gateway_invoke_any(),
];
const EDITOR: &[Permission] = &[
Permission::card_read(),
Permission::artifact_read(),
Permission::audit_read(),
Permission::operators_read(),
Permission::gateway_read(),
Permission::gateway_payload_read(),
Permission::bifrost_table_read(),
Permission::bifrost_query_read(),
Permission::bifrost_table_write(),
Permission::bifrost_record_write(),
Permission::verifier_run_any(),
Permission::trigger_write(),
Permission::operator_invoke(),
Permission::gateway_invoke_any(),
Permission::card_write(),
Permission::card_delete(),
Permission::artifact_write(),
Permission::policy_lock(),
Permission::service_install(),
];
pub const BUILTIN_ROLES: &[BuiltinRole] = &[
BuiltinRole {
name: "admin",
permissions: &[Permission::wildcard()],
},
BuiltinRole {
name: "editor",
permissions: EDITOR,
},
BuiltinRole {
name: "workload",
permissions: WORKLOAD,
},
BuiltinRole {
name: DEFAULT_ISSUER_ROLE,
permissions: VIEWER,
},
];
pub const DEFAULT_ISSUER_ROLE: &str = "viewer";
#[must_use]
pub fn builtin_role_uuid(data_tenant_id: DataTenantId, role_name: &str) -> Uuid {
Uuid::new_v5(
&NS_BUILTIN_ROLE,
format!("{data_tenant_id}/{role_name}").as_bytes(),
)
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use super::{BUILTIN_ROLES, builtin_role_uuid};
use crate::Permission;
fn permissions(name: &str) -> &'static [Permission] {
BUILTIN_ROLES
.iter()
.find(|role| role.name == name)
.expect("role is built in")
.permissions
}
fn holds(name: &str, required: &Permission) -> bool {
permissions(name)
.iter()
.any(|permission| permission.covers(required))
}
#[test]
fn all_seeds_are_valid_permissions() {
for role in BUILTIN_ROLES {
let value = serde_json::to_value(role.permissions).expect("permissions serialize");
let round_trip: Vec<Permission> =
serde_json::from_value(value).expect("permissions deserialize");
assert_eq!(round_trip, role.permissions);
}
}
#[test]
fn exactly_four_roles_are_built_in() {
let names = BUILTIN_ROLES
.iter()
.map(|role| role.name)
.collect::<BTreeSet<_>>();
assert_eq!(
names,
BTreeSet::from(["admin", "editor", "viewer", "workload"])
);
}
#[test]
fn roles_are_strictly_nested() {
for (lower, upper) in [
("viewer", "workload"),
("workload", "editor"),
("editor", "admin"),
] {
for permission in permissions(lower) {
assert!(holds(upper, permission), "{upper} lacks {permission}");
}
assert!(
permissions(upper)
.iter()
.any(|permission| !holds(lower, permission)),
"{upper} adds nothing to {lower}"
);
}
}
#[test]
fn each_role_holds_its_exact_permissions() {
let viewer = vec![
Permission::card_read(),
Permission::artifact_read(),
Permission::audit_read(),
Permission::operators_read(),
Permission::gateway_read(),
Permission::gateway_payload_read(),
Permission::bifrost_table_read(),
Permission::bifrost_query_read(),
];
let mut workload = viewer.clone();
workload.extend([
Permission::bifrost_table_write(),
Permission::bifrost_record_write(),
Permission::verifier_run_any(),
Permission::trigger_write(),
Permission::operator_invoke(),
Permission::gateway_invoke_any(),
]);
let mut editor = workload.clone();
editor.extend([
Permission::card_write(),
Permission::card_delete(),
Permission::artifact_write(),
Permission::policy_lock(),
Permission::service_install(),
]);
assert_eq!(permissions("viewer"), viewer.as_slice());
assert_eq!(permissions("workload"), workload.as_slice());
assert_eq!(permissions("editor"), editor.as_slice());
assert_eq!(permissions("admin"), [Permission::wildcard()].as_slice());
}
#[test]
fn only_admin_holds_administration() {
for required in [
Permission::service_accounts_write(),
Permission::identity_connections_write(),
Permission::users_manage(),
Permission::operators_write(),
Permission::gateway_write(),
Permission::gateway_delete(),
Permission::bifrost_peer_invoke(),
Permission::bifrost_table_install(),
Permission::tenant_create(),
] {
let granting = BUILTIN_ROLES
.iter()
.filter(|role| holds(role.name, &required))
.map(|role| role.name)
.collect::<Vec<_>>();
assert_eq!(granting, vec!["admin"], "{required}");
}
assert!(!holds("workload", &Permission::card_write()));
assert!(!holds("workload", &Permission::artifact_write()));
}
#[test]
fn role_uuid_is_stable_per_tenant_and_name() {
let tenant = wyrd_spec::DataTenantId::new_v7();
assert_eq!(
builtin_role_uuid(tenant, "admin"),
builtin_role_uuid(tenant, "admin")
);
assert_ne!(
builtin_role_uuid(tenant, "admin"),
builtin_role_uuid(tenant, "viewer")
);
}
}