summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main.rs1
-rw-r--r--src/scopes/admin.rs28
-rw-r--r--src/scopes/mod.rs128
3 files changed, 157 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
index c288c7a..183dca2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,6 +8,7 @@ use exun::*;
mod api;
mod models;
mod resources;
+mod scopes;
mod services;
use resources::*;
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)
+}