1 //! Declarative permission system
3 //! A declarative way to define API access permissions.
5 use std
::collections
::HashMap
;
10 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
14 /// Allow the whole World, no authentication required
16 /// Allow any authenticated user
18 /// Allow access for the specified user
20 /// Allow access if specified param matches logged in user
21 UserParam(&'
static str),
22 /// Allow access for the specified group of users
24 /// Use a parameter value as userid to run sub-permission tests.
25 WithParam(&'
static str, &'
static Permission
),
26 /// Check privilege/role on the specified path. The boolean
27 /// attribute specifies if you want to allow partial matches (u64
28 /// interpreted as bitmask).
29 Privilege(&'
static [&'
static str], u64, bool
),
30 /// Allow access if all sub-permissions match
31 And(&'
static [&'
static Permission
]),
32 /// Allow access if any sub-permissions match
33 Or(&'
static [&'
static Permission
]),
36 impl fmt
::Debug
for Permission
{
37 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
39 Permission
::Superuser
=> f
.write_str("Superuser"),
40 Permission
::World
=> f
.write_str("World"),
41 Permission
::Anybody
=> f
.write_str("Anybody"),
42 Permission
::User(ref userid
) => write
!(f
, "User({})", userid
),
43 Permission
::UserParam(param_name
) => write
!(f
, "UserParam({})", param_name
),
44 Permission
::Group(ref group
) => write
!(f
, "Group({})", group
),
45 Permission
::WithParam(param_name
, subtest
) => {
46 write
!(f
, "WithParam({}, {:?})", param_name
, subtest
)
48 Permission
::Privilege(path
, privs
, partial
) => {
49 write
!(f
, "Privilege({:?}, {:0b}, {})", path
, privs
, partial
)
51 Permission
::And(list
) => {
52 f
.write_str("And(\n")?
;
53 for subtest
in list
.iter() {
54 writeln
!(f
, " {:?}", subtest
)?
;
58 Permission
::Or(list
) => {
59 f
.write_str("Or(\n")?
;
60 for subtest
in list
.iter() {
61 writeln
!(f
, " {:?}", subtest
)?
;
69 /// Trait to query user information (used by check_api_permission)
70 pub trait UserInformation
{
71 fn is_superuser(&self, userid
: &str) -> bool
;
72 fn is_group_member(&self, userid
: &str, group
: &str) -> bool
;
73 fn lookup_privs(&self, userid
: &str, path
: &[&str]) -> u64;
76 impl<T
: UserInformation
> UserInformation
for std
::sync
::Arc
<T
> {
77 fn is_superuser(&self, userid
: &str) -> bool
{
78 self.deref().is_superuser(userid
)
80 fn is_group_member(&self, userid
: &str, group
: &str) -> bool
{
81 self.deref().is_group_member(userid
, group
)
83 fn lookup_privs(&self, userid
: &str, path
: &[&str]) -> u64 {
84 self.deref().lookup_privs(userid
, path
)
88 /// Example implementation to check access permissions
90 /// This implementation supports URI variables in Privilege path
91 /// components, i.e. '{storage}'. We replace this with actual
92 /// parameter values before calling lookup_privs().
93 pub fn check_api_permission(
96 param
: &HashMap
<String
, String
>,
97 info
: &dyn UserInformation
,
99 if let Some(userid
) = userid
{
100 if info
.is_superuser(userid
) {
105 check_api_permission_tail(perm
, userid
, param
, info
)
108 // some of them are deeply nested
109 #[allow(clippy::needless_return)]
110 fn check_api_permission_tail(
112 userid
: Option
<&str>,
113 param
: &HashMap
<String
, String
>,
114 info
: &dyn UserInformation
,
117 Permission
::World
=> return true,
118 Permission
::Anybody
=> {
119 return userid
.is_some();
121 Permission
::Superuser
=> match userid
{
122 None
=> return false,
123 Some(userid
) => return info
.is_superuser(userid
),
125 Permission
::User(expected_userid
) => match userid
{
126 None
=> return false,
127 Some(ref userid
) => return userid
== expected_userid
,
129 Permission
::UserParam(param_name
) => match (userid
, param
.get(¶m_name
.to_string())) {
130 (None
, _
) => return false,
131 (_
, None
) => return false,
132 (Some(ref userid
), Some(ref expected
)) => return userid
== expected
,
134 Permission
::Group(expected_group
) => match userid
{
135 None
=> return false,
136 Some(userid
) => return info
.is_group_member(userid
, expected_group
),
138 Permission
::WithParam(param_name
, subtest
) => {
139 return check_api_permission(
141 param
.get(*param_name
).map(|v
| v
.as_str()),
146 Permission
::Privilege(path
, expected_privs
, partial
) => {
148 let mut new_path
: Vec
<&str> = Vec
::new();
149 for comp
in path
.iter() {
150 if comp
.starts_with('{') && comp.ends_with('}'
) {
151 let param_name
= unsafe { comp.get_unchecked(1..comp.len() - 1) }
;
152 match param
.get(param_name
) {
153 None
=> return false,
155 new_path
.push(value
);
163 None
=> return false,
165 let privs
= info
.lookup_privs(userid
, &new_path
);
170 return (expected_privs
& privs
) != 0;
172 return (*expected_privs
& privs
) == *expected_privs
;
177 Permission
::And(list
) => {
178 for subtest
in list
.iter() {
179 if !check_api_permission_tail(subtest
, userid
, param
, info
) {
186 Permission
::Or(list
) => {
187 for subtest
in list
.iter() {
188 if check_api_permission_tail(subtest
, userid
, param
, info
) {
200 use serde_json
::{json, Value}
;
202 use crate::permission
::*;
204 struct MockedUserInfo
{
209 impl UserInformation
for MockedUserInfo
{
210 fn is_superuser(&self, userid
: &str) -> bool
{
214 fn is_group_member(&self, userid
: &str, group
: &str) -> bool
{
215 if let Some(groups
) = self.groups
[userid
].as_array() {
216 return groups
.contains(&Value
::from(group
));
222 fn lookup_privs(&self, userid
: &str, path
: &[&str]) -> u64 {
223 let path
= format
!("/{}", path
.join("/"));
224 if let Some(users
) = self.privs
.get(path
) {
225 if let Some(privilege
) = users
.get(userid
) {
226 return privilege
.as_u64().unwrap();
235 fn test_privileges() {
236 let userinfo
= MockedUserInfo
{
259 let mut param
= HashMap
::new();
260 param
.insert("user".to_string(), "user1".to_string());
261 param
.insert("datastore".to_string(), "foo".to_string());
263 let test_check
= |perm
: &Permission
, userid
: Option
<&str>, should_succeed
: bool
| {
264 println
!("{:?} on {:?}: {}", userid
, perm
, should_succeed
);
266 check_api_permission(perm
, userid
, ¶m
, &userinfo
),
271 test_check(&Permission
::Superuser
, Some("root"), true);
272 test_check(&Permission
::Superuser
, Some("user1"), false);
273 test_check(&Permission
::Superuser
, None
, false);
275 test_check(&Permission
::World
, Some("root"), true);
276 test_check(&Permission
::World
, Some("user1"), true);
277 test_check(&Permission
::World
, None
, true);
279 test_check(&Permission
::Anybody
, Some("root"), true);
280 test_check(&Permission
::Anybody
, Some("user1"), true);
281 test_check(&Permission
::Anybody
, None
, false);
283 test_check(&Permission
::User("user1"), Some("root"), true);
284 test_check(&Permission
::User("user1"), Some("user1"), true);
285 test_check(&Permission
::User("user1"), Some("user2"), false);
286 test_check(&Permission
::User("user1"), None
, false);
288 test_check(&Permission
::Group("group1"), Some("root"), true);
289 test_check(&Permission
::Group("group1"), Some("user1"), true);
290 test_check(&Permission
::Group("group1"), Some("user2"), false);
291 test_check(&Permission
::Group("group1"), None
, false);
294 &Permission
::WithParam("user", &Permission
::User("root")),
299 &Permission
::WithParam("user", &Permission
::User("user1")),
304 &Permission
::WithParam("user", &Permission
::User("user2")),
309 &Permission
::WithParam("user", &Permission
::User("")),
315 &Permission
::And(&[&Permission
::User("user1"), &Permission
::Group("group2")]),
320 &Permission
::And(&[&Permission
::User("user1"), &Permission
::Group("group2")]),
325 &Permission
::And(&[&Permission
::User("user1"), &Permission
::Group("group1")]),
330 &Permission
::And(&[&Permission
::User("user1"), &Permission
::Group("group2")]),
336 &Permission
::Or(&[&Permission
::User("user1"), &Permission
::Group("group2")]),
341 &Permission
::Or(&[&Permission
::User("user1"), &Permission
::Group("group2")]),
346 &Permission
::Or(&[&Permission
::User("user1"), &Permission
::Group("group1")]),
351 &Permission
::Or(&[&Permission
::User("user1"), &Permission
::Group("group2")]),
356 test_check(&Permission
::Privilege(&[], 0b11, true), Some("root"), true);
357 test_check(&Permission
::Privilege(&[], 0b11, true), Some("user1"), true);
359 &Permission
::Privilege(&[], 0b11, false),
364 &Permission
::Privilege(&[], 0b11, true),
369 &Permission
::Privilege(&[], 0b11, false),
373 test_check(&Permission
::Privilege(&[], 0b11, true), None
, false);
374 test_check(&Permission
::Privilege(&[], 0b11, false), None
, false);
377 &Permission
::Privilege(&["datastore"], 0b01, true),
382 &Permission
::Privilege(&["datastore"], 0b01, true),
387 &Permission
::Privilege(&["datastore"], 0b01, true),
393 &Permission
::Privilege(&["datastore", "{datastore}"], 0b01, true),
398 &Permission
::Privilege(&["datastore", "{datastore}"], 0b01, true),
403 &Permission
::Privilege(&["datastore", "{datastore}"], 0b01, true),