alibi_plugins/admin/
access.rs1use super::AdminConfig;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Default, PartialEq, Eq)]
8pub struct RolePermissions {
9 pub permissions: HashMap<String, Vec<String>>,
11}
12
13impl RolePermissions {
14 #[must_use]
16 pub fn new() -> Self {
17 Self::default()
18 }
19
20 #[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}