1use std::{future::Future, marker::PhantomData};
4
5use anyhow::{Context, Result};
6use aranya_crypto::{
7 policy::{LabelId, RoleId},
8 DeviceId, Random as _, Rng,
9};
10use aranya_keygen::PublicKeys;
11use aranya_policy_ifgen::{Actionable, VmEffect};
12use aranya_policy_text::Text;
13#[cfg(feature = "afc")]
14use aranya_runtime::NullSink;
15use aranya_runtime::{GraphId, PolicyStore, Session, StorageProvider, VmPolicy};
16use futures_util::TryFutureExt as _;
17use tracing::{debug, instrument, warn, Instrument};
18
19use crate::{
20 aranya::Client,
21 policy::{self, ChanOp, Effect, PublicKeyBundle, RoleManagementPerm, SimplePerm},
22 vm_policy::{MsgSink, VecSink},
23};
24
25#[derive(Debug)]
27pub(crate) struct SessionData {
28 #[cfg(feature = "afc")]
30 pub ctrl: Vec<Box<[u8]>>,
31 pub effects: Vec<Effect>,
33}
34
35impl<PS, SP, CE> Client<PS, SP>
37where
38 PS: PolicyStore<Policy = VmPolicy<CE>, Effect = VmEffect> + Send + 'static,
39 SP: StorageProvider + Send + 'static,
40 CE: aranya_crypto::Engine + Send + Sync + 'static,
41{
42 #[instrument(skip_all)]
46 pub async fn create_team(
47 &self,
48 owner_keys: PublicKeyBundle,
49 nonce: Option<&[u8]>,
50 ) -> Result<(GraphId, Vec<Effect>)> {
51 let mut sink = VecSink::new();
52 let policy_data = &[0u8];
53 let act = policy::create_team(
54 owner_keys,
55 nonce.unwrap_or(&<[u8; 16]>::random(Rng)).to_vec(),
56 );
57 let id = {
58 let mut client = self.lock_aranya().await;
59 act.with_action(|act| client.new_graph(policy_data, act, &mut sink))
60 .context("unable to create new team")?
61 };
62 Ok((id, sink.collect()?))
63 }
64
65 #[instrument(skip_all, fields(%graph_id))]
68 pub fn actions(&self, graph_id: GraphId) -> impl Actions<PS, SP, CE> {
69 ActionsImpl {
70 client: self.clone(),
71 graph_id,
72 _eng: PhantomData,
73 }
74 }
75
76 #[instrument(skip_all, fields(%graph_id))]
79 pub(crate) async fn session_new(&self, graph_id: GraphId) -> Result<Session<SP, PS>> {
80 let session = self.lock_aranya().await.session(graph_id)?;
81 Ok(session)
82 }
83
84 #[instrument(skip_all)]
87 pub(crate) async fn session_receive(
88 &self,
89 session: &mut Session<SP, PS>,
90 command: &[u8],
91 ) -> Result<Vec<Effect>> {
92 let client = self.lock_aranya().await;
93 let mut sink = VecSink::new();
94 session.receive(&client, &mut sink, command)?;
95 Ok(sink.collect()?)
96 }
97}
98
99struct ActionsImpl<PS, SP, CE> {
101 client: Client<PS, SP>,
103 graph_id: GraphId,
105 _eng: PhantomData<CE>,
107}
108
109impl<PS, SP, CE> Actions<PS, SP, CE> for ActionsImpl<PS, SP, CE>
110where
111 PS: PolicyStore<Policy = VmPolicy<CE>, Effect = VmEffect> + Send + 'static,
112 SP: StorageProvider + Send + 'static,
113 CE: aranya_crypto::Engine + Send + Sync + 'static,
114{
115 async fn call_persistent_action(
116 &self,
117 act: impl Actionable<Interface = policy::Persistent> + Send,
118 ) -> Result<Vec<Effect>> {
119 let mut sink = VecSink::new();
120 {
122 let mut client = self.client.lock_aranya().await;
123 act.with_action(|act| client.action(self.graph_id, &mut sink, act))?;
124 }
125
126 let total = sink.effects.len();
127 for (i, effect) in sink.effects.iter().enumerate() {
128 debug!(i, total, effect = effect.name.as_str());
129 }
130
131 Ok(sink.collect()?)
132 }
133
134 async fn call_session_action(
135 &self,
136 act: impl Actionable<Interface = policy::Ephemeral> + Send,
137 ) -> Result<SessionData> {
138 let mut sink = VecSink::new();
139 let mut msg_sink = MsgSink::new();
140 {
141 let mut client = self.client.lock_aranya().await;
142 let mut session = client.session(self.graph_id)?;
143 act.with_action(|act| session.action(&client, &mut sink, &mut msg_sink, act))?;
144 }
145 Ok(SessionData {
146 #[cfg(feature = "afc")]
147 ctrl: msg_sink.into_cmds(),
148 effects: sink.collect()?,
149 })
150 }
151}
152
153pub trait Actions<PS, SP, CE>
155where
156 PS: PolicyStore<Policy = VmPolicy<CE>, Effect = VmEffect> + Send + 'static,
157 SP: StorageProvider + Send + 'static,
158 CE: aranya_crypto::Engine + Send + Sync + 'static,
159{
160 fn call_persistent_action(
162 &self,
163 act: impl Actionable<Interface = policy::Persistent> + Send,
164 ) -> impl Future<Output = Result<Vec<Effect>>> + Send;
165
166 #[allow(clippy::type_complexity)]
167 fn call_session_action(
169 &self,
170 act: impl Actionable<Interface = policy::Ephemeral> + Send,
171 ) -> impl Future<Output = Result<SessionData>> + Send;
172
173 #[instrument(skip_all)]
175 fn add_device(
176 &self,
177 keys: PublicKeyBundle,
178 initial_role_id: Option<RoleId>,
179 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
180 self.call_persistent_action(policy::add_device(
181 keys,
182 initial_role_id.map(|id| id.as_base()),
183 ))
184 .in_current_span()
185 }
186
187 #[cfg(feature = "preview")]
189 #[instrument(skip(self))]
190 fn create_role(
191 &self,
192 role_name: Text,
193 owning_role_id: RoleId,
194 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
195 self.call_persistent_action(policy::create_role(role_name, owning_role_id.as_base()))
196 .in_current_span()
197 }
198
199 #[cfg(feature = "preview")]
201 #[instrument(skip(self))]
202 fn delete_role(&self, role_id: RoleId) -> impl Future<Output = Result<Vec<Effect>>> + Send {
203 self.call_persistent_action(policy::delete_role(role_id.as_base()))
204 .in_current_span()
205 }
206
207 #[instrument(skip(self), fields(%label_id, %managing_role_id))]
209 fn add_label_managing_role(
210 &self,
211 label_id: LabelId,
212 managing_role_id: RoleId,
213 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
214 self.call_persistent_action(policy::add_label_managing_role(
215 label_id.as_base(),
216 managing_role_id.as_base(),
217 ))
218 .in_current_span()
219 }
220
221 #[instrument(skip(self), fields(%role_id, %perm))]
223 fn add_perm_to_role(
224 &self,
225 role_id: RoleId,
226 perm: SimplePerm,
227 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
228 self.call_persistent_action(policy::add_perm_to_role(role_id.as_base(), perm))
229 .in_current_span()
230 }
231
232 #[instrument(skip(self), fields(%role_id, %new_owning_role_id))]
234 fn add_role_owner(
235 &self,
236 role_id: RoleId,
237 new_owning_role_id: RoleId,
238 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
239 self.call_persistent_action(policy::add_role_owner(
240 role_id.as_base(),
241 new_owning_role_id.as_base(),
242 ))
243 .in_current_span()
244 }
245
246 #[instrument(skip(self), fields(%device_id, %label_id, %op))]
248 fn assign_label_to_device(
249 &self,
250 device_id: DeviceId,
251 label_id: LabelId,
252 op: ChanOp,
253 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
254 self.call_persistent_action(policy::assign_label_to_device(
255 device_id.as_base(),
256 label_id.as_base(),
257 op,
258 ))
259 .in_current_span()
260 }
261
262 #[instrument(skip(self), fields(%device_id, %role_id))]
264 fn assign_role(
265 &self,
266 device_id: DeviceId,
267 role_id: RoleId,
268 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
269 self.call_persistent_action(policy::assign_role(device_id.as_base(), role_id.as_base()))
270 .in_current_span()
271 }
272
273 #[instrument(skip(self), fields(%target_role_id, %managing_role_id, %perm))]
275 fn assign_role_management_perm(
276 &self,
277 target_role_id: RoleId,
278 managing_role_id: RoleId,
279 perm: RoleManagementPerm,
280 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
281 self.call_persistent_action(policy::assign_role_management_perm(
282 target_role_id.as_base(),
283 managing_role_id.as_base(),
284 perm,
285 ))
286 .in_current_span()
287 }
288
289 #[instrument(skip(self), fields(%device_id, %old_role_id, %new_role_id))]
291 fn change_role(
292 &self,
293 device_id: DeviceId,
294 old_role_id: RoleId,
295 new_role_id: RoleId,
296 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
297 self.call_persistent_action(policy::change_role(
298 device_id.as_base(),
299 old_role_id.as_base(),
300 new_role_id.as_base(),
301 ))
302 .in_current_span()
303 }
304
305 #[instrument(skip(self), fields(%name, %managing_role_id))]
307 fn create_label(
308 &self,
309 name: Text,
310 managing_role_id: RoleId,
311 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
312 self.call_persistent_action(policy::create_label(name, managing_role_id.as_base()))
313 .in_current_span()
314 }
315
316 #[cfg(feature = "afc")]
318 #[allow(clippy::type_complexity)]
319 #[instrument(skip(self), fields(open_id = %open_id, label_id = %label_id))]
320 fn create_afc_uni_channel_off_graph(
321 &self,
322 open_id: DeviceId,
323 label_id: LabelId,
324 ) -> impl Future<Output = Result<SessionData>> + Send {
325 self.call_session_action(policy::create_afc_uni_channel(
326 open_id.as_base(),
327 label_id.as_base(),
328 ))
329 .in_current_span()
330 }
331
332 #[instrument(skip(self), fields(%label_id))]
334 fn delete_label(&self, label_id: LabelId) -> impl Future<Output = Result<Vec<Effect>>> + Send {
335 self.call_persistent_action(policy::delete_label(label_id.as_base()))
336 .in_current_span()
337 }
338
339 #[allow(clippy::type_complexity)]
341 #[instrument(skip(self), fields(%device_id))]
342 fn query_device_public_key_bundle(
343 &self,
344 device_id: DeviceId,
345 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
346 self.call_session_action(policy::query_device_public_key_bundle(device_id.as_base()))
347 .map_ok(|SessionData { effects, .. }| effects)
348 .in_current_span()
349 }
350
351 #[allow(clippy::type_complexity)]
353 #[instrument(skip(self), fields(%device_id))]
354 fn query_device_role(
355 &self,
356 device_id: DeviceId,
357 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
358 self.call_session_action(policy::query_device_role(device_id.as_base()))
359 .map_ok(|SessionData { effects, .. }| effects)
360 .in_current_span()
361 }
362
363 #[allow(clippy::type_complexity)]
365 #[instrument(skip(self))]
366 fn query_devices_on_team(&self) -> impl Future<Output = Result<Vec<Effect>>> + Send {
367 self.call_session_action(policy::query_devices_on_team())
368 .map_ok(|SessionData { effects, .. }| effects)
369 .in_current_span()
370 }
371
372 #[allow(clippy::type_complexity)]
374 #[instrument(skip(self), fields(%label_id))]
375 fn query_label(&self, label_id: LabelId) -> impl Future<Output = Result<Vec<Effect>>> + Send {
376 self.call_session_action(policy::query_label(label_id.as_base()))
377 .map_ok(|SessionData { effects, .. }| effects)
378 .in_current_span()
379 }
380
381 #[allow(clippy::type_complexity)]
383 #[instrument(skip(self))]
384 fn query_labels(&self) -> impl Future<Output = Result<Vec<Effect>>> + Send {
385 self.call_session_action(policy::query_labels())
386 .map_ok(|SessionData { effects, .. }| effects)
387 .in_current_span()
388 }
389
390 #[instrument(skip(self), fields(%device))]
392 fn query_labels_assigned_to_device(
393 &self,
394 device: DeviceId,
395 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
396 self.call_session_action(policy::query_labels_assigned_to_device(device.as_base()))
397 .map_ok(|SessionData { effects, .. }| effects)
398 .in_current_span()
399 }
400
401 #[instrument(skip(self))]
403 fn query_team_roles(&self) -> impl Future<Output = Result<Vec<Effect>>> + Send {
404 self.call_session_action(policy::query_team_roles())
405 .map_ok(|SessionData { effects, .. }| effects)
406 .in_current_span()
407 }
408
409 #[instrument(skip(self), fields(%role_id))]
411 fn query_role_owners(
412 &self,
413 role_id: RoleId,
414 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
415 self.call_session_action(policy::query_role_owners(role_id.as_base()))
416 .map_ok(|SessionData { effects, .. }| effects)
417 .in_current_span()
418 }
419
420 #[instrument(skip(self), fields(%device_id))]
422 fn remove_device(
423 &self,
424 device_id: DeviceId,
425 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
426 self.call_persistent_action(policy::remove_device(device_id.as_base()))
427 .in_current_span()
428 }
429
430 #[instrument(skip(self), fields(%role_id, %perm))]
432 fn remove_perm_from_role(
433 &self,
434 role_id: RoleId,
435 perm: SimplePerm,
436 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
437 self.call_persistent_action(policy::remove_perm_from_role(role_id.as_base(), perm))
438 .in_current_span()
439 }
440
441 #[instrument(skip(self), fields(%role_id, %new_owning_role_id))]
443 fn remove_role_owner(
444 &self,
445 role_id: RoleId,
446 new_owning_role_id: RoleId,
447 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
448 self.call_persistent_action(policy::remove_role_owner(
449 role_id.as_base(),
450 new_owning_role_id.as_base(),
451 ))
452 .in_current_span()
453 }
454
455 #[instrument(skip(self), fields(%device_id, %label_id))]
457 fn revoke_label_from_device(
458 &self,
459 device_id: DeviceId,
460 label_id: LabelId,
461 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
462 self.call_persistent_action(policy::revoke_label_from_device(
463 device_id.as_base(),
464 label_id.as_base(),
465 ))
466 .in_current_span()
467 }
468
469 #[instrument(skip(self), fields(%label_id, %managing_role_id))]
471 fn revoke_label_managing_role(
472 &self,
473 label_id: LabelId,
474 managing_role_id: RoleId,
475 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
476 self.call_persistent_action(policy::revoke_label_managing_role(
477 label_id.as_base(),
478 managing_role_id.as_base(),
479 ))
480 .in_current_span()
481 }
482
483 #[instrument(skip(self), fields(%device_id, %role_id))]
485 fn revoke_role(
486 &self,
487 device_id: DeviceId,
488 role_id: RoleId,
489 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
490 self.call_persistent_action(policy::revoke_role(device_id.as_base(), role_id.as_base()))
491 .in_current_span()
492 }
493
494 #[instrument(skip(self), fields(%target_role_id, %managing_role_id, %perm))]
496 fn revoke_role_management_perm(
497 &self,
498 target_role_id: RoleId,
499 managing_role_id: RoleId,
500 perm: RoleManagementPerm,
501 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
502 self.call_persistent_action(policy::revoke_role_management_perm(
503 target_role_id.as_base(),
504 managing_role_id.as_base(),
505 perm,
506 ))
507 .in_current_span()
508 }
509
510 #[instrument(skip(self), fields(%managing_role_id))]
512 fn setup_default_roles(
513 &self,
514 managing_role_id: RoleId,
515 ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
516 self.call_persistent_action(policy::setup_default_roles(managing_role_id.as_base()))
517 .in_current_span()
518 }
519
520 #[instrument(skip(self), fields(%team_id))]
522 fn terminate_team(&self, team_id: GraphId) -> impl Future<Output = Result<Vec<Effect>>> + Send {
523 self.call_persistent_action(policy::terminate_team(team_id.as_base()))
524 .in_current_span()
525 }
526}
527
528impl<CS: aranya_crypto::CipherSuite> TryFrom<&PublicKeys<CS>> for PublicKeyBundle {
529 type Error = postcard::Error;
530 fn try_from(pk: &PublicKeys<CS>) -> Result<Self, Self::Error> {
531 Ok(Self {
532 ident_key: postcard::to_allocvec(&pk.ident_pk)?,
533 enc_key: postcard::to_allocvec(&pk.enc_pk)?,
534 sign_key: postcard::to_allocvec(&pk.sign_pk)?,
535 })
536 }
537}
538
539#[cfg(feature = "afc")]
540pub(crate) fn query_afc_channel_is_valid<PS, SP, CE>(
541 aranya: &mut aranya_runtime::ClientState<PS, SP>,
542 graph_id: GraphId,
543 sender_id: DeviceId,
544 receiver_id: DeviceId,
545 label_id: LabelId,
546) -> Result<bool>
547where
548 PS: PolicyStore<Policy = VmPolicy<CE>, Effect = VmEffect>,
549 SP: StorageProvider,
550 CE: aranya_crypto::Engine,
551{
552 let mut session = aranya.session(graph_id)?;
553 let mut sink = VecSink::new();
554 policy::query_afc_channel_is_valid(
555 sender_id.as_base(),
556 receiver_id.as_base(),
557 label_id.as_base(),
558 )
559 .with_action(|act| session.action(aranya, &mut sink, &mut NullSink, act))?;
560 let effects = sink.collect()?;
561 Ok(effects.iter().any(|e| {
562 if let Effect::QueryAfcChannelIsValidResult(e) = e {
563 return e.is_valid;
564 }
565 false
566 }))
567}