1use std::collections::BTreeMap;
7#[cfg(feature = "server")]
8use std::collections::HashMap;
9
10use credibil_infosec::jose::jwk::PublicKeyJwk;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14use super::{Cursor, Descriptor};
15use crate::authorization::Authorization;
16use crate::hd_key::{self, DerivationPath, DerivationScheme, DerivedPrivateJwk, PrivateKeyJwk};
17use crate::utils::cid;
18use crate::{Result, bad};
19
20pub const PROTOCOL_URI: &str = "https://credibil.website/dwn/permissions";
22
23pub const REQUEST_PATH: &str = "request";
25
26pub const GRANT_PATH: &str = "grant";
28
29pub const REVOCATION_PATH: &str = "grant/revocation";
31
32#[derive(Clone, Debug, Default, Deserialize, Serialize)]
34pub struct Configure {
35 pub descriptor: ConfigureDescriptor,
37
38 pub authorization: Authorization,
40
41 #[serde(skip)]
43 #[cfg(feature = "server")]
44 pub(crate) indexes: HashMap<String, String>,
45}
46
47impl Configure {
48 pub fn cid(&self) -> Result<String> {
54 cid::from_value(self)
55 }
56}
57
58#[derive(Clone, Debug, Default, Deserialize, Serialize)]
60#[serde(rename_all = "camelCase")]
61pub struct ConfigureDescriptor {
62 #[serde(flatten)]
64 pub base: Descriptor,
65
66 pub definition: Definition,
68}
69
70#[derive(Clone, Debug, Default, Deserialize, Serialize)]
72#[serde(rename_all = "camelCase")]
73pub struct Definition {
74 pub protocol: String,
76
77 pub published: bool,
80
81 pub types: BTreeMap<String, ProtocolType>,
83
84 pub structure: BTreeMap<String, RuleSet>,
86}
87
88impl Definition {
89 #[must_use]
91 pub fn new(protocol: impl Into<String>) -> Self {
92 Self {
93 protocol: protocol.into(),
94 ..Self::default()
95 }
96 }
97
98 #[must_use]
100 pub const fn published(mut self, published: bool) -> Self {
101 self.published = published;
102 self
103 }
104
105 #[must_use]
107 pub fn add_type(mut self, name: impl Into<String>, type_: ProtocolType) -> Self {
108 self.types.insert(name.into(), type_);
109 self
110 }
111
112 #[must_use]
114 pub fn add_rule(mut self, name: impl Into<String>, rule_set: RuleSet) -> Self {
115 self.structure.insert(name.into(), rule_set);
116 self
117 }
118
119 pub fn with_encryption(
126 mut self, root_key_id: &str, private_key_jwk: PrivateKeyJwk,
127 ) -> Result<Self> {
128 let root_key = DerivedPrivateJwk {
129 root_key_id: root_key_id.to_string(),
130 derivation_scheme: DerivationScheme::ProtocolPath,
131 derivation_path: None,
132 derived_private_key: private_key_jwk,
133 };
134
135 let path = vec![DerivationScheme::ProtocolPath.to_string(), self.protocol.clone()];
137 let derived_jwk = hd_key::derive_jwk(root_key, &DerivationPath::Relative(&path))?;
138
139 add_encryption(&mut self.structure, &derived_jwk)?;
141
142 Ok(self)
143 }
144}
145
146fn add_encryption(
147 structure: &mut BTreeMap<String, RuleSet>, parent_key: &DerivedPrivateJwk,
148) -> Result<()> {
149 for (key, rule_set) in structure {
150 let derived_jwk =
151 hd_key::derive_jwk(parent_key.clone(), &DerivationPath::Relative(&[key.clone()]))?;
152 let public_key_jwk = derived_jwk.derived_private_key.public_key.clone();
153 rule_set.encryption = Some(PathEncryption {
154 root_key_id: parent_key.root_key_id.clone(),
155 public_key_jwk,
156 });
157
158 add_encryption(&mut rule_set.structure, &derived_jwk)?;
160 }
161 Ok(())
162}
163
164#[derive(Clone, Debug, Default, Deserialize, Serialize)]
166#[serde(rename_all = "camelCase")]
167pub struct ProtocolType {
168 #[serde(skip_serializing_if = "Option::is_none")]
170 pub schema: Option<String>,
171
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub data_formats: Option<Vec<String>>,
175}
176
177#[derive(Clone, Debug, Default, Deserialize, Serialize)]
179pub struct RuleSet {
180 #[serde(skip_serializing_if = "Option::is_none")]
182 #[serde(rename = "$encryption")]
183 pub encryption: Option<PathEncryption>,
184
185 #[serde(skip_serializing_if = "Option::is_none")]
187 #[serde(rename = "$actions")]
188 pub actions: Option<Vec<ActionRule>>,
189
190 #[serde(skip_serializing_if = "Option::is_none")]
192 #[serde(rename = "$role")]
193 pub role: Option<bool>,
194
195 #[serde(skip_serializing_if = "Option::is_none")]
197 #[serde(rename = "$size")]
198 pub size: Option<Size>,
199
200 #[serde(skip_serializing_if = "Option::is_none")]
202 #[serde(rename = "$tags")]
203 pub tags: Option<Tags>,
204
205 #[serde(flatten)]
208 pub structure: BTreeMap<String, RuleSet>,
209}
210
211#[derive(Clone, Debug, Default, Deserialize, Serialize)]
213#[serde(rename_all = "camelCase")]
214pub struct PathEncryption {
215 pub root_key_id: String,
217
218 pub public_key_jwk: PublicKeyJwk,
220}
221
222#[derive(Clone, Debug, Default, Deserialize, Serialize)]
251#[serde(rename_all = "camelCase")]
252pub struct ActionRule {
253 #[serde(skip_serializing_if = "Option::is_none")]
256 pub who: Option<Actor>,
257
258 #[serde(skip_serializing_if = "Option::is_none")]
261 pub role: Option<String>,
262
263 #[serde(skip_serializing_if = "Option::is_none")]
266 pub of: Option<String>,
267
268 pub can: Vec<Action>,
271}
272
273#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
275#[serde(rename_all = "camelCase")]
276pub enum Actor {
277 #[default]
279 Anyone,
280
281 Author,
283
284 Recipient,
286}
287
288#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
290#[serde(rename_all = "camelCase")]
291pub enum Action {
292 Create,
294
295 Delete,
297
298 Prune,
300
301 Query,
303
304 Subscribe,
306
307 #[default]
309 Read,
310
311 Update,
313
314 #[serde(rename = "co-delete")]
316 CoDelete,
317
318 #[serde(rename = "co-prune")]
320 CoPrune,
321
322 #[serde(rename = "co-update")]
324 CoUpdate,
325}
326
327#[derive(Clone, Debug, Default, Deserialize, Serialize)]
329pub struct Size {
330 #[serde(skip_serializing_if = "Option::is_none")]
332 pub min: Option<usize>,
333
334 #[serde(skip_serializing_if = "Option::is_none")]
336 pub max: Option<usize>,
337}
338
339#[derive(Clone, Debug, Default, Deserialize, Serialize)]
341#[serde(rename_all = "camelCase")]
342pub struct Tags {
343 #[serde(skip_serializing_if = "Option::is_none")]
345 #[serde(rename = "$requiredTags")]
346 pub required: Option<Vec<String>>,
347
348 #[serde(skip_serializing_if = "Option::is_none")]
350 #[serde(rename = "$allowUndefinedTags")]
351 pub allow_undefined: Option<bool>,
352
353 #[serde(flatten)]
355 pub undefined: BTreeMap<String, Value>,
356}
357
358#[derive(Clone, Debug, Default, Deserialize, Serialize)]
360pub struct ConfigureReply {
361 #[serde(flatten)]
363 pub message: Configure,
364}
365
366#[derive(PartialEq, Eq, PartialOrd)]
368pub enum Access {
369 Published,
371
372 Unpublished,
374}
375
376#[derive(Clone, Debug, Default, Deserialize, Serialize)]
378pub struct Query {
379 pub descriptor: QueryDescriptor,
381
382 #[serde(skip_serializing_if = "Option::is_none")]
384 pub authorization: Option<Authorization>,
385}
386
387#[derive(Clone, Debug, Default, Deserialize, Serialize)]
389#[serde(rename_all = "camelCase")]
390pub struct QueryDescriptor {
391 #[serde(flatten)]
393 pub base: Descriptor,
394
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub filter: Option<ProtocolsFilter>,
398}
399
400#[derive(Clone, Debug, Default, Deserialize, Serialize)]
402pub struct QueryReply {
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub entries: Option<Vec<Configure>>,
406
407 #[serde(skip_serializing_if = "Option::is_none")]
409 pub cursor: Option<Cursor>,
410}
411
412#[derive(Clone, Debug, Default, Deserialize, Serialize)]
414#[serde(rename_all = "camelCase")]
415pub struct ProtocolsFilter {
416 pub protocol: String,
418}
419
420pub(crate) fn validate_structure(definition: &Definition) -> Result<()> {
422 let keys = definition.types.keys().collect::<Vec<&String>>();
423
424 let roles = role_paths("", &definition.structure, &[])?;
426
427 for rule_set in definition.structure.values() {
429 validate_rule_set(rule_set, "", &keys, &roles)?;
430 }
431
432 Ok(())
433}
434
435fn validate_rule_set(
437 rule_set: &RuleSet, protocol_path: &str, types: &Vec<&String>, roles: &Vec<String>,
438) -> Result<()> {
439 if let Some(size) = &rule_set.size {
441 if size.max.is_some() && size.min > size.max {
442 return Err(bad!("invalid size range"));
443 }
444 }
445
446 if let Some(tags) = &rule_set.tags {
448 for tag in tags.undefined.keys() {
449 let schema = serde_json::from_str(tag)?;
450 jsonschema::validator_for(&schema)
451 .map_err(|e| bad!("tag schema validation error: {e}"))?;
452 }
453 }
454
455 let empty = Vec::new();
457 let mut action_iter = rule_set.actions.as_ref().unwrap_or(&empty).iter();
458
459 while let Some(action) = action_iter.next() {
460 if let Some(role) = &action.role {
462 if !roles.contains(role) {
464 return Err(bad!("missing role {role} in action"));
465 }
466
467 let mut read_actions = vec![Action::Read, Action::Query, Action::Subscribe];
470 read_actions.retain(|ra| action.can.contains(ra));
471
472 if !read_actions.is_empty() && read_actions.len() != 3 {
474 return Err(bad!("role {role} is missing read-like actions"));
475 }
476 }
477
478 if action.who.as_ref().is_some_and(|w| w == &Actor::Anyone) && action.of.is_some() {
480 return Err(bad!("`of` must not be set when `who` is \"anyone\""));
481 }
482
483 if action.who.as_ref().is_some_and(|w| w == &Actor::Recipient) && action.of.is_none() {
492 let allowed = [Action::CoUpdate, Action::CoDelete, Action::CoPrune];
493 if !allowed.iter().any(|ra| action.can.contains(ra)) {
494 return Err(bad!(
495 "recipient action must contain only co-update, co-delete, and co-prune",
496 ));
497 }
498 }
499
500 if action.who.as_ref().is_some_and(|w| w == &Actor::Author) && action.of.is_none() {
502 return Err(bad!("`of` must be set when `who` is set to 'author'"));
503 }
504
505 if action.can.contains(&Action::Update) && !action.can.contains(&Action::Create) {
507 return Err(bad!("action rule {action:?} contains 'update' but no 'create'"));
508 }
509 if action.can.contains(&Action::Delete) && !action.can.contains(&Action::Create) {
510 return Err(bad!("action rule {action:?} contains 'delete' but no 'create'"));
511 }
512
513 for other in action_iter.clone() {
516 if action.who.is_some() {
517 if action.who == other.who && action.of == other.of {
518 return Err(bad!("an actor may only have one rule within a rule set"));
519 }
520 } else if action.role == other.role {
521 return Err(bad!(
522 "more than one action rule per role {:?} not allowed within a rule set: {action:?}",
523 action.role
524 ));
525 }
526 }
527 }
528
529 for (set_name, rule_set) in &rule_set.structure {
531 if !types.contains(&set_name) {
532 return Err(bad!("rule set {set_name} is not declared as an allowed type"));
533 }
534 let protocol_path = if protocol_path.is_empty() {
535 set_name
536 } else {
537 &format!("{protocol_path}/{set_name}")
538 };
539 validate_rule_set(rule_set, protocol_path, types, roles)?;
540 }
541
542 Ok(())
543}
544
545fn role_paths(
547 protocol_path: &str, structure: &BTreeMap<String, RuleSet>, roles: &[String],
548) -> Result<Vec<String>> {
549 if protocol_path.split('/').count() > 10 {
551 return Err(bad!("Storable nesting depth exceeded 10 levels."));
552 }
553
554 let mut roles = roles.to_owned();
555
556 for (rule_name, rule_set) in structure {
558 let protocol_path = if protocol_path.is_empty() {
559 rule_name
560 } else {
561 &format!("{protocol_path}/{rule_name}")
562 };
563
564 if rule_set.role.is_some() {
565 roles.push(protocol_path.to_string());
566 } else {
567 roles = role_paths(protocol_path, &rule_set.structure, &roles)?;
568 }
569 }
570
571 Ok(roles)
572}