-
Notifications
You must be signed in to change notification settings - Fork 95
Expand file tree
/
Copy pathpermission.rs
More file actions
270 lines (231 loc) · 8.65 KB
/
permission.rs
File metadata and controls
270 lines (231 loc) · 8.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
use crate::error::CoreError;
use derive_more::Display;
use serde::Serialize;
use std::collections::{HashMap, HashSet};
#[derive(Serialize, Debug, Display, Eq, PartialEq, Clone, Copy, Hash)]
#[serde(transparent)]
#[display("{}", text_id)]
pub struct Permission {
text_id: &'static str,
#[serde(skip)]
bit: u16,
}
impl Permission {
pub const fn new(text_id: &'static str, bit: u16) -> Permission {
Permission { text_id, bit }
}
pub fn text_id(&self) -> &str {
self.text_id
}
pub fn bit(&self) -> u16 {
self.bit
}
}
impl From<Permission> for u16 {
fn from(perm: Permission) -> Self {
perm.bit
}
}
/// Structure containing all information about different [`Permission`] levels
/// of a pointercrate instance
///
/// Pointercrate's permission system is built on two concepts: Assignment and
/// implication
///
/// ## Assignment
/// If permission `A` _assigns_ permission `B` then a user with permission `A`
/// can modify the permission bit associated with `B` for _any user they can
/// access via the API_. The set of users that a user `X`can access via the API
/// is the set of users that have a permission bit set which `X` has the ability
/// to assign. The exception are users with the `ADMINISTRATOR` permission,
/// which can access all other users.
///
/// ### Example
///
/// Consider three permissions, `A`, `B` and `C`, and two users `X`and `Y`.
/// Assume the following relations hold:
/// - Permission `A` can assign permission `B` and `C`
/// - User `X` has permission `A`
/// - User `Y` has permission `B`
///
/// In this scenario, user `X` can
/// - access user `Y` via the API (because user `Y` has permission `B`, which
/// `X` can assign due to having permission `A`)
/// - grant user `Y` the permission `C` (because `X` can access `Y` and assign
/// `C`)
/// - revoke permission `B` from user `Y` (because `X` can access `Y` and assign
/// `B`)
///
/// Note that in the last case, after revoking permission `B` from `Y`, user `X`
/// will no longer be able to access `Y` (as `Y` no longer has any permissions
/// that `X` can assign). Particularly, **if you revoke all permissions you can
/// assign from some user, you will not be able to re-grant that user any
/// permissions**. Only an Administrator will be able to do so.
///
/// ## Implication
///
/// If permissions `C` _implies_ permission `D` then a user with permission `C`
/// will be able to perform all tasks that a user with permission `D` could
/// perform (e.g. an endpoint that explicitly checks via
/// [`PermissionsManager::require_permission`] that the requestor has permission
/// `D` will allow users with only permission `D` to perform requests). Note
/// that "tasks" above also includes assignment!
///
/// ### Example
///
/// Extend the above example with a new permission `D` and a new user `Z`, and
/// the following relations:
/// - Permission `D` implies permission `A`
/// - User `Z` has permission `D`
///
/// Then, user `Z` will be able to perform the same operations as user `X`
/// (w.r.t. assigning permissions and accessing users).
#[derive(Clone)]
pub struct PermissionsManager {
permissions: HashSet<Permission>,
implication_map: HashMap<Permission, HashSet<Permission>>,
assignable_map: HashMap<Permission, HashSet<Permission>>,
}
impl PermissionsManager {
pub fn new(permissions: Vec<Permission>) -> Self {
let mut permission_set = HashSet::new();
for perm in permissions {
permission_set.insert(perm);
}
PermissionsManager {
permissions: permission_set,
implication_map: HashMap::new(),
assignable_map: HashMap::new(),
}
}
pub fn merge_with(&mut self, other: PermissionsManager) {
for new_permission in &other.permissions {
if let Some(conflict) = self
.permissions
.iter()
.find(|&p| p.bit() == new_permission.bit() && p != new_permission)
{
panic!(
"Cannot merge permission managers, conflicting permissions {} and {}",
conflict, new_permission
)
}
}
self.permissions.extend(other.permissions);
self.implication_map.extend(other.implication_map);
self.assignable_map.extend(other.assignable_map);
}
// we should probably verify that added permissions are all part of what was in
// the constructor but whatever
pub fn assigns(mut self, perm1: Permission, perm2: Permission) -> Self {
self.assignable_map.entry(perm1).or_default().insert(perm2);
self
}
pub fn implies(mut self, perm1: Permission, perm2: Permission) -> Self {
self.implication_map.entry(perm1).or_default().insert(perm2);
self
}
pub fn implied_by(&self, permission: Permission) -> HashSet<Permission> {
let mut implied = HashSet::new();
implied.insert(permission);
if let Some(set) = self.implication_map.get(&permission) {
for perm in set {
implied.extend(self.implied_by(*perm));
}
}
implied
}
pub fn assignable_by(&self, permission: Permission) -> HashSet<Permission> {
let mut assignable = HashSet::new();
for perm in self.implied_by(permission) {
if let Some(set) = self.assignable_map.get(&perm) {
for perm in set {
assignable.insert(*perm);
}
}
}
assignable
}
pub fn implied_by_bits(&self, permission_bits: u16) -> HashSet<Permission> {
let mut implied = HashSet::new();
for perm in self.bits_to_permissions(permission_bits) {
if perm.bit & permission_bits == perm.bit {
implied.extend(self.implied_by(perm));
}
}
implied
}
pub fn assignable_by_bits(&self, permission_bits: u16) -> HashSet<Permission> {
let mut assignable = HashSet::new();
for perm in self.bits_to_permissions(permission_bits) {
assignable.extend(self.assignable_by(perm));
}
assignable
}
pub fn bits_to_permissions(&self, bits: u16) -> HashSet<Permission> {
let mut perms = HashSet::new();
for perm in &self.permissions {
if perm.bit() & bits == perm.bit() {
perms.insert(*perm);
}
}
perms
}
pub fn require_permission(&self, permissions_we_have: u16, permission_required: Permission) -> Result<(), CoreError> {
if !self.implied_by_bits(permissions_we_have).contains(&permission_required) {
return Err(CoreError::MissingPermissions {
required: permission_required,
});
}
Ok(())
}
pub fn is_elevated(&self, permission_bits: u16) -> bool {
permission_bits > 0
}
}
#[cfg(test)]
mod test {
// copied from https://riptutorial.com/rust/example/4149/create-a-hashset-macro because im lazy as fuck
macro_rules! set {
( $( $x:expr ),* $(,)? ) => { // Match zero or more comma delimited items
{
let mut temp_set = HashSet::new(); // Create a mutable HashSet
$(
temp_set.insert($x); // Insert each item matched into the HashSet
)*
temp_set // Return the populated HashSet
}
};
}
use crate::permission::{Permission, PermissionsManager};
use std::collections::HashSet;
const PERM1: Permission = Permission::new("1", 0x1);
const PERM2: Permission = Permission::new("2", 0x2);
const PERM3: Permission = Permission::new("3", 0x4);
const PERM4: Permission = Permission::new("4", 0x8);
const PERM5: Permission = Permission::new("5", 0x10);
const PERM6: Permission = Permission::new("6", 0x20);
fn permission_manager() -> PermissionsManager {
PermissionsManager::new(vec![PERM1, PERM2, PERM3, PERM4, PERM5])
.implies(PERM1, PERM2)
.implies(PERM2, PERM3)
.implies(PERM4, PERM5)
.assigns(PERM4, PERM2)
.assigns(PERM2, PERM3)
.assigns(PERM4, PERM5)
.assigns(PERM5, PERM6)
}
#[test]
fn test_implication() {
assert_eq!(permission_manager().implied_by(PERM1), set![PERM1, PERM2, PERM3]);
assert_eq!(permission_manager().implied_by(PERM4), set![PERM4, PERM5]);
assert_eq!(
permission_manager().implied_by_bits(0x1 | 0x8),
set![PERM1, PERM2, PERM3, PERM4, PERM5,]
);
}
#[test]
fn test_assignment() {
assert_eq!(permission_manager().assignable_by(PERM4), set![PERM2, PERM5, PERM6]);
}
}