Skip to content

Commit b0b2cae

Browse files
authored
fix(rust/signed-doc): Update the Catalyst Signed Document time based validation (#266)
* wip * move threshold value under the `CatalystSignedDocumentProvider` * wip
1 parent e76a071 commit b0b2cae

File tree

5 files changed

+111
-107
lines changed

5 files changed

+111
-107
lines changed

rust/signed_doc/src/providers.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Providers traits, which are used during different validation procedures.
22
3-
use std::future::Future;
3+
use std::{future::Future, time::Duration};
44

55
use catalyst_types::id_uri::IdUri;
66
use ed25519_dalek::VerifyingKey;
@@ -21,12 +21,22 @@ pub trait CatalystSignedDocumentProvider: Send + Sync {
2121
fn try_get_doc(
2222
&self, doc_ref: &DocumentRef,
2323
) -> impl Future<Output = anyhow::Result<Option<CatalystSignedDocument>>> + Send;
24+
25+
/// Returns a future threshold value, which is used in the validation of the `ver`
26+
/// field that it is not too far in the future.
27+
/// If `None` is returned, skips "too far in the future" validation.
28+
fn future_threshold(&self) -> Option<Duration>;
29+
30+
/// Returns a past threshold value, which is used in the validation of the `ver`
31+
/// field that it is not too far behind.
32+
/// If `None` is returned, skips "too far behind" validation.
33+
fn past_threshold(&self) -> Option<Duration>;
2434
}
2535

2636
pub mod tests {
2737
//! Simple providers implementation just for the testing purposes
2838
29-
use std::collections::HashMap;
39+
use std::{collections::HashMap, time::Duration};
3040

3141
use catalyst_types::uuid::Uuid;
3242

@@ -56,6 +66,14 @@ pub mod tests {
5666
) -> anyhow::Result<Option<CatalystSignedDocument>> {
5767
Ok(self.0.get(&doc_ref.id.uuid()).cloned())
5868
}
69+
70+
fn future_threshold(&self) -> Option<std::time::Duration> {
71+
Some(Duration::from_secs(5))
72+
}
73+
74+
fn past_threshold(&self) -> Option<Duration> {
75+
Some(Duration::from_secs(5))
76+
}
5977
}
6078

6179
/// Simple testing implementation of `VerifyingKeyProvider`

rust/signed_doc/src/validator/mod.rs

Lines changed: 79 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
pub(crate) mod rules;
44
pub(crate) mod utils;
55

6-
use std::{collections::HashMap, sync::LazyLock, time::SystemTime};
6+
use std::{
7+
collections::HashMap,
8+
sync::LazyLock,
9+
time::{Duration, SystemTime},
10+
};
711

12+
use anyhow::Context;
813
use catalyst_types::{id_uri::IdUri, problem_report::ProblemReport, uuid::Uuid};
914
use coset::{CoseSign, CoseSignature};
1015
use rules::{
@@ -115,7 +120,7 @@ fn document_rules_init() -> HashMap<Uuid, Rules> {
115120
/// # Errors
116121
/// If `provider` returns error, fails fast throwing that error.
117122
pub async fn validate<Provider>(
118-
doc: &CatalystSignedDocument, future_threshold: u64, past_threshold: u64, provider: &Provider,
123+
doc: &CatalystSignedDocument, provider: &Provider,
119124
) -> anyhow::Result<bool>
120125
where Provider: CatalystSignedDocumentProvider {
121126
let Ok(doc_type) = doc.doc_type() else {
@@ -126,7 +131,7 @@ where Provider: CatalystSignedDocumentProvider {
126131
return Ok(false);
127132
};
128133

129-
if !validate_id_and_ver(doc, future_threshold, past_threshold)? {
134+
if !validate_id_and_ver(doc, provider)? {
130135
return Ok(false);
131136
}
132137

@@ -144,13 +149,15 @@ where Provider: CatalystSignedDocumentProvider {
144149

145150
/// Validates document `id` and `ver` fields on the timestamps:
146151
/// 1. document `ver` cannot be smaller than document id field
147-
/// 2. document `id` cannot be too far in the future (`future_threshold` arg) from
148-
/// `SystemTime::now()` based on the provide threshold
149-
/// 3. document `id` cannot be too far behind (`past_threshold` arg) from
150-
/// `SystemTime::now()` based on the provide threshold
151-
fn validate_id_and_ver(
152-
doc: &CatalystSignedDocument, future_threshold: u64, past_threshold: u64,
153-
) -> anyhow::Result<bool> {
152+
/// 2. If `provider.future_threshold()` not `None`, document `id` cannot be too far in the
153+
/// future (`future_threshold` arg) from `SystemTime::now()` based on the provide
154+
/// threshold
155+
/// 3. If `provider.future_threshold()` not `None`, document `id` cannot be too far behind
156+
/// (`past_threshold` arg) from `SystemTime::now()` based on the provide threshold
157+
fn validate_id_and_ver<Provider>(
158+
doc: &CatalystSignedDocument, provider: &Provider,
159+
) -> anyhow::Result<bool>
160+
where Provider: CatalystSignedDocumentProvider {
154161
let id = doc.doc_id().ok();
155162
let ver = doc.doc_ver().ok();
156163
if id.is_none() {
@@ -178,39 +185,58 @@ fn validate_id_and_ver(
178185
is_valid = false;
179186
}
180187

181-
let (id_time, _) = id
188+
let (ver_time_secs, ver_time_nanos) = ver
182189
.uuid()
183190
.get_timestamp()
184-
.ok_or(anyhow::anyhow!("Document id field must be a UUIDv7"))?
191+
.ok_or(anyhow::anyhow!("Document ver field must be a UUIDv7"))?
185192
.to_unix();
186193

187-
let now = SystemTime::now()
188-
.duration_since(SystemTime::UNIX_EPOCH)
189-
.map_err(|_| {
190-
anyhow::anyhow!(
191-
"Cannot validate document id field, SystemTime before UNIX EPOCH!"
192-
)
193-
})?
194-
.as_secs();
195-
196-
if id_time > now.saturating_add(future_threshold) {
197-
doc.report().invalid_value(
198-
"id",
199-
&ver.to_string(),
200-
"id < now + future_threshold",
201-
&format!("Document ID timestamp {id} cannot be too far in future (threshold: {future_threshold}) from now: {now}"),
202-
);
203-
is_valid = false;
204-
}
205-
if id_time < now.saturating_sub(past_threshold) {
194+
let Some(ver_time) =
195+
SystemTime::UNIX_EPOCH.checked_add(Duration::new(ver_time_secs, ver_time_nanos))
196+
else {
206197
doc.report().invalid_value(
207-
"id",
198+
"ver",
208199
&ver.to_string(),
209-
"id > now - past_threshold",
210-
&format!("Document ID timestamp {id} cannot be too far behind (threshold: {past_threshold}) from now: {now}"),
200+
"Must a valid duration since `UNIX_EPOCH`",
201+
"Cannot instantiate a valid `SystemTime` value from the provided `ver` field timestamp.",
211202
);
212-
is_valid = false;
203+
return Ok(false);
204+
};
205+
206+
let now = SystemTime::now();
207+
208+
if let Ok(version_age) = ver_time.duration_since(now) {
209+
// `now` is earlier than `ver_time`
210+
if let Some(future_threshold) = provider.future_threshold() {
211+
if version_age > future_threshold {
212+
doc.report().invalid_value(
213+
"ver",
214+
&ver.to_string(),
215+
"ver < now + future_threshold",
216+
&format!("Document Version timestamp {id} cannot be too far in future (threshold: {future_threshold:?}) from now: {now:?}"),
217+
);
218+
is_valid = false;
219+
}
220+
}
221+
} else {
222+
// `ver_time` is earlier than `now`
223+
let version_age = now
224+
.duration_since(ver_time)
225+
.context("BUG! `ver_time` must be earlier than `now` at this place")?;
226+
227+
if let Some(past_threshold) = provider.past_threshold() {
228+
if version_age > past_threshold {
229+
doc.report().invalid_value(
230+
"ver",
231+
&ver.to_string(),
232+
"ver > now - past_threshold",
233+
&format!("Document Version timestamp {id} cannot be too far behind (threshold: {past_threshold:?}) from now: {now:?}",),
234+
);
235+
is_valid = false;
236+
}
237+
}
213238
}
239+
214240
Ok(is_valid)
215241
},
216242

@@ -290,24 +316,21 @@ where
290316
Ok(true)
291317
}
292318

293-
#[allow(missing_docs)]
294-
pub mod tests {
295-
/// A Test Future Threshold value for the Document's time based id field validation (5
296-
/// secs);
297-
pub const TEST_FUTURE_THRESHOLD: u64 = 5;
298-
/// A Test Future Threshold value for the Document's time based id field validation (5
299-
/// secs);
300-
pub const TEST_PAST_THRESHOLD: u64 = 5;
301-
302-
#[cfg(test)]
303-
#[test]
304-
fn document_id_and_ver_test() {
305-
use std::time::SystemTime;
319+
#[cfg(test)]
320+
mod tests {
321+
use std::time::SystemTime;
306322

307-
use uuid::{Timestamp, Uuid};
323+
use uuid::{Timestamp, Uuid};
308324

309-
use crate::{validator::validate_id_and_ver, Builder, UuidV7};
325+
use crate::{
326+
providers::{tests::TestCatalystSignedDocumentProvider, CatalystSignedDocumentProvider},
327+
validator::{document_rules_init, validate_id_and_ver},
328+
Builder, UuidV7,
329+
};
310330

331+
#[test]
332+
fn document_id_and_ver_test() {
333+
let provider = TestCatalystSignedDocumentProvider::default();
311334
let now = SystemTime::now()
312335
.duration_since(SystemTime::UNIX_EPOCH)
313336
.unwrap()
@@ -322,8 +345,7 @@ pub mod tests {
322345
.unwrap()
323346
.build();
324347

325-
let is_valid =
326-
validate_id_and_ver(&doc, TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD).unwrap();
348+
let is_valid = validate_id_and_ver(&doc, &provider).unwrap();
327349
assert!(is_valid);
328350

329351
let ver = Uuid::new_v7(Timestamp::from_unix_time(now - 1, 0, 0, 0));
@@ -337,12 +359,11 @@ pub mod tests {
337359
.unwrap()
338360
.build();
339361

340-
let is_valid =
341-
validate_id_and_ver(&doc, TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD).unwrap();
362+
let is_valid = validate_id_and_ver(&doc, &provider).unwrap();
342363
assert!(!is_valid);
343364

344365
let to_far_in_past = Uuid::new_v7(Timestamp::from_unix_time(
345-
now - TEST_PAST_THRESHOLD - 1,
366+
now - provider.past_threshold().unwrap().as_secs() - 1,
346367
0,
347368
0,
348369
0,
@@ -355,12 +376,11 @@ pub mod tests {
355376
.unwrap()
356377
.build();
357378

358-
let is_valid =
359-
validate_id_and_ver(&doc, TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD).unwrap();
379+
let is_valid = validate_id_and_ver(&doc, &provider).unwrap();
360380
assert!(!is_valid);
361381

362382
let to_far_in_future = Uuid::new_v7(Timestamp::from_unix_time(
363-
now + TEST_FUTURE_THRESHOLD + 1,
383+
now + provider.future_threshold().unwrap().as_secs() + 1,
364384
0,
365385
0,
366386
0,
@@ -373,16 +393,12 @@ pub mod tests {
373393
.unwrap()
374394
.build();
375395

376-
let is_valid =
377-
validate_id_and_ver(&doc, TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD).unwrap();
396+
let is_valid = validate_id_and_ver(&doc, &provider).unwrap();
378397
assert!(!is_valid);
379398
}
380399

381-
#[cfg(test)]
382400
#[test]
383401
fn document_rules_init_test() {
384-
use super::document_rules_init;
385-
386402
document_rules_init();
387403
}
388404
}

rust/signed_doc/tests/comment.rs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
//! Integration test for comment document validation part.
22
3-
use catalyst_signed_doc::{
4-
providers::tests::TestCatalystSignedDocumentProvider,
5-
validator::tests::{TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD},
6-
*,
7-
};
3+
use catalyst_signed_doc::{providers::tests::TestCatalystSignedDocumentProvider, *};
84

95
mod common;
106

@@ -35,9 +31,7 @@ async fn test_valid_comment_doc() {
3531
provider.add_document(template_doc).unwrap();
3632
provider.add_document(proposal_doc).unwrap();
3733

38-
let is_valid = validator::validate(&doc, TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD, &provider)
39-
.await
40-
.unwrap();
34+
let is_valid = validator::validate(&doc, &provider).await.unwrap();
4135

4236
assert!(is_valid);
4337
}
@@ -91,9 +85,7 @@ async fn test_valid_comment_doc_with_reply() {
9185
provider.add_document(proposal_doc).unwrap();
9286
provider.add_document(comment_doc).unwrap();
9387

94-
let is_valid = validator::validate(&doc, TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD, &provider)
95-
.await
96-
.unwrap();
88+
let is_valid = validator::validate(&doc, &provider).await.unwrap();
9789

9890
assert!(is_valid);
9991
}
@@ -124,9 +116,7 @@ async fn test_invalid_comment_doc() {
124116
provider.add_document(template_doc).unwrap();
125117
provider.add_document(proposal_doc).unwrap();
126118

127-
let is_valid = validator::validate(&doc, TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD, &provider)
128-
.await
129-
.unwrap();
119+
let is_valid = validator::validate(&doc, &provider).await.unwrap();
130120

131121
assert!(!is_valid);
132122
}

rust/signed_doc/tests/proposal.rs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
//! Integration test for proposal document validation part.
22
3-
use catalyst_signed_doc::{
4-
providers::tests::TestCatalystSignedDocumentProvider,
5-
validator::tests::{TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD},
6-
*,
7-
};
3+
use catalyst_signed_doc::{providers::tests::TestCatalystSignedDocumentProvider, *};
84

95
mod common;
106

@@ -29,9 +25,7 @@ async fn test_valid_proposal_doc() {
2925
let mut provider = TestCatalystSignedDocumentProvider::default();
3026
provider.add_document(template_doc).unwrap();
3127

32-
let is_valid = validator::validate(&doc, TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD, &provider)
33-
.await
34-
.unwrap();
28+
let is_valid = validator::validate(&doc, &provider).await.unwrap();
3529

3630
assert!(is_valid);
3731
}
@@ -56,9 +50,7 @@ async fn test_valid_proposal_doc_with_empty_provider() {
5650

5751
let provider = TestCatalystSignedDocumentProvider::default();
5852

59-
let is_valid = validator::validate(&doc, TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD, &provider)
60-
.await
61-
.unwrap();
53+
let is_valid = validator::validate(&doc, &provider).await.unwrap();
6254

6355
assert!(!is_valid);
6456
}
@@ -79,9 +71,7 @@ async fn test_invalid_proposal_doc() {
7971

8072
let provider = TestCatalystSignedDocumentProvider::default();
8173

82-
let is_valid = validator::validate(&doc, TEST_FUTURE_THRESHOLD, TEST_PAST_THRESHOLD, &provider)
83-
.await
84-
.unwrap();
74+
let is_valid = validator::validate(&doc, &provider).await.unwrap();
8575

8676
assert!(!is_valid);
8777
}

0 commit comments

Comments
 (0)