|
| 1 | +//! Original Author Validation Rule |
| 2 | +
|
| 3 | +use crate::{providers::CatalystSignedDocumentProvider, CatalystSignedDocument}; |
| 4 | + |
| 5 | +/// Original Author Validation Rule |
| 6 | +#[derive(Debug)] |
| 7 | +pub(crate) struct OriginalAuthorRule; |
| 8 | + |
| 9 | +impl OriginalAuthorRule { |
| 10 | + /// Field validation rule |
| 11 | + pub(crate) async fn check<Provider>( |
| 12 | + &self, |
| 13 | + doc: &CatalystSignedDocument, |
| 14 | + provider: &Provider, |
| 15 | + ) -> anyhow::Result<bool> |
| 16 | + where |
| 17 | + Provider: CatalystSignedDocumentProvider, |
| 18 | + { |
| 19 | + let doc_id = doc.doc_id()?; |
| 20 | + let Some(original_doc) = provider.try_get_last_doc(doc_id).await? else { |
| 21 | + return Ok(true); |
| 22 | + }; |
| 23 | + let is_valid = original_doc.authors() == doc.authors(); |
| 24 | + if !is_valid { |
| 25 | + doc.report().functional_validation( |
| 26 | + &format!("New document authors must match the authors from the previous version for Document ID {doc_id}"), |
| 27 | + &format!( |
| 28 | + "Document's signatures must be identical to previous version for Document ID {doc_id}" |
| 29 | + ), |
| 30 | + ); |
| 31 | + } |
| 32 | + Ok(is_valid) |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +#[cfg(test)] |
| 37 | +mod tests { |
| 38 | + use catalyst_types::{ |
| 39 | + catalyst_id::{role_index::RoleId, CatalystId}, |
| 40 | + uuid::{UuidV4, UuidV7}, |
| 41 | + }; |
| 42 | + use ed25519_dalek::ed25519::signature::Signer; |
| 43 | + use test_case::test_case; |
| 44 | + |
| 45 | + use super::*; |
| 46 | + use crate::{ |
| 47 | + builder::tests::Builder, metadata::SupportedField, providers::tests::TestCatalystProvider, |
| 48 | + ContentType, |
| 49 | + }; |
| 50 | + |
| 51 | + #[derive(Clone)] |
| 52 | + struct CatalystAuthorId { |
| 53 | + sk: ed25519_dalek::SigningKey, |
| 54 | + kid: CatalystId, |
| 55 | + } |
| 56 | + |
| 57 | + impl CatalystAuthorId { |
| 58 | + fn new() -> Self { |
| 59 | + let sk = ed25519_dalek::SigningKey::generate(&mut rand::rngs::OsRng); |
| 60 | + let pk = sk.verifying_key(); |
| 61 | + let kid = CatalystId::new("cardano", None, pk).with_role(RoleId::Role0); |
| 62 | + Self { sk, kid } |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + fn doc_builder( |
| 67 | + doc_id: UuidV7, |
| 68 | + doc_ver: UuidV7, |
| 69 | + authors: [CatalystAuthorId; 3], |
| 70 | + ) -> (UuidV7, [CatalystAuthorId; 3], CatalystSignedDocument) { |
| 71 | + let mut doc_builder = Builder::new() |
| 72 | + .with_metadata_field(SupportedField::Id(doc_id)) |
| 73 | + .with_metadata_field(SupportedField::Ver(doc_ver)) |
| 74 | + .with_metadata_field(SupportedField::Type(UuidV4::new().into())) |
| 75 | + .with_metadata_field(SupportedField::ContentType(ContentType::Json)) |
| 76 | + .with_content(vec![1, 2, 3]); |
| 77 | + for author in &authors { |
| 78 | + doc_builder = doc_builder |
| 79 | + .add_signature(|m| author.sk.sign(&m).to_vec(), author.kid.clone()) |
| 80 | + .unwrap(); |
| 81 | + } |
| 82 | + (doc_id, authors, doc_builder.build()) |
| 83 | + } |
| 84 | + |
| 85 | + fn gen_authors() -> [CatalystAuthorId; 3] { |
| 86 | + [ |
| 87 | + CatalystAuthorId::new(), |
| 88 | + CatalystAuthorId::new(), |
| 89 | + CatalystAuthorId::new(), |
| 90 | + ] |
| 91 | + } |
| 92 | + |
| 93 | + fn gen_next_ver_doc( |
| 94 | + doc_id: UuidV7, |
| 95 | + authors: [CatalystAuthorId; 3], |
| 96 | + ) -> CatalystSignedDocument { |
| 97 | + let (_, _, new_doc) = doc_builder(doc_id, UuidV7::new(), authors); |
| 98 | + new_doc |
| 99 | + } |
| 100 | + |
| 101 | + fn gen_original_doc_and_provider() -> (UuidV7, [CatalystAuthorId; 3], TestCatalystProvider) { |
| 102 | + let authors = gen_authors(); |
| 103 | + let doc_id = UuidV7::new(); |
| 104 | + let doc_ver_1 = UuidV7::new(); |
| 105 | + let (_, _, doc_1) = doc_builder(doc_id, doc_ver_1, authors.clone()); |
| 106 | + let mut provider = TestCatalystProvider::default(); |
| 107 | + provider.add_document(None, &doc_1).unwrap(); |
| 108 | + (doc_id, authors, provider) |
| 109 | + } |
| 110 | + |
| 111 | + #[test_case( |
| 112 | + || { |
| 113 | + let (doc_id, authors, provider) = gen_original_doc_and_provider(); |
| 114 | + let doc_2 = gen_next_ver_doc(doc_id, authors); |
| 115 | + (doc_2, provider) |
| 116 | + } => true ; |
| 117 | + "Catalyst Signed Document has the same authors as the previous version" |
| 118 | + )] |
| 119 | + #[test_case( |
| 120 | + || { |
| 121 | + let (doc_id, _, provider) = gen_original_doc_and_provider(); |
| 122 | + let other_authors = gen_authors(); |
| 123 | + let doc_2 = gen_next_ver_doc(doc_id, other_authors); |
| 124 | + (doc_2, provider) |
| 125 | + } => false ; |
| 126 | + "Catalyst Signed Document has the different authors from the previous version" |
| 127 | + )] |
| 128 | + #[tokio::test] |
| 129 | + async fn original_author_rule_test( |
| 130 | + doc_gen: impl FnOnce() -> (CatalystSignedDocument, TestCatalystProvider) |
| 131 | + ) -> bool { |
| 132 | + let (doc_v2, provider) = doc_gen(); |
| 133 | + |
| 134 | + OriginalAuthorRule.check(&doc_v2, &provider).await.unwrap() |
| 135 | + } |
| 136 | +} |
0 commit comments