|
| 1 | +use itertools::Itertools as _; |
1 | 2 | use std::{collections::HashSet, future::Future, sync::Arc};
|
2 | 3 |
|
3 | 4 | use tracing::debug;
|
4 | 5 | use tracing::Level;
|
5 | 6 |
|
6 |
| -use crate::roles::{BuiltinRoleSet, RoleConfig}; |
| 7 | +use crate::roles::{BuiltinRoleSet, RoleConfig, RoleIdentifier}; |
7 | 8 |
|
8 | 9 | pub type UserIdentity = String;
|
9 | 10 | pub type UserName = String;
|
@@ -47,8 +48,8 @@ pub trait StorageDriver: Clone {
|
47 | 48 | roles_config: &RoleConfig<Self::BuiltinRole>,
|
48 | 49 | ) -> impl Future<Output = Result<HashSet<Self::BuiltinRole>, Self::Error>> + Send;
|
49 | 50 |
|
50 |
| - fn ensure_subject_roles( |
51 |
| - &self, |
| 51 | + fn ensure_subject_roles<'a>( |
| 52 | + &'a self, |
52 | 53 | subject_id: i64,
|
53 | 54 | roles_config: &RoleConfig<Self::BuiltinRole>,
|
54 | 55 | roles: HashSet<Self::BuiltinRole>,
|
@@ -100,23 +101,101 @@ impl<S: StorageDriver> Authorizer<S> {
|
100 | 101 | }
|
101 | 102 | }
|
102 | 103 |
|
| 104 | + pub fn user_id(&self) -> i64 { |
| 105 | + self.user_id |
| 106 | + } |
| 107 | + |
103 | 108 | pub fn is_superuser(&self) -> bool {
|
104 | 109 | self.roles_config.is_superuser()
|
105 | 110 | }
|
106 | 111 |
|
| 112 | + /// Returns whether a user with some id exists |
| 113 | + #[tracing::instrument(skip_all, fields(user_id = %user_id), ret(level = Level::DEBUG), err)] |
| 114 | + pub async fn user_exists(&self, user_id: i64) -> Result<bool, S::Error> { |
| 115 | + self.storage |
| 116 | + .get_user_info(user_id) |
| 117 | + .await |
| 118 | + .map(|x| x.is_some()) |
| 119 | + } |
| 120 | + |
107 | 121 | /// Check that the user has all the required builting roles
|
108 | 122 | #[tracing::instrument(skip_all, fields(user = %self.user, user_roles = ?self.user_roles, ?required_roles), ret(level = Level::DEBUG), err)]
|
109 | 123 | pub async fn check_roles(
|
110 | 124 | &self,
|
111 | 125 | required_roles: HashSet<S::BuiltinRole>,
|
112 | 126 | ) -> Result<bool, S::Error> {
|
113 | 127 | if self.is_superuser() {
|
114 |
| - tracing::debug!("role checking skipped for superuser"); |
| 128 | + tracing::warn!("role checking skipped for superuser"); |
115 | 129 | return Ok(true);
|
116 | 130 | }
|
117 | 131 |
|
118 | 132 | Ok(required_roles.is_subset(&self.user_roles))
|
119 | 133 | }
|
| 134 | + |
| 135 | + #[tracing::instrument(skip_all, fields(user_id, auth_user = %self.user, user_roles = ?self.user_roles), ret(level = Level::DEBUG), err)] |
| 136 | + pub async fn infer_application_roles( |
| 137 | + &self, |
| 138 | + user_id: i64, |
| 139 | + ) -> Result<Vec<RoleIdentifier>, S::Error> { |
| 140 | + if self.is_superuser() { |
| 141 | + return Ok(self.roles_config.application_roles().cloned().collect_vec()); |
| 142 | + } |
| 143 | + |
| 144 | + let resolved_roles = &self.roles_config.resolved_roles; |
| 145 | + let user_roles = self |
| 146 | + .storage |
| 147 | + .fetch_subject_roles(user_id, &self.roles_config) |
| 148 | + .await?; |
| 149 | + |
| 150 | + let app_roles = resolved_roles |
| 151 | + .iter() |
| 152 | + .filter(|(_, builtins)| user_roles.is_superset(builtins)) |
| 153 | + .map(|(app_role, _)| app_role) |
| 154 | + .cloned() |
| 155 | + .collect_vec(); |
| 156 | + |
| 157 | + Ok(app_roles) |
| 158 | + } |
| 159 | + |
| 160 | + #[tracing::instrument(skip_all, fields(user_id, auth_user = %self.user, user_roles = ?self.user_roles), ret(level = Level::DEBUG), err)] |
| 161 | + pub async fn user_builtin_roles( |
| 162 | + &self, |
| 163 | + user_id: i64, |
| 164 | + ) -> Result<HashSet<S::BuiltinRole>, S::Error> { |
| 165 | + let user_roles = self |
| 166 | + .storage |
| 167 | + .fetch_subject_roles(user_id, &self.roles_config) |
| 168 | + .await?; |
| 169 | + Ok(user_roles.clone()) |
| 170 | + } |
| 171 | + |
| 172 | + #[tracing::instrument(skip_all, fields(user_id, auth_user = %self.user, ?roles, role_config = ?self.roles_config), ret(level = Level::DEBUG), err)] |
| 173 | + pub async fn grant_roles( |
| 174 | + &mut self, |
| 175 | + user_id: i64, |
| 176 | + roles: HashSet<S::BuiltinRole>, |
| 177 | + ) -> Result<(), S::Error> { |
| 178 | + self.storage |
| 179 | + .ensure_subject_roles(user_id, &self.roles_config, roles.clone()) |
| 180 | + .await?; |
| 181 | + self.user_roles.extend(roles); |
| 182 | + Ok(()) |
| 183 | + } |
| 184 | + |
| 185 | + #[tracing::instrument(skip_all, fields(user_id, auth_user = %self.user, ?roles, role_config = ?self.roles_config), ret(level = Level::DEBUG), err)] |
| 186 | + pub async fn strip_roles( |
| 187 | + &mut self, |
| 188 | + user_id: i64, |
| 189 | + roles: HashSet<S::BuiltinRole>, |
| 190 | + ) -> Result<(), S::Error> { |
| 191 | + let removed_roles = self |
| 192 | + .storage |
| 193 | + .remove_subject_roles(user_id, &self.roles_config, roles.clone()) |
| 194 | + .await?; |
| 195 | + tracing::debug!(?removed_roles, "removed roles"); |
| 196 | + self.user_roles.retain(|r| !roles.contains(r)); |
| 197 | + Ok(()) |
| 198 | + } |
120 | 199 | }
|
121 | 200 |
|
122 | 201 | impl<S: StorageDriver> std::fmt::Debug for Authorizer<S> {
|
@@ -288,5 +367,26 @@ mod tests {
|
288 | 367 | .collect();
|
289 | 368 | Ok(removed_roles)
|
290 | 369 | }
|
| 370 | + |
| 371 | + async fn get_user_id(&self, user_info: &UserInfo) -> Result<Option<i64>, Self::Error> { |
| 372 | + self.users |
| 373 | + .lock() |
| 374 | + .unwrap() |
| 375 | + .get(&user_info.identity) |
| 376 | + .map(|id| Ok(Some(*id))) |
| 377 | + .unwrap_or_else(|| Ok(None)) |
| 378 | + } |
| 379 | + |
| 380 | + async fn get_user_info(&self, user_id: i64) -> Result<Option<UserInfo>, Self::Error> { |
| 381 | + let users = self.users.lock().unwrap(); |
| 382 | + let user_info = users |
| 383 | + .iter() |
| 384 | + .find(|(_, id)| **id == user_id) |
| 385 | + .map(|(identity, _)| UserInfo { |
| 386 | + identity: identity.clone(), |
| 387 | + name: "Mocked User".to_owned(), |
| 388 | + }); |
| 389 | + async move { Ok(user_info) } |
| 390 | + } |
291 | 391 | }
|
292 | 392 | }
|
0 commit comments