diff options
| author | mrw1593 <botahamec@outlook.com> | 2023-06-03 11:35:22 -0400 |
|---|---|---|
| committer | mrw1593 <botahamec@outlook.com> | 2023-06-03 11:35:22 -0400 |
| commit | d8d5650cc4d232215dce109f8aa3f0161079bf42 (patch) | |
| tree | a565b09261495989137fcebf38775438e3e96038 /src/scopes | |
| parent | 346978d0b6e1a433b108358ff841a7ece938db7a (diff) | |
Set up scoping service
Diffstat (limited to 'src/scopes')
| -rw-r--r-- | src/scopes/admin.rs | 28 | ||||
| -rw-r--r-- | src/scopes/mod.rs | 128 |
2 files changed, 156 insertions, 0 deletions
diff --git a/src/scopes/admin.rs b/src/scopes/admin.rs new file mode 100644 index 0000000..1e13b85 --- /dev/null +++ b/src/scopes/admin.rs @@ -0,0 +1,28 @@ +use std::fmt::{self, Display}; + +use crate::models::{client::Client, user::User}; + +use super::{Action, Scope}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Admin; + +impl Display for Admin { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("admin") + } +} + +impl Scope for Admin { + fn parse_modifiers(_modifiers: &str) -> Result<Self, Box<str>> { + Ok(Self) + } + + fn has_user_permission(&self, _: &User, _: &Action<User>) -> bool { + true + } + + fn has_client_permission(&self, _: &User, _: &Action<Client>) -> bool { + true + } +} diff --git a/src/scopes/mod.rs b/src/scopes/mod.rs new file mode 100644 index 0000000..fb7780f --- /dev/null +++ b/src/scopes/mod.rs @@ -0,0 +1,128 @@ +use std::collections::HashSet; + +use self::admin::Admin; +use crate::models::{client::Client, user::User}; + +mod admin; + +/// The action which was attempted on a resource +pub enum Action<T> { + Create(T), + Read(T), + Update(T, T), + Delete(T), +} + +trait ScopeSuperSet { + fn is_superset_of(&self, other: &Self) -> bool; +} + +trait Scope: ToString { + /// Parse a scope of the format: `{Scope::NAME}:{modifiers}` + fn parse_modifiers(modifiers: &str) -> Result<Self, Box<str>> + where + Self: Sized; + + /// Returns `true` if and only if the given `user` is allowed to take the + /// given `action` with this scope + fn has_user_permission(&self, user: &User, action: &Action<User>) -> bool; + + // Returns `true` if and only if the given `user` is allowed to take the + /// given `action` with this scope + fn has_client_permission(&self, user: &User, action: &Action<Client>) -> bool; +} + +pub struct ParseScopeError { + scope: Box<str>, + error: ParseScopeErrorType, +} + +impl ParseScopeError { + fn invalid_type(scope: &str, scope_type: &str) -> Self { + let scope = scope.into(); + let error = ParseScopeErrorType::InvalidType(scope_type.into()); + Self { scope, error } + } +} + +pub enum ParseScopeErrorType { + InvalidType(Box<str>), + InvalidModifiers(Box<str>), +} + +fn parse_scope(scope: &str) -> Result<Box<dyn Scope>, ParseScopeError> { + let mut split = scope.split(':'); + let scope_type = split.next().unwrap(); + let _modifiers: String = split.collect(); + + match scope_type { + "admin" => Ok(Box::new(Admin)), + _ => Err(ParseScopeError::invalid_type(scope, scope_type)), + } +} + +fn parse_scopes(scopes: &str) -> Result<Vec<Box<dyn Scope>>, ParseScopeError> { + scopes + .split_whitespace() + .map(|scope| parse_scope(scope)) + .collect() +} + +fn parse_scopes_errors( + results: &[Result<Box<dyn Scope>, ParseScopeError>], +) -> Vec<&ParseScopeError> { + let mut errors = Vec::with_capacity(results.len()); + for result in results { + if let Err(pse) = result { + errors.push(pse) + } + } + + errors +} + +/// Returns `true` if and only if all values in `left_scopes` are contained in +/// `right_scopes`. +pub fn is_subset_of(left_scopes: &str, right_scopes: &str) -> bool { + let right_scopes: HashSet<&str> = right_scopes.split_whitespace().collect(); + + for scope in left_scopes.split_whitespace() { + if !right_scopes.contains(scope) { + return false; + } + } + + true +} + +pub fn has_user_permission( + user: User, + action: Action<User>, + client_scopes: &str, +) -> Result<bool, ParseScopeError> { + let scopes = parse_scopes(client_scopes)?; + + for scope in scopes { + if scope.has_user_permission(&user, &action) { + return Ok(true); + } + } + + Ok(false) +} + +pub fn has_client_permission( + user: User, + action: Action<Client>, + client_scopes: &str, +) -> Result<bool, ParseScopeError> { + let scopes = parse_scopes(client_scopes)?; + + for scope in scopes { + if scope.has_client_permission(&user, &action) { + return Ok(true); + } + } + + Ok(false) +} |
