From d8d5650cc4d232215dce109f8aa3f0161079bf42 Mon Sep 17 00:00:00 2001 From: mrw1593 Date: Sat, 3 Jun 2023 11:35:22 -0400 Subject: Set up scoping service --- src/scopes/admin.rs | 28 ++++++++++++ src/scopes/mod.rs | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 src/scopes/admin.rs create mode 100644 src/scopes/mod.rs (limited to 'src/scopes') 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> { + Ok(Self) + } + + fn has_user_permission(&self, _: &User, _: &Action) -> bool { + true + } + + fn has_client_permission(&self, _: &User, _: &Action) -> 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 { + 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> + 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) -> 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) -> bool; +} + +pub struct ParseScopeError { + scope: Box, + 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), + InvalidModifiers(Box), +} + +fn parse_scope(scope: &str) -> Result, 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>, ParseScopeError> { + scopes + .split_whitespace() + .map(|scope| parse_scope(scope)) + .collect() +} + +fn parse_scopes_errors( + results: &[Result, 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, + client_scopes: &str, +) -> Result { + 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_scopes: &str, +) -> Result { + let scopes = parse_scopes(client_scopes)?; + + for scope in scopes { + if scope.has_client_permission(&user, &action) { + return Ok(true); + } + } + + Ok(false) +} -- cgit v1.2.3