coil_runtime/storage/
gate.rs1use coil_assets::ManagedAsset;
2use coil_auth::{AuthModelPackage, Capability, CoilAuth, DefaultSubject};
3use zanzibar::RebacEngine;
4
5use super::RuntimeStorageError;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct ManagedAssetPublicationGate {
9 pub can_publish: bool,
10 pub can_replace: bool,
11 pub can_manage_storage: bool,
12 pub public_delivery_enabled: bool,
13}
14
15impl ManagedAssetPublicationGate {
16 pub fn can_publish_publicly(&self) -> bool {
17 self.can_publish
18 && self.can_replace
19 && self.can_manage_storage
20 && self.public_delivery_enabled
21 }
22
23 pub fn ensure_public_delivery_allowed(
24 &self,
25 asset_id: impl Into<String>,
26 ) -> Result<(), RuntimeStorageError> {
27 if self.can_publish_publicly() {
28 Ok(())
29 } else {
30 Err(RuntimeStorageError::PublicationAuthorizationDenied {
31 asset_id: asset_id.into(),
32 reason: self.denial_reason(),
33 })
34 }
35 }
36
37 pub async fn resolve<E>(
38 auth: &CoilAuth<E>,
39 package: &impl AuthModelPackage,
40 subject: &DefaultSubject,
41 asset: &ManagedAsset,
42 ) -> Result<Self, RuntimeStorageError>
43 where
44 E: RebacEngine,
45 {
46 let asset_entity = asset.auth_entity();
47 let can_publish = check_capability(
48 auth,
49 package,
50 subject,
51 asset,
52 Capability::AssetPublish,
53 &asset_entity,
54 )
55 .await?;
56 let can_replace = check_capability(
57 auth,
58 package,
59 subject,
60 asset,
61 Capability::AssetReplace,
62 &asset_entity,
63 )
64 .await?;
65 let can_manage_storage = check_capability(
66 auth,
67 package,
68 subject,
69 asset,
70 Capability::AssetManageStorage,
71 &asset_entity,
72 )
73 .await?;
74
75 Ok(Self {
76 can_publish,
77 can_replace,
78 can_manage_storage,
79 public_delivery_enabled: asset.publication().is_published()
80 && asset
81 .publication()
82 .live_revision()
83 .is_some_and(|revision| revision.storage_plan().public_delivery_eligible()),
84 })
85 }
86
87 fn denial_reason(&self) -> String {
88 let mut missing = Vec::new();
89 if !self.can_publish {
90 missing.push(Capability::AssetPublish.to_string());
91 }
92 if !self.can_replace {
93 missing.push(Capability::AssetReplace.to_string());
94 }
95 if !self.can_manage_storage {
96 missing.push(Capability::AssetManageStorage.to_string());
97 }
98 if !self.public_delivery_enabled {
99 missing.push("published public delivery state".to_string());
100 }
101 missing.join(", ")
102 }
103}
104
105async fn check_capability<E>(
106 auth: &CoilAuth<E>,
107 package: &impl AuthModelPackage,
108 subject: &DefaultSubject,
109 asset: &ManagedAsset,
110 capability: Capability,
111 asset_entity: &coil_auth::Entity,
112) -> Result<bool, RuntimeStorageError>
113where
114 E: RebacEngine,
115{
116 auth.check_capability(package, subject, capability, asset_entity)
117 .await
118 .map_err(|_| RuntimeStorageError::PublicationAuthorizationDenied {
119 asset_id: asset.id().to_string(),
120 reason: capability.to_string(),
121 })
122}