1use std::{
2 collections::{HashMap, HashSet},
3 sync::Arc,
4};
5
6use thiserror::Error;
7use tracing::{debug, warn};
8use uuid::Uuid;
9
10use crate::{
11 drivers::{Capability, session::SessionLifecycleCapability},
12 guest_data::GuestError,
13 registry::ResourceId,
14};
15
16type Result<T, E = SessionError> = std::result::Result<T, E>;
17
18pub struct Session {
19 id: Uuid,
21 parent: Uuid,
23 entitlements: HashMap<Capability, ResourceScope>,
26 _pubkey: [u8; 32],
28}
29
30pub enum ResourceScope {
35 None,
36 Some(HashSet<ResourceId>),
37 Any,
38}
39
40#[derive(Error, Debug)]
41pub enum SessionError {
42 #[error("invalid payload signature")]
43 InvalidSignature,
44 #[error("session not authorised to perform this action")]
45 Unauthorised,
46 #[error("attempted to create a session with entitlements beyond its own")]
47 EntitlementScope,
48 #[error("attempted to revoke a resource from 'Any' scope")]
49 RevokeOnAny,
50}
51
52impl Session {
53 pub fn bootstrap(entitlements: Vec<Capability>, pubkey: [u8; 32]) -> Self {
60 let entitlements =
61 HashMap::from_iter(entitlements.into_iter().map(|id| (id, ResourceScope::Any)));
62
63 Self {
64 id: Uuid::new_v4(),
65 parent: Uuid::nil(),
66 entitlements,
67 _pubkey: pubkey,
68 }
69 }
70
71 pub fn create(
78 &self,
79 entitlements: HashMap<Capability, ResourceScope>,
80 pubkey: [u8; 32],
81 ) -> Result<Self> {
82 let mut entitlements = entitlements;
83
84 for (cap, child_scope) in entitlements.iter_mut() {
85 let parent_scope = self
86 .entitlements
87 .get(cap)
88 .ok_or(SessionError::EntitlementScope)?;
89
90 if !parent_scope.contains(child_scope) {
91 return Err(SessionError::EntitlementScope)?;
92 }
93 }
94
95 Ok(Self {
96 id: Uuid::new_v4(),
97 parent: self.id,
98 entitlements,
99 _pubkey: pubkey,
100 })
101 }
102
103 pub fn authenticate(&self, _payload: &[u8], _signature: &[u8]) -> bool {
108 let success = true; if success {
111 debug!(session = %self.id, status = "success", "authenticate");
112 } else {
113 warn!(session = %self.id, status = "fail", "authenticate");
114 }
115
116 todo!()
118 }
119
120 pub fn authorise(&self, capability: Capability, resource_id: ResourceId) -> bool {
124 let success = match self.entitlements.get(&capability) {
125 Some(ResourceScope::Any) => true,
126 Some(ResourceScope::Some(ids)) => ids.contains(&resource_id),
127 _ => false,
128 };
129
130 if success {
131 debug!(session = %self.id, capability = ?capability, resource = resource_id, status = "success", "authorise");
132 } else {
133 warn!(session = %self.id, capability = ?capability, resource = resource_id, status = "fail", "authorise");
134 }
135
136 success
137 }
138
139 fn upsert_entitlement(&mut self, entitlement: Capability) {
140 self.entitlements
141 .insert(entitlement, ResourceScope::Some(HashSet::new()));
142 }
143
144 fn remove_entitlement(&mut self, entitlement: Capability) -> Result<(), SessionError> {
145 self.entitlements
146 .remove(&entitlement)
147 .map(|_| ())
148 .ok_or(SessionError::EntitlementScope)
149 }
150
151 pub(crate) fn grant_resource(&mut self, entitlement: Capability, resource: ResourceId) -> bool {
152 match self.entitlements.get_mut(&entitlement) {
153 Some(ResourceScope::Some(scope)) => scope.insert(resource),
154 Some(scope) if matches!(scope, ResourceScope::None) => {
155 let mut set = HashSet::new();
156 set.insert(resource);
157 *scope = ResourceScope::Some(set);
158 true
159 }
160 _ => false,
161 }
162 }
163
164 pub(crate) fn revoke_resource(
165 &mut self,
166 entitlement: Capability,
167 resource: ResourceId,
168 ) -> Result<bool, SessionError> {
169 match self.entitlements.get_mut(&entitlement) {
170 Some(scope) if matches!(scope, ResourceScope::Some(_)) => {
171 let set = scope.get_set_mut().unwrap();
172 let r = set.remove(&resource);
173 if set.is_empty() {
174 *scope = ResourceScope::None;
175 }
176 Ok(r)
177 }
178 Some(ResourceScope::Any) => Err(SessionError::RevokeOnAny),
179 _ => Ok(false),
180 }
181 }
182
183 fn ensure_removable(&self) -> Result<(), SessionError> {
184 if self.parent.is_nil() {
185 Err(SessionError::Unauthorised)
186 } else {
187 Ok(())
188 }
189 }
190}
191
192impl From<SessionError> for GuestError {
193 fn from(value: SessionError) -> Self {
194 GuestError::Subsystem(value.to_string())
195 }
196}
197
198impl ResourceScope {
199 fn contains(&self, other: &ResourceScope) -> bool {
200 match (self, other) {
201 (ResourceScope::Any, _) => true,
202 (ResourceScope::Some(_), ResourceScope::None) => true,
203 (ResourceScope::Some(self_ids), ResourceScope::Some(other_ids)) => {
204 other_ids.iter().all(|id| self_ids.contains(id))
205 }
206 (ResourceScope::None, ResourceScope::None) => true,
207 _ => false,
208 }
209 }
210
211 fn get_set_mut(&mut self) -> Option<&mut HashSet<ResourceId>> {
212 match self {
213 Self::Any | Self::None => None,
214 Self::Some(set) => Some(set),
215 }
216 }
217}
218
219impl From<SessionError> for i32 {
220 fn from(value: SessionError) -> Self {
221 match value {
222 SessionError::InvalidSignature => -110,
223 SessionError::Unauthorised => -111,
224 SessionError::EntitlementScope => -112,
225 SessionError::RevokeOnAny => -113,
226 }
227 }
228}
229
230#[derive(Clone)]
232pub struct SessionLifecycleDriver;
233
234impl SessionLifecycleDriver {
235 pub fn new() -> Arc<Self> {
237 Arc::new(Self)
238 }
239}
240
241impl SessionLifecycleCapability for SessionLifecycleDriver {
242 type Error = SessionError;
243
244 fn create(&self, me: &Session, pubkey: [u8; 32]) -> Result<Session, Self::Error> {
245 me.create(HashMap::new(), pubkey)
246 }
247
248 fn add_entitlement(
249 &self,
250 target: &mut Session,
251 entitlement: Capability,
252 ) -> Result<(), Self::Error> {
253 target.upsert_entitlement(entitlement);
254 Ok(())
255 }
256
257 fn rm_entitlement(
258 &self,
259 target: &mut Session,
260 entitlement: Capability,
261 ) -> Result<(), Self::Error> {
262 target.remove_entitlement(entitlement)
263 }
264
265 fn add_resource(
266 &self,
267 target: &mut Session,
268 entitlement: Capability,
269 resource: ResourceId,
270 ) -> Result<bool, Self::Error> {
271 Ok(target.grant_resource(entitlement, resource))
272 }
273
274 fn rm_resource(
275 &self,
276 target: &mut Session,
277 entitlement: Capability,
278 resource: ResourceId,
279 ) -> Result<bool, Self::Error> {
280 target.revoke_resource(entitlement, resource)
281 }
282
283 fn remove(&self, target: &Session) -> Result<(), Self::Error> {
284 target.ensure_removable()
285 }
286}