Skip to main content

alibi_plugins/admin/
access.rs

1//! Permission helpers for the admin plugin.
2
3use super::AdminConfig;
4use std::collections::HashMap;
5
6/// Role-based permission grants for the admin plugin.
7#[derive(Debug, Clone, Default, PartialEq, Eq)]
8pub struct RolePermissions {
9    /// Resource name -> allowed actions.
10    pub permissions: HashMap<String, Vec<String>>,
11}
12
13impl RolePermissions {
14    /// Create an empty role definition.
15    #[must_use]
16    pub fn new() -> Self {
17        Self::default()
18    }
19
20    /// Allow a set of actions for one resource.
21    #[must_use]
22    pub fn allow<I, S>(mut self, resource: impl Into<String>, actions: I) -> Self
23    where
24        I: IntoIterator<Item = S>,
25        S: Into<String>,
26    {
27        _ = self.permissions.insert(
28            resource.into(),
29            actions.into_iter().map(Into::into).collect(),
30        );
31        self
32    }
33
34    fn allows(&self, requested: &HashMap<String, Vec<String>>) -> bool {
35        use crate::access::{Connector, authorize};
36        authorize(
37            |resource| self.permissions.get(resource).map(Vec::as_slice),
38            requested
39                .iter()
40                .map(|(resource, actions)| (resource.as_str(), actions.as_slice(), Connector::And)),
41            Connector::And,
42        )
43        .success()
44    }
45}
46
47pub(super) fn default_roles() -> HashMap<String, RolePermissions> {
48    HashMap::from([
49        (
50            "admin".to_owned(),
51            RolePermissions::new()
52                .allow(
53                    "user",
54                    [
55                        "create",
56                        "list",
57                        "set-role",
58                        "ban",
59                        "impersonate",
60                        "delete",
61                        "set-password",
62                        "set-email",
63                        "get",
64                        "update",
65                    ],
66                )
67                .allow("session", ["list", "revoke", "delete"]),
68        ),
69        ("user".to_owned(), RolePermissions::new()),
70    ])
71}
72
73fn configured_roles(config: &AdminConfig) -> HashMap<String, RolePermissions> {
74    config.roles.clone().unwrap_or_else(default_roles)
75}
76
77fn role_names<'a>(role: Option<&'a str>, default_role: &'a str) -> Vec<&'a str> {
78    role.filter(|role| !role.is_empty())
79        .or_else(|| (!default_role.is_empty()).then_some(default_role))
80        .unwrap_or("user")
81        .split(',')
82        .collect()
83}
84
85pub(super) fn is_admin_user_id(user_id: Option<&str>, config: &AdminConfig) -> bool {
86    user_id.is_some_and(|user_id| {
87        config
88            .admin_user_ids
89            .as_ref()
90            .is_some_and(|ids| ids.iter().any(|id| id == user_id))
91    })
92}
93
94pub(super) fn has_permission(
95    user_id: Option<&str>,
96    role: Option<&str>,
97    config: &AdminConfig,
98    requested: &HashMap<String, Vec<String>>,
99) -> bool {
100    if is_admin_user_id(user_id, config) {
101        return true;
102    }
103
104    let roles = configured_roles(config);
105    role_names(role, &config.default_role)
106        .into_iter()
107        .filter_map(|role| roles.get(role))
108        .any(|role| role.allows(requested))
109}
110
111pub(super) fn is_admin_role(role: Option<&str>, config: &AdminConfig) -> bool {
112    role_names(role, &config.default_role)
113        .into_iter()
114        .any(|role| {
115            config.admin_roles.as_ref().map_or_else(
116                || role == "admin",
117                |admins| admins.iter().any(|admin| admin.trim() == role),
118            )
119        })
120}