noosphere_core/authority/
author.rs1use crate::{
2 authority::{
3 generate_ed25519_key, Authorization, SphereAbility, SPHERE_SEMANTICS, SUPPORTED_KEYS,
4 },
5 data::Did,
6 view::Sphere,
7};
8use anyhow::{anyhow, Result};
9use noosphere_storage::{SphereDb, Storage};
10use ucan::{
11 chain::ProofChain,
12 crypto::{did::DidParser, KeyMaterial},
13};
14use ucan_key_support::ed25519::Ed25519KeyMaterial;
15
16use super::generate_capability;
17
18#[derive(PartialEq, Eq, Debug, Clone)]
22pub enum Access {
23 ReadWrite,
25 ReadOnly,
27}
28
29#[derive(Clone, Debug)]
34pub struct Author<K>
35where
36 K: KeyMaterial + Clone + 'static,
37{
38 pub key: K,
40 pub authorization: Option<Authorization>,
42}
43
44impl Author<Ed25519KeyMaterial> {
45 pub fn anonymous() -> Self {
48 Author {
49 key: generate_ed25519_key(),
50 authorization: None,
51 }
52 }
53}
54
55impl<K> Author<K>
56where
57 K: KeyMaterial + Clone + 'static,
58{
59 pub async fn identity(&self) -> Result<Did> {
61 Ok(Did(self.key.get_did().await?))
62 }
63
64 pub fn require_authorization(&self) -> Result<&Authorization> {
68 self.authorization
69 .as_ref()
70 .ok_or_else(|| anyhow!("Authorization is required but none is configured"))
71 }
72
73 pub async fn access_to<S>(&self, sphere_identity: &Did, db: &SphereDb<S>) -> Result<Access>
75 where
76 S: Storage,
77 {
78 let author_did = Did(self.key.get_did().await?);
79
80 if &author_did == sphere_identity {
83 return Ok(Access::ReadWrite);
84 }
85
86 if let Some(authorization) = &self.authorization {
87 let ucan = authorization.as_ucan(db).await?;
88
89 if ucan.audience() != author_did.as_str() {
90 return Ok(Access::ReadOnly);
91 }
92
93 let sphere = Sphere::at(&db.require_version(sphere_identity).await?.into(), db);
94 match sphere.verify_authorization(authorization).await {
95 Ok(_) => (),
96 Err(error) => {
97 warn!("Could not verify authorization: {}", error);
98 return Ok(Access::ReadOnly);
99 }
100 };
101
102 let read_write_capability = generate_capability(sphere_identity, SphereAbility::Push);
103
104 let mut did_parser = DidParser::new(SUPPORTED_KEYS);
105 let proof_chain = match ProofChain::from_ucan(ucan, None, &mut did_parser, db).await {
106 Ok(proof_chain) => proof_chain,
107 Err(error) => {
108 warn!("Could not construct a verified proof chain: {}", error);
109 return Ok(Access::ReadOnly);
110 }
111 };
112
113 let capability_infos = proof_chain.reduce_capabilities(&SPHERE_SEMANTICS);
114
115 for info in capability_infos {
116 if info.originators.contains(sphere_identity.as_str())
117 && info.capability.enables(&read_write_capability)
118 {
119 return Ok(Access::ReadWrite);
120 }
121 }
122 }
123
124 Ok(Access::ReadOnly)
125 }
126
127 pub async fn did(&self) -> Result<Did> {
129 Ok(Did(self.key.get_did().await?))
130 }
131
132 pub async fn is_authorizer_of<S>(
135 &self,
136 authorization: &Authorization,
137 db: &SphereDb<S>,
138 ) -> Result<bool>
139 where
140 S: Storage,
141 {
142 let proof_chain = authorization.as_proof_chain(db).await?;
143 let mut links_to_check = vec![&proof_chain];
144 let author_did = self.did().await?;
145
146 while let Some(link) = links_to_check.pop() {
147 if link.ucan().issuer() == author_did {
148 return Ok(true);
149 }
150
151 links_to_check.extend(link.proofs().iter());
152 }
153
154 Ok(false)
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use anyhow::Result;
161 use noosphere_storage::{MemoryStorage, SphereDb};
162 use ucan::{builder::UcanBuilder, crypto::KeyMaterial, store::UcanJwtStore};
163
164 use crate::{
165 authority::{generate_capability, generate_ed25519_key, Authorization, SphereAbility},
166 data::Did,
167 view::Sphere,
168 };
169
170 use super::{Access, Author};
171
172 #[cfg(target_arch = "wasm32")]
173 use wasm_bindgen_test::wasm_bindgen_test;
174
175 #[cfg(target_arch = "wasm32")]
176 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
177
178 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
179 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
180 async fn it_gives_read_only_access_when_there_is_no_authorization() -> Result<()> {
181 let author = Author::anonymous();
182 let mut db = SphereDb::new(&MemoryStorage::default()).await?;
183
184 let (sphere, _, _) = Sphere::generate("did:key:foo", &mut db).await?;
185
186 db.set_version(&sphere.get_identity().await?, sphere.cid())
187 .await?;
188
189 let access = author
190 .access_to(&sphere.get_identity().await?, &db)
191 .await
192 .unwrap();
193
194 assert_eq!(access, Access::ReadOnly);
195
196 Ok(())
197 }
198
199 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
200 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
201 async fn it_gives_read_write_access_if_the_key_is_authorized() -> Result<()> {
202 let owner_key = generate_ed25519_key();
203 let owner_did = Did(owner_key.get_did().await.unwrap());
204 let mut db = SphereDb::new(&MemoryStorage::default()).await.unwrap();
205
206 let (sphere, authorization, _) = Sphere::generate(&owner_did, &mut db).await.unwrap();
207
208 db.set_version(&sphere.get_identity().await?, sphere.cid())
209 .await?;
210
211 let author = Author {
212 key: owner_key,
213 authorization: Some(authorization),
214 };
215
216 let access = author
217 .access_to(&sphere.get_identity().await.unwrap(), &db)
218 .await
219 .unwrap();
220
221 assert_eq!(access, Access::ReadWrite);
222 Ok(())
223 }
224
225 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
226 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
227 async fn it_gives_read_write_access_to_the_root_sphere_credential() -> Result<()> {
228 let owner_key = generate_ed25519_key();
229 let owner_did = Did(owner_key.get_did().await.unwrap());
230 let mut db = SphereDb::new(&MemoryStorage::default()).await.unwrap();
231
232 let (sphere, authorization, mnemonic) =
233 Sphere::generate(&owner_did, &mut db).await.unwrap();
234
235 let root_credential = mnemonic.to_credential()?;
236
237 db.set_version(&sphere.get_identity().await?, sphere.cid())
238 .await?;
239
240 let author = Author {
241 key: root_credential,
242 authorization: Some(authorization),
243 };
244
245 let access = author
246 .access_to(&sphere.get_identity().await.unwrap(), &db)
247 .await
248 .unwrap();
249
250 assert_eq!(access, Access::ReadWrite);
251 Ok(())
252 }
253
254 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
255 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
256 async fn it_can_find_itself_in_an_authorization_lineage() -> Result<()> {
257 let owner_key = generate_ed25519_key();
258 let owner_did = Did(owner_key.get_did().await.unwrap());
259 let mut db = SphereDb::new(&MemoryStorage::default()).await.unwrap();
260
261 let (sphere, authorization, _) = Sphere::generate(&owner_did, &mut db).await.unwrap();
262
263 let next_key = generate_ed25519_key();
264 let next_did = Did(next_key.get_did().await.unwrap());
265
266 let final_did = Did("did:key:foo".into());
267
268 let authorization_ucan = authorization.as_ucan(&db).await?;
269
270 let capability =
271 generate_capability(&sphere.get_identity().await?, SphereAbility::Authorize);
272 let next_ucan = UcanBuilder::default()
273 .issued_by(&owner_key)
274 .for_audience(&next_did)
275 .with_lifetime(100)
276 .claiming_capability(&capability)
277 .witnessed_by(&authorization_ucan, None)
278 .build()?
279 .sign()
280 .await?;
281
282 let final_ucan = UcanBuilder::default()
283 .issued_by(&next_key)
284 .for_audience(&final_did)
285 .with_lifetime(100)
286 .claiming_capability(&capability)
287 .witnessed_by(&next_ucan, None)
288 .build()?
289 .sign()
290 .await?;
291
292 db.write_token(&next_ucan.encode()?).await?;
293 db.write_token(&final_ucan.encode()?).await?;
294
295 let author = Author {
296 key: owner_key,
297 authorization: Some(authorization.clone()),
298 };
299
300 let next_authorization = Authorization::Ucan(next_ucan);
301 let final_authorization = Authorization::Ucan(final_ucan);
302
303 assert!(author.is_authorizer_of(&next_authorization, &db).await?);
304 assert!(author.is_authorizer_of(&final_authorization, &db).await?);
305
306 let next_author = Author {
307 key: next_key,
308 authorization: Some(next_authorization),
309 };
310
311 assert!(!next_author.is_authorizer_of(&authorization, &db).await?);
312
313 let unrelated_key = generate_ed25519_key();
314
315 let unrelated_author = Author {
316 key: unrelated_key,
317 authorization: None,
318 };
319
320 assert!(
321 !unrelated_author
322 .is_authorizer_of(&authorization, &db)
323 .await?
324 );
325 assert!(
326 !unrelated_author
327 .is_authorizer_of(&final_authorization, &db)
328 .await?
329 );
330
331 Ok(())
332 }
333}