alibi_plugins/admin/
access.rs1use super::AdminConfig;
4use crate::access::{AccessControl, create_access_control};
5use std::collections::HashMap;
6
7pub const DEFAULT_STATEMENTS: &[(&str, &[&str])] = &[
12 (
13 "user",
14 &[
15 "create",
16 "list",
17 "set-role",
18 "ban",
19 "impersonate",
20 "impersonate-admins",
21 "delete",
22 "set-password",
23 "set-email",
24 "get",
25 "update",
26 ],
27 ),
28 ("session", &["list", "revoke", "delete"]),
29];
30
31#[must_use]
33pub fn default_ac() -> AccessControl {
34 create_access_control(
35 DEFAULT_STATEMENTS
36 .iter()
37 .map(|(resource, actions)| {
38 (
39 (*resource).to_owned(),
40 actions.iter().map(|action| (*action).to_owned()).collect(),
41 )
42 })
43 .collect(),
44 )
45}
46
47#[derive(Debug, Clone, Default, PartialEq, Eq)]
49pub struct RolePermissions {
50 pub permissions: HashMap<String, Vec<String>>,
52}
53
54impl RolePermissions {
55 #[must_use]
57 pub fn new() -> Self {
58 Self::default()
59 }
60
61 #[must_use]
64 pub fn allow<I, S>(mut self, resource: impl Into<String>, actions: I) -> Self
65 where
66 I: IntoIterator<Item = S>,
67 S: Into<String>,
68 {
69 _ = self.permissions.insert(
70 resource.into(),
71 actions.into_iter().map(Into::into).collect(),
72 );
73 self
74 }
75
76 fn allows(&self, requested: &HashMap<String, Vec<String>>) -> bool {
77 use crate::access::{Connector, authorize};
78 authorize(
79 |resource| self.permissions.get(resource).map(Vec::as_slice),
80 requested
81 .iter()
82 .map(|(resource, actions)| (resource.as_str(), actions.as_slice(), Connector::And)),
83 Connector::And,
84 )
85 .success()
86 }
87}
88
89#[must_use]
93pub fn admin_role() -> RolePermissions {
94 DEFAULT_STATEMENTS
95 .iter()
96 .fold(RolePermissions::new(), |role, (resource, actions)| {
97 role.allow(
98 *resource,
99 actions
100 .iter()
101 .copied()
102 .filter(|action| *resource != "user" || *action != "impersonate-admins"),
103 )
104 })
105}
106
107#[must_use]
109pub fn user_role() -> RolePermissions {
110 RolePermissions::new()
111}
112
113#[must_use]
125pub fn default_roles() -> HashMap<String, RolePermissions> {
126 HashMap::from([
127 ("admin".to_owned(), admin_role()),
128 ("user".to_owned(), user_role()),
129 ])
130}
131
132fn configured_roles(config: &AdminConfig) -> HashMap<String, RolePermissions> {
133 config.roles.clone().unwrap_or_else(default_roles)
134}
135
136fn role_names<'a>(role: Option<&'a str>, default_role: &'a str) -> Vec<&'a str> {
137 role.filter(|role| !role.is_empty())
138 .or_else(|| (!default_role.is_empty()).then_some(default_role))
139 .unwrap_or("user")
140 .split(',')
141 .collect()
142}
143
144pub(super) fn is_admin_user_id(user_id: Option<&str>, config: &AdminConfig) -> bool {
145 user_id.is_some_and(|user_id| {
146 config
147 .admin_user_ids
148 .as_ref()
149 .is_some_and(|ids| ids.iter().any(|id| id == user_id))
150 })
151}
152
153pub(super) fn has_permission(
154 user_id: Option<&str>,
155 role: Option<&str>,
156 config: &AdminConfig,
157 requested: &HashMap<String, Vec<String>>,
158) -> bool {
159 if is_admin_user_id(user_id, config) {
160 return true;
161 }
162
163 let roles = configured_roles(config);
164 role_names(role, &config.default_role)
165 .into_iter()
166 .filter_map(|role| roles.get(role))
167 .any(|role| role.allows(requested))
168}
169
170pub(super) fn is_admin_role(role: Option<&str>, config: &AdminConfig) -> bool {
171 role_names(role, &config.default_role)
172 .into_iter()
173 .any(|role| {
174 config.admin_roles.as_ref().map_or_else(
175 || role == "admin",
176 |admins| admins.iter().any(|admin| admin.trim() == role),
177 )
178 })
179}