// management_api.mbt — Policy and role management APIs.
//
// Mirrors the subset of Casbin's management and RBAC APIs that operate on
// the in-memory store:
//
// - policy rows (`p`): add / remove / has / get / filtered queries;
// - grouping rows (`g`): the same operations, each rebuilding role links
// when automatic rebuilding is enabled;
// - RBAC: roles for a user, users for a role, implicit roles and users,
// permissions for a user, implicit permissions, and the cascading deletes
// (`delete_roles_for_user`, `delete_user`, `delete_role`).
//
// Filtered queries and removals use Casbin's convention: an empty string in
// `values` matches any field. Unlike Casbin, row sizes are validated when a
// row is added, so mistakes surface at the call site instead of at
// enforcement time. `domain` arguments are optional and only meaningful for
// role definitions with three tokens (`g = _, _, _`).
///|
/// Adds a policy row to the `p` section. Returns `false` when the row
/// already exists.
pub fn Enforcer::add_policy(
self : Enforcer,
values : Array[String],
) -> Result[Bool, CasbinError] {
self.add_named_policy("p", values)
}
///|
/// Adds a policy row to the section `key`.
pub fn Enforcer::add_named_policy(
self : Enforcer,
key : String,
values : Array[String],
) -> Result[Bool, CasbinError] {
Ok(enforcer_add_named_policy_raise(self, key, values)) catch {
error => Err(error)
}
}
///|
fn enforcer_add_named_policy_raise(
enforcer : Enforcer,
key : String,
values : Array[String],
) -> Bool raise CasbinError {
let assertion = match enforcer.model.policy_definition(key) {
Some(assertion) => assertion
None =>
raise casbin_error(
ModelValidation,
"policy type \"" + key + "\" is not defined in the model",
)
}
if values.length() != assertion.tokens().length() {
raise casbin_error(
Enforcement,
"invalid policy rule size for \"" +
key +
"\": expected " +
assertion.tokens().length().to_string() +
", got " +
values.length().to_string(),
)
}
enforcer.store.add(key, values)
}
///|
/// Removes a policy row from the `p` section.
pub fn Enforcer::remove_policy(
self : Enforcer,
values : Array[String],
) -> Result[Bool, CasbinError] {
self.remove_named_policy("p", values)
}
///|
/// Removes a policy row from the section `key`.
pub fn Enforcer::remove_named_policy(
self : Enforcer,
key : String,
values : Array[String],
) -> Result[Bool, CasbinError] {
Ok(self.store.remove(key, values))
}
///|
/// Whether the `p` section contains the row.
pub fn Enforcer::has_policy(
self : Enforcer,
values : Array[String],
) -> Result[Bool, CasbinError] {
Ok(self.store.has("p", values))
}
///|
/// The rows of the `p` section, in insertion order.
pub fn Enforcer::get_policy(self : Enforcer) -> Array[Array[String]] {
self.store.rows("p")
}
///|
/// The `p` rows whose fields from `field_index` on match `values`; an
/// empty string in `values` matches any field.
pub fn Enforcer::get_filtered_policy(
self : Enforcer,
field_index : Int,
values : Array[String],
) -> Array[Array[String]] {
self.filtered_rows(self.policy_type, field_index, values)
}
///|
/// Removes every `p` row matched by `values`. Returns whether any row was
/// removed.
pub fn Enforcer::remove_filtered_policy(
self : Enforcer,
field_index : Int,
values : Array[String],
) -> Result[Bool, CasbinError] {
Ok(self.remove_filtered_rows(self.policy_type, field_index, values))
}
///|
/// Adds a grouping row to the `g` section. Returns `false` when the row
/// already exists.
pub fn Enforcer::add_grouping_policy(
self : Enforcer,
values : Array[String],
) -> Result[Bool, CasbinError] {
Ok(enforcer_add_grouping_policy_raise(self, values)) catch {
error => Err(error)
}
}
///|
fn enforcer_add_grouping_policy_raise(
enforcer : Enforcer,
values : Array[String],
) -> Bool raise CasbinError {
let assertion = match enforcer.model.role_definition("g") {
Some(assertion) => assertion
None =>
raise casbin_error(
ModelValidation,
"role type \"g\" is not defined in the model",
)
}
if values.length() < assertion.tokens().length() {
raise casbin_error(
Enforcement,
"grouping policy elements do not meet role definition \"g\"",
)
}
let added = enforcer.store.add("g", values)
if added && enforcer.auto_build_role_links {
enforcer_build_role_links_raise(enforcer)
}
added
}
///|
/// Removes a grouping row from the `g` section.
pub fn Enforcer::remove_grouping_policy(
self : Enforcer,
values : Array[String],
) -> Result[Bool, CasbinError] {
Ok(enforcer_remove_grouping_policy_raise(self, values)) catch {
error => Err(error)
}
}
///|
fn enforcer_remove_grouping_policy_raise(
enforcer : Enforcer,
values : Array[String],
) -> Bool raise CasbinError {
let removed = enforcer.store.remove("g", values)
if removed && enforcer.auto_build_role_links {
enforcer_build_role_links_raise(enforcer)
}
removed
}
///|
/// Whether the `g` section contains the row.
pub fn Enforcer::has_grouping_policy(
self : Enforcer,
values : Array[String],
) -> Result[Bool, CasbinError] {
Ok(self.store.has("g", values))
}
///|
/// The rows of the `g` section, in insertion order.
pub fn Enforcer::get_grouping_policy(self : Enforcer) -> Array[Array[String]] {
self.store.rows("g")
}
///|
/// The `g` rows matched by `values` (empty string matches any field).
pub fn Enforcer::get_filtered_grouping_policy(
self : Enforcer,
field_index : Int,
values : Array[String],
) -> Array[Array[String]] {
self.filtered_rows("g", field_index, values)
}
///|
/// Removes every `g` row matched by `values`. Returns whether any row was
/// removed.
pub fn Enforcer::remove_filtered_grouping_policy(
self : Enforcer,
field_index : Int,
values : Array[String],
) -> Result[Bool, CasbinError] {
Ok(enforcer_remove_filtered_grouping_rows_raise(self, field_index, values)) catch {
error => Err(error)
}
}
///|
fn enforcer_remove_filtered_grouping_rows_raise(
enforcer : Enforcer,
field_index : Int,
values : Array[String],
) -> Bool raise CasbinError {
let removed = enforcer.remove_filtered_rows("g", field_index, values)
if removed && enforcer.auto_build_role_links {
enforcer_build_role_links_raise(enforcer)
}
removed
}
///|
/// The roles `user` has, directly.
pub fn Enforcer::get_roles_for_user(
self : Enforcer,
user : String,
domain? : String,
) -> Result[Array[String], CasbinError] {
Ok(enforcer_get_roles_for_user_raise(self, user, domain)) catch {
error => Err(error)
}
}
///|
fn enforcer_get_roles_for_user_raise(
enforcer : Enforcer,
user : String,
domain : String?,
) -> Array[String] raise CasbinError {
let manager = match enforcer.manager_for("g") {
Some(manager) => manager
None =>
raise casbin_error(
ModelValidation,
"role manager \"g\" is not initialized",
)
}
manager.get_roles(user, domain=enforcer.manager_domain("g", domain))
}
///|
/// The users of `role`, directly.
pub fn Enforcer::get_users_for_role(
self : Enforcer,
role : String,
domain? : String,
) -> Result[Array[String], CasbinError] {
Ok(enforcer_get_users_for_role_raise(self, role, domain)) catch {
error => Err(error)
}
}
///|
fn enforcer_get_users_for_role_raise(
enforcer : Enforcer,
role : String,
domain : String?,
) -> Array[String] raise CasbinError {
let manager = match enforcer.manager_for("g") {
Some(manager) => manager
None =>
raise casbin_error(
ModelValidation,
"role manager \"g\" is not initialized",
)
}
manager.get_users(role, domain=enforcer.manager_domain("g", domain))
}
///|
/// Whether `user` has the role `role`, directly.
pub fn Enforcer::has_role_for_user(
self : Enforcer,
user : String,
role : String,
domain? : String,
) -> Result[Bool, CasbinError] {
Ok(enforcer_has_role_for_user_raise(self, user, role, domain)) catch {
error => Err(error)
}
}
///|
fn enforcer_has_role_for_user_raise(
enforcer : Enforcer,
user : String,
role : String,
domain : String?,
) -> Bool raise CasbinError {
let roles = enforcer_get_roles_for_user_raise(enforcer, user, domain)
contains_name(roles, role)
}
///|
/// Adds `role` to `user`, optionally within `domain`.
pub fn Enforcer::add_role_for_user(
self : Enforcer,
user : String,
role : String,
domain? : String,
) -> Result[Bool, CasbinError] {
let values = match domain {
None => [user, role]
Some(value) => [user, role, value]
}
self.add_grouping_policy(values)
}
///|
/// Removes `role` from `user`.
pub fn Enforcer::delete_role_for_user(
self : Enforcer,
user : String,
role : String,
domain? : String,
) -> Result[Bool, CasbinError] {
let values = match domain {
None => [user, role]
Some(value) => [user, role, value]
}
self.remove_grouping_policy(values)
}
///|
/// Removes all roles of `user`, optionally within `domain`.
pub fn Enforcer::delete_roles_for_user(
self : Enforcer,
user : String,
domain? : String,
) -> Result[Bool, CasbinError] {
Ok(enforcer_delete_roles_for_user_raise(self, user, domain)) catch {
error => Err(error)
}
}
///|
fn enforcer_delete_roles_for_user_raise(
enforcer : Enforcer,
user : String,
domain : String?,
) -> Bool raise CasbinError {
let values = match domain {
None => [user]
Some(value) => [user, "", value]
}
enforcer_remove_filtered_grouping_rows_raise(enforcer, 0, values)
}
///|
/// Removes the user: every grouping row with the user as first field and
/// every policy row with the user as subject.
pub fn Enforcer::delete_user(
self : Enforcer,
user : String,
) -> Result[Bool, CasbinError] {
Ok(enforcer_delete_user_raise(self, user)) catch {
error => Err(error)
}
}
///|
fn enforcer_delete_user_raise(
enforcer : Enforcer,
user : String,
) -> Bool raise CasbinError {
let grouping_removed = enforcer_remove_filtered_grouping_rows_raise(
enforcer,
0,
[user],
)
let subject_index = match enforcer.field_index("sub") {
Some(index) => index
None => 0
}
let policy_removed = enforcer.remove_filtered_rows(
enforcer.policy_type,
subject_index,
[user],
)
grouping_removed || policy_removed
}
///|
/// Removes the role: grouping rows that reference it in either position and
/// policy rows that use it as subject.
pub fn Enforcer::delete_role(
self : Enforcer,
role : String,
) -> Result[Bool, CasbinError] {
Ok(enforcer_delete_role_raise(self, role)) catch {
error => Err(error)
}
}
///|
fn enforcer_delete_role_raise(
enforcer : Enforcer,
role : String,
) -> Bool raise CasbinError {
let as_user = enforcer_remove_filtered_grouping_rows_raise(enforcer, 0, [role])
let as_role = enforcer_remove_filtered_grouping_rows_raise(enforcer, 1, [role])
let subject_index = match enforcer.field_index("sub") {
Some(index) => index
None => 0
}
let policy_removed = enforcer.remove_filtered_rows(
enforcer.policy_type,
subject_index,
[role],
)
as_user || as_role || policy_removed
}
///|
/// Every role `user` has, direct and indirect, across all role managers.
pub fn Enforcer::get_implicit_roles_for_user(
self : Enforcer,
user : String,
domain? : String,
) -> Array[String] {
enforcer_implicit_roles_for_user(self, user, domain)
}
///|
fn enforcer_implicit_roles_for_user(
enforcer : Enforcer,
user : String,
domain : String?,
) -> Array[String] {
let result : Array[String] = []
for entry in enforcer.role_managers {
let manager = entry.1
let manager_domain = enforcer.manager_domain(entry.0, domain)
for role in manager.get_implicit_roles(user, domain=manager_domain) {
if !contains_name(result, role) {
result.push(role)
}
}
}
result
}
///|
/// Every user of `role`, direct and indirect, across all role managers.
pub fn Enforcer::get_implicit_users_for_role(
self : Enforcer,
role : String,
domain? : String,
) -> Array[String] {
let result : Array[String] = []
for entry in self.role_managers {
let manager = entry.1
let manager_domain = self.manager_domain(entry.0, domain)
for user in manager.get_implicit_users(role, domain=manager_domain) {
if !contains_name(result, user) {
result.push(user)
}
}
}
result
}
///|
/// The policy rows whose subject is `user` (and whose domain field is
/// `domain`, when given).
pub fn Enforcer::get_permissions_for_user(
self : Enforcer,
user : String,
domain? : String,
) -> Result[Array[Array[String]], CasbinError] {
Ok(enforcer_get_permissions_for_user_raise(self, user, domain)) catch {
error => Err(error)
}
}
///|
fn enforcer_get_permissions_for_user_raise(
enforcer : Enforcer,
user : String,
domain : String?,
) -> Array[Array[String]] raise CasbinError {
let arguments : Array[String] = []
for _i in 0.. index
None => 0
}
arguments[subject_index] = user
match domain {
Some(value) =>
match enforcer.field_index("dom") {
Some(index) => arguments[index] = value
None =>
raise casbin_error(
ModelValidation,
"the policy definition has no domain field",
)
}
None => ()
}
enforcer.filtered_rows(enforcer.policy_type, 0, arguments)
}
///|
/// The policy rows whose subject is `user` or one of the user's implicit
/// roles. With a domain, only rows for that domain are returned.
pub fn Enforcer::get_implicit_permissions_for_user(
self : Enforcer,
user : String,
domain? : String,
) -> Result[Array[Array[String]], CasbinError] {
Ok(enforcer_get_implicit_permissions_for_user_raise(self, user, domain)) catch {
error => Err(error)
}
}
///|
fn enforcer_get_implicit_permissions_for_user_raise(
enforcer : Enforcer,
user : String,
domain : String?,
) -> Array[Array[String]] raise CasbinError {
let allowed : Array[String] = [user]
for role in enforcer_implicit_roles_for_user(enforcer, user, domain) {
allowed.push(role)
}
let subject_index = match enforcer.field_index("sub") {
Some(index) => index
None => 0
}
let result : Array[Array[String]] = []
for row in enforcer.store.rows(enforcer.policy_type) {
if subject_index >= row.length() ||
!contains_name(allowed, row[subject_index]) {
continue
}
match domain {
None => result.push(row)
Some(value) =>
match enforcer.field_index("dom") {
Some(domain_index) =>
if domain_index < row.length() && row[domain_index] == value {
result.push(row)
}
None =>
raise casbin_error(
ModelValidation,
"the policy definition has no domain field",
)
}
}
}
result
}
///|
/// Removes every policy row whose subject is `user`.
pub fn Enforcer::delete_permissions_for_user(
self : Enforcer,
user : String,
) -> Result[Bool, CasbinError] {
let subject_index = match self.field_index("sub") {
Some(index) => index
None => 0
}
self.remove_filtered_policy(subject_index, [user])
}
///|
fn Enforcer::manager_for(self : Enforcer, key : String) -> RoleManager? {
for entry in self.role_managers {
if entry.0 == key {
return Some(entry.1)
}
}
None
}
///|
/// The domain to use for a manager: definitions with three tokens are
/// domain-aware, everything else lives in the default domain.
fn Enforcer::manager_domain(
self : Enforcer,
key : String,
domain : String?,
) -> String {
let token_count = match self.model.role_definition(key) {
Some(assertion) => assertion.tokens().length()
None => 2
}
if token_count >= 3 {
match domain {
Some(value) => value
None => ""
}
} else {
""
}
}
///|
/// The index of the policy token `p_`, if the policy definition has
/// it (mirrors Casbin's `GetFieldIndex` fallback).
fn Enforcer::field_index(self : Enforcer, field : String) -> Int? {
find_token(self.policy_tokens, self.policy_type + "_" + field)
}
///|
/// The rows of `key` whose fields from `field_index` on match `values`; an
/// empty string in `values` matches any field.
fn Enforcer::filtered_rows(
self : Enforcer,
key : String,
field_index : Int,
values : Array[String],
) -> Array[Array[String]] {
let result : Array[Array[String]] = []
for row in self.store.rows(key) {
let mut matched = true
for i in 0..= row.length() || row[field_index + i] != values[i] {
matched = false
break
}
}
}
if matched {
result.push(row)
}
}
result
}
///|
/// Removes the rows of `key` matched by `values`. Returns whether any row
/// was removed.
fn Enforcer::remove_filtered_rows(
self : Enforcer,
key : String,
field_index : Int,
values : Array[String],
) -> Bool {
let rows = self.filtered_rows(key, field_index, values)
let mut removed = false
for row in rows {
if self.store.remove(key, row) {
removed = true
}
}
removed
}