1use super::probe::*;
2use super::*;
3
4#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
5#[serde(transparent)]
6pub struct ProviderAdminGrantId(pub ObjectHash);
7
8impl ProviderAdminGrantId {
9 pub fn from_random_bytes(bytes: [u8; 32]) -> Self {
10 Self(ObjectHash::from_digest(bytes))
11 }
12}
13
14#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct ProviderAccessGrantId(pub ObjectHash);
17
18impl ProviderAccessGrantId {
19 pub fn from_random_bytes(bytes: [u8; 32]) -> Self {
20 Self(ObjectHash::from_digest(bytes))
21 }
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case", deny_unknown_fields)]
28pub enum ProviderAccessLocator {
29 S3SharedCredentialGeneration {
30 generation: u64,
31 access_key_id_hash: ObjectHash,
32 },
33 GoogleDrivePermission {
34 drive_id: String,
35 permission_id: String,
36 },
37 DropboxSharedFolderMember {
38 namespace_id: String,
39 account_id: String,
40 },
41 OneDrivePermission {
42 drive_id: String,
43 item_id: String,
44 permission_id: String,
45 },
46 CloudKitPrivateZoneOwner {
47 owner_name: String,
48 zone_name: String,
49 owner_record_name: String,
50 },
51 CloudKitParticipant {
52 share_record_name: String,
53 owner_name: String,
54 zone_name: String,
55 participant_record_name: String,
56 },
57}
58
59impl ProviderAccessLocator {
60 pub fn for_current_administrator(
61 binding: &crate::objects::ResolvedProviderBinding,
62 ) -> Result<Self, StorageError> {
63 binding.validate()?;
64 match (&binding.store, &binding.device.principal) {
65 (
66 StoreProviderBinding::S3 { .. },
67 crate::objects::ProviderPrincipalId::CustomS3Credential { access_key_id_hash },
68 ) => Ok(Self::S3SharedCredentialGeneration {
69 generation: 1,
70 access_key_id_hash: *access_key_id_hash,
71 }),
72 (
73 StoreProviderBinding::GoogleDrive {
74 corpus: crate::objects::GoogleDriveCorpus::SharedDrive { drive_id, .. },
75 },
76 crate::objects::ProviderPrincipalId::GoogleDrive { permission_id },
77 ) => Ok(Self::GoogleDrivePermission {
78 drive_id: drive_id.clone(),
79 permission_id: permission_id.clone(),
80 }),
81 (
82 StoreProviderBinding::Dropbox { namespace_id },
83 crate::objects::ProviderPrincipalId::Dropbox { account_id },
84 ) => Ok(Self::DropboxSharedFolderMember {
85 namespace_id: namespace_id.clone(),
86 account_id: account_id.clone(),
87 }),
88 (
89 StoreProviderBinding::CloudKit {
90 owner_name,
91 zone_name,
92 ..
93 },
94 crate::objects::ProviderPrincipalId::CloudKitPrivateZoneOwner { record_name },
95 ) => Ok(Self::CloudKitPrivateZoneOwner {
96 owner_name: owner_name.clone(),
97 zone_name: zone_name.clone(),
98 owner_record_name: record_name.clone(),
99 }),
100 _ => Err(StorageError::Configuration(
101 "provider adapter did not expose the administrator's exact access locator"
102 .to_string(),
103 )),
104 }
105 }
106
107 pub fn validate_for(
108 &self,
109 store: &StoreProviderBinding,
110 provider: &ProviderDeviceBinding,
111 ) -> Result<(), StorageError> {
112 provider.validate_for(store)?;
113 let valid = match (store, &provider.principal, self) {
114 (
115 StoreProviderBinding::S3 { .. },
116 crate::objects::ProviderPrincipalId::CustomS3Credential {
117 access_key_id_hash: provider_hash,
118 },
119 Self::S3SharedCredentialGeneration {
120 generation,
121 access_key_id_hash,
122 },
123 ) => *generation > 0 && provider_hash == access_key_id_hash,
124 (
125 StoreProviderBinding::S3 { .. },
126 crate::objects::ProviderPrincipalId::Aws { .. },
127 Self::S3SharedCredentialGeneration { generation, .. },
128 ) => *generation > 0,
129 (
130 StoreProviderBinding::GoogleDrive {
131 corpus: crate::objects::GoogleDriveCorpus::SharedDrive { drive_id, .. },
132 },
133 crate::objects::ProviderPrincipalId::GoogleDrive { permission_id },
134 Self::GoogleDrivePermission {
135 drive_id: locator_drive,
136 permission_id: locator_permission,
137 },
138 ) => drive_id == locator_drive && permission_id == locator_permission,
139 (
140 StoreProviderBinding::Dropbox { namespace_id },
141 crate::objects::ProviderPrincipalId::Dropbox { account_id },
142 Self::DropboxSharedFolderMember {
143 namespace_id: locator_namespace,
144 account_id: locator_account,
145 },
146 ) => namespace_id == locator_namespace && account_id == locator_account,
147 (
148 StoreProviderBinding::OneDrive {
149 drive_id,
150 folder_id,
151 },
152 crate::objects::ProviderPrincipalId::OneDrive { .. },
153 Self::OneDrivePermission {
154 drive_id: locator_drive,
155 item_id,
156 permission_id,
157 },
158 ) => drive_id == locator_drive && folder_id == item_id && !permission_id.is_empty(),
159 (
160 StoreProviderBinding::CloudKit {
161 owner_name,
162 zone_name,
163 ..
164 },
165 crate::objects::ProviderPrincipalId::CloudKitPrivateZoneOwner { record_name },
166 Self::CloudKitPrivateZoneOwner {
167 owner_name: locator_owner,
168 zone_name: locator_zone,
169 owner_record_name,
170 },
171 ) => {
172 owner_name == locator_owner
173 && zone_name == locator_zone
174 && record_name == owner_record_name
175 }
176 (
177 StoreProviderBinding::CloudKit {
178 owner_name,
179 zone_name,
180 ..
181 },
182 crate::objects::ProviderPrincipalId::CloudKitSharedZoneParticipant { record_name },
183 Self::CloudKitParticipant {
184 share_record_name,
185 owner_name: locator_owner,
186 zone_name: locator_zone,
187 participant_record_name,
188 },
189 ) => {
190 !share_record_name.is_empty()
191 && owner_name == locator_owner
192 && zone_name == locator_zone
193 && record_name == participant_record_name
194 }
195 _ => false,
196 };
197 if valid {
198 Ok(())
199 } else {
200 Err(StorageError::Configuration(
201 "provider access locator differs from its Store and provider binding".to_string(),
202 ))
203 }
204 }
205}
206
207#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
210#[serde(deny_unknown_fields)]
211pub struct StoreMemberProviderAccessGrantBody {
212 pub grant_id: ProviderAccessGrantId,
213 pub member_pubkey: String,
214 pub provider: ProviderDeviceBinding,
215 pub locator: ProviderAccessLocator,
216 pub administrator_grant: ProviderAdminGrantId,
217 pub administrator: StoreDeviceRegistrationRef,
218}
219
220impl crate::store_commit::SignedBody for StoreMemberProviderAccessGrantBody {
221 const DOMAIN: &'static [u8] = MEMBER_ACCESS_GRANT_DOMAIN;
222}
223
224pub type StoreMemberProviderAccessGrant =
225 crate::store_commit::Signed<StoreMemberProviderAccessGrantBody>;
226
227impl StoreMemberProviderAccessGrant {
228 #[allow(clippy::too_many_arguments)]
229 pub fn signed(
230 grant_id: ProviderAccessGrantId,
231 member_pubkey: String,
232 provider: ProviderDeviceBinding,
233 locator: ProviderAccessLocator,
234 administrator_grant: ProviderAdminGrantId,
235 administrator: StoreDeviceRegistrationRef,
236 store: &StoreProviderBinding,
237 administrator_registration: &StoreDeviceRegistration,
238 administrator_signer: &dyn coven_keys::keys::DeviceSigningAuthority,
239 ) -> Result<Self, ProviderProbeError> {
240 administrator.verify_registration(administrator_registration)?;
241 if administrator_signer.public_key_hex() != administrator_registration.device_signing_pubkey
242 {
243 return invalid("provider access grant signer is not the administrator device");
244 }
245 locator.validate_for(store, &provider)?;
246 Ok(crate::store_commit::Signed::sign_by_device(
247 StoreMemberProviderAccessGrantBody {
248 grant_id,
249 member_pubkey,
250 provider,
251 locator,
252 administrator_grant,
253 administrator,
254 },
255 administrator_signer,
256 ))
257 }
258
259 pub fn grant_hash(&self) -> ObjectHash {
260 self.hash()
261 }
262
263 pub fn verify(
264 &self,
265 store: &StoreProviderBinding,
266 administrator: &StoreDeviceRegistration,
267 ) -> Result<(), ProviderProbeError> {
268 self.administrator.verify_registration(administrator)?;
269 self.provider
270 .validate_for(store)
271 .map_err(ProviderProbeError::Storage)?;
272 self.locator
273 .validate_for(store, &self.provider)
274 .map_err(ProviderProbeError::Storage)?;
275 if self
276 .verify_by(&administrator.device_signing_pubkey)
277 .is_err()
278 {
279 return invalid("provider access grant signature is invalid");
280 }
281 Ok(())
282 }
283}
284
285#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
286#[serde(deny_unknown_fields)]
287pub struct StoreMemberProviderAccessGrantRef {
288 pub grant_id: ProviderAccessGrantId,
289 pub grant_hash: ObjectHash,
290 pub object: ExactObjectRef,
291}
292
293impl StoreMemberProviderAccessGrantRef {
294 pub fn from_grant(grant: &StoreMemberProviderAccessGrant, object: ExactObjectRef) -> Self {
295 Self {
296 grant_id: grant.grant_id.clone(),
297 grant_hash: grant.grant_hash(),
298 object,
299 }
300 }
301
302 pub fn verify(&self, grant: &StoreMemberProviderAccessGrant) -> Result<(), ProviderProbeError> {
303 if self.grant_id != grant.grant_id || self.grant_hash != grant.grant_hash() {
304 return invalid("provider access grant reference differs from its signed grant");
305 }
306 Ok(())
307 }
308}
309
310#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
311#[serde(deny_unknown_fields)]
312pub struct ActivatedStoreMemberProviderAccessGrant {
313 pub grant: StoreMemberProviderAccessGrant,
314 pub grant_ref: StoreMemberProviderAccessGrantRef,
315 pub activation: StoreBatchCommitRef,
316}
317
318#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
319#[serde(rename_all = "snake_case", deny_unknown_fields)]
320pub enum ProviderAccessWithdrawal {
321 Direct {
322 locator: ProviderAccessLocator,
323 verified_absent: bool,
324 },
325 S3CredentialRotation {
326 retired_generation: u64,
327 active_generation: u64,
328 retired_credential_verified_rejected: bool,
329 },
330}
331
332impl ProviderAccessWithdrawal {
333 pub(super) fn validate(&self) -> Result<(), ProviderProbeError> {
334 let valid = match self {
335 Self::Direct {
336 verified_absent, ..
337 } => *verified_absent,
338 Self::S3CredentialRotation {
339 retired_generation,
340 active_generation,
341 retired_credential_verified_rejected,
342 } => {
343 *retired_generation > 0
344 && retired_generation.checked_add(1) == Some(*active_generation)
345 && *retired_credential_verified_rejected
346 }
347 };
348 if valid {
349 Ok(())
350 } else {
351 invalid("provider access withdrawal does not prove the stored authority is unusable")
352 }
353 }
354
355 pub fn verify_for_locator(
356 &self,
357 locator: &ProviderAccessLocator,
358 ) -> Result<(), ProviderProbeError> {
359 self.validate()?;
360 let matches = match (self, locator) {
361 (
362 Self::Direct {
363 locator: withdrawn, ..
364 },
365 expected,
366 ) => withdrawn == expected,
367 (
368 Self::S3CredentialRotation {
369 retired_generation, ..
370 },
371 ProviderAccessLocator::S3SharedCredentialGeneration { generation, .. },
372 ) => retired_generation == generation,
373 _ => false,
374 };
375 if matches {
376 Ok(())
377 } else {
378 invalid("provider access withdrawal differs from the stored authority locator")
379 }
380 }
381}