use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use thiserror::Error;
use tracing::{debug, warn};
use uuid::Uuid;
use crate::{
drivers::{Capability, session::SessionLifecycleCapability},
guest_data::GuestError,
registry::ResourceId,
};
type Result<T, E = SessionError> = std::result::Result<T, E>;
pub struct Session {
id: Uuid,
parent: Uuid,
entitlements: HashMap<Capability, ResourceScope>,
_pubkey: [u8; 32],
}
pub enum ResourceScope {
None,
Some(HashSet<ResourceId>),
Any,
}
#[derive(Error, Debug)]
pub enum SessionError {
#[error("invalid payload signature")]
InvalidSignature,
#[error("session not authorised to perform this action")]
Unauthorised,
#[error("attempted to create a session with entitlements beyond its own")]
EntitlementScope,
#[error("attempted to revoke a resource from 'Any' scope")]
RevokeOnAny,
}
impl Session {
pub fn bootstrap(entitlements: Vec<Capability>, pubkey: [u8; 32]) -> Self {
let entitlements =
HashMap::from_iter(entitlements.into_iter().map(|id| (id, ResourceScope::Any)));
Self {
id: Uuid::new_v4(),
parent: Uuid::nil(),
entitlements,
_pubkey: pubkey,
}
}
pub fn create(
&self,
entitlements: HashMap<Capability, ResourceScope>,
pubkey: [u8; 32],
) -> Result<Self> {
let mut entitlements = entitlements;
for (cap, child_scope) in entitlements.iter_mut() {
let parent_scope = self
.entitlements
.get(cap)
.ok_or(SessionError::EntitlementScope)?;
if !parent_scope.contains(child_scope) {
return Err(SessionError::EntitlementScope)?;
}
}
Ok(Self {
id: Uuid::new_v4(),
parent: self.id,
entitlements,
_pubkey: pubkey,
})
}
pub fn authenticate(&self, _payload: &[u8], _signature: &[u8]) -> bool {
let success = true;
if success {
debug!(session = %self.id, status = "success", "authenticate");
} else {
warn!(session = %self.id, status = "fail", "authenticate");
}
todo!()
}
pub fn authorise(&self, capability: Capability, resource_id: ResourceId) -> bool {
let success = match self.entitlements.get(&capability) {
Some(ResourceScope::Any) => true,
Some(ResourceScope::Some(ids)) => ids.contains(&resource_id),
_ => false,
};
if success {
debug!(session = %self.id, capability = ?capability, resource = resource_id, status = "success", "authorise");
} else {
warn!(session = %self.id, capability = ?capability, resource = resource_id, status = "fail", "authorise");
}
success
}
fn upsert_entitlement(&mut self, entitlement: Capability) {
self.entitlements
.insert(entitlement, ResourceScope::Some(HashSet::new()));
}
fn remove_entitlement(&mut self, entitlement: Capability) -> Result<(), SessionError> {
self.entitlements
.remove(&entitlement)
.map(|_| ())
.ok_or(SessionError::EntitlementScope)
}
pub(crate) fn grant_resource(&mut self, entitlement: Capability, resource: ResourceId) -> bool {
match self.entitlements.get_mut(&entitlement) {
Some(ResourceScope::Some(scope)) => scope.insert(resource),
Some(scope) if matches!(scope, ResourceScope::None) => {
let mut set = HashSet::new();
set.insert(resource);
*scope = ResourceScope::Some(set);
true
}
_ => false,
}
}
pub(crate) fn revoke_resource(
&mut self,
entitlement: Capability,
resource: ResourceId,
) -> Result<bool, SessionError> {
match self.entitlements.get_mut(&entitlement) {
Some(scope) if matches!(scope, ResourceScope::Some(_)) => {
let set = scope.get_set_mut().unwrap();
let r = set.remove(&resource);
if set.is_empty() {
*scope = ResourceScope::None;
}
Ok(r)
}
Some(ResourceScope::Any) => Err(SessionError::RevokeOnAny),
_ => Ok(false),
}
}
fn ensure_removable(&self) -> Result<(), SessionError> {
if self.parent.is_nil() {
Err(SessionError::Unauthorised)
} else {
Ok(())
}
}
}
impl From<SessionError> for GuestError {
fn from(value: SessionError) -> Self {
GuestError::Subsystem(value.to_string())
}
}
impl ResourceScope {
fn contains(&self, other: &ResourceScope) -> bool {
match (self, other) {
(ResourceScope::Any, _) => true,
(ResourceScope::Some(_), ResourceScope::None) => true,
(ResourceScope::Some(self_ids), ResourceScope::Some(other_ids)) => {
other_ids.iter().all(|id| self_ids.contains(id))
}
(ResourceScope::None, ResourceScope::None) => true,
_ => false,
}
}
fn get_set_mut(&mut self) -> Option<&mut HashSet<ResourceId>> {
match self {
Self::Any | Self::None => None,
Self::Some(set) => Some(set),
}
}
}
impl From<SessionError> for i32 {
fn from(value: SessionError) -> Self {
match value {
SessionError::InvalidSignature => -110,
SessionError::Unauthorised => -111,
SessionError::EntitlementScope => -112,
SessionError::RevokeOnAny => -113,
}
}
}
#[derive(Clone)]
pub struct SessionLifecycleDriver;
impl SessionLifecycleDriver {
pub fn new() -> Arc<Self> {
Arc::new(Self)
}
}
impl SessionLifecycleCapability for SessionLifecycleDriver {
type Error = SessionError;
fn create(&self, me: &Session, pubkey: [u8; 32]) -> Result<Session, Self::Error> {
me.create(HashMap::new(), pubkey)
}
fn add_entitlement(
&self,
target: &mut Session,
entitlement: Capability,
) -> Result<(), Self::Error> {
target.upsert_entitlement(entitlement);
Ok(())
}
fn rm_entitlement(
&self,
target: &mut Session,
entitlement: Capability,
) -> Result<(), Self::Error> {
target.remove_entitlement(entitlement)
}
fn add_resource(
&self,
target: &mut Session,
entitlement: Capability,
resource: ResourceId,
) -> Result<bool, Self::Error> {
Ok(target.grant_resource(entitlement, resource))
}
fn rm_resource(
&self,
target: &mut Session,
entitlement: Capability,
resource: ResourceId,
) -> Result<bool, Self::Error> {
target.revoke_resource(entitlement, resource)
}
fn remove(&self, target: &Session) -> Result<(), Self::Error> {
target.ensure_removable()
}
}