crev_data/proof/
content.rs

1use crate::{proof, proof::Proof, Error, ParseError, Result};
2use chrono::{self, prelude::*};
3use crev_common::{
4    self,
5    serde::{as_base64, as_rfc3339_fixed, from_base64, from_rfc3339_fixed},
6};
7use derive_builder::Builder;
8use serde::{self, Deserialize, Serialize};
9use std::{fmt, io};
10
11pub type Date = chrono::DateTime<FixedOffset>;
12
13/// Common operations on types containing `Common`
14pub trait CommonOps {
15    // until we support legacy, we have to stick to `Common` here
16    fn common(&self) -> &Common;
17
18    /// Is it crate review or something else?
19    fn kind(&self) -> &str {
20        self.common()
21            .kind
22            .as_ref()
23            .expect("Common types are expected to always have the `kind` field backfilled")
24    }
25
26    /// Who wrote and signed this review/trust
27    fn from(&self) -> &crate::PublicId {
28        &self.common().from
29    }
30
31    /// When it has been written according to its creator
32    fn date(&self) -> &chrono::DateTime<chrono::offset::FixedOffset> {
33        &self.common().date
34    }
35
36    /// When it has been written according to its creator
37    fn date_utc(&self) -> chrono::DateTime<Utc> {
38        self.date().with_timezone(&Utc)
39    }
40
41    /// ID of the person who signed it
42    fn author_id(&self) -> &crate::Id {
43        &self.common().from.id
44    }
45
46    /// Displayable version of ID of the person who signed it
47    fn author_public_id(&self) -> &crate::PublicId {
48        &self.common().from
49    }
50
51    /// Easy check of `kind()`
52    fn ensure_kind_is(&self, kind: &str) -> ValidationResult<()> {
53        let expected = self.kind();
54        if expected != kind {
55            return Err(ValidationError::InvalidKind(Box::new((
56                expected.to_string(),
57                kind.to_string(),
58            ))));
59        }
60        Ok(())
61    }
62}
63
64/// Reference to original proof when reissuing
65#[derive(Clone, Debug, Serialize, Deserialize)]
66pub struct OriginalReference {
67    /// original proof digest (blake2b256)
68    #[serde(serialize_with = "as_base64", deserialize_with = "from_base64")]
69    pub proof: Vec<u8>,
70    /// Any text
71    #[serde(skip_serializing_if = "String::is_empty", default = "Default::default")]
72    pub comment: String,
73}
74
75/// A `Common` part of every `Content` format
76#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
77pub struct Common {
78    /// Type of this review/trust/whatever file
79    pub kind: Option<String>,
80    /// A version, to allow future backward-incompatible extensions
81    /// and changes.
82    pub version: i64,
83    #[builder(default = "crev_common::now()")]
84    #[serde(
85        serialize_with = "as_rfc3339_fixed",
86        deserialize_with = "from_rfc3339_fixed"
87    )]
88    /// Timestamp of proof creation
89    pub date: chrono::DateTime<FixedOffset>,
90    /// Author of the proof
91    pub from: crate::PublicId,
92    /// Reference to original proof when reissuing
93    #[serde(skip_serializing_if = "Option::is_none", default = "Option::default")]
94    pub original: Option<OriginalReference>,
95}
96
97impl CommonOps for Common {
98    fn common(&self) -> &Common {
99        self
100    }
101}
102
103pub trait WithReview {
104    fn review(&self) -> &super::Review;
105}
106
107#[derive(Debug, thiserror::Error)]
108pub enum ValidationError {
109    #[error("Invalid kind: {}, expected: {}", _0.0, _0.1)]
110    InvalidKind(Box<(String, String)>),
111
112    /// Alternative source can't be empty
113    #[error("Alternative source can't be empty")]
114    AlternativeSourceCanNotBeEmpty,
115
116    /// Alternative name can't be empty
117    #[error("Alternative name can't be empty")]
118    AlternativeNameCanNotBeEmpty,
119
120    /// Issues with an empty `id` field are not allowed
121    #[error("Issues with an empty `id` field are not allowed")]
122    IssuesWithAnEmptyIDFieldAreNotAllowed,
123
124    /// Advisories with no `id`s are not allowed
125    #[error("Advisories with no `id`s are not allowed")]
126    AdvisoriesWithNoIDSAreNotAllowed,
127
128    /// Advisories with an empty `id` field are not allowed
129    #[error("Advisories with an empty `id` field are not allowed")]
130    AdvisoriesWithAnEmptyIDFieldAreNotAllowed,
131}
132
133pub type ValidationResult<T> = std::result::Result<T, ValidationError>;
134
135/// Proof Content
136///
137/// `Content` is a standardized format of a crev proof body
138/// (part that is being signed over).
139///
140/// It is open-ended, and different software
141/// can implement their own formats.
142pub trait Content: CommonOps {
143    fn validate_data(&self) -> ValidationResult<()> {
144        // typically just OK
145        Ok(())
146    }
147
148    fn serialize_to(&self, fmt: &mut dyn std::fmt::Write) -> fmt::Result;
149}
150
151pub trait ContentDeserialize: Content + Sized {
152    fn deserialize_from<IO>(io: IO) -> std::result::Result<Self, Error>
153    where
154        IO: io::Read;
155}
156
157impl<T> ContentDeserialize for T
158where
159    T: serde::de::DeserializeOwned + Content + Sized,
160{
161    fn deserialize_from<IO>(io: IO) -> std::result::Result<Self, Error>
162    where
163        IO: io::Read,
164    {
165        let s: Self = serde_yaml::from_reader(io).map_err(ParseError::Proof)?;
166
167        s.validate_data()?;
168
169        Ok(s)
170    }
171}
172
173/// A Proof Content `Draft`
174///
175/// A simplified version of content, used
176/// for user interaction - editing the parts
177/// that are not necessary for the user to see.
178pub struct Draft {
179    pub(crate) title: String,
180    pub(crate) body: String,
181}
182
183impl Draft {
184    #[must_use]
185    pub fn title(&self) -> &str {
186        &self.title
187    }
188
189    #[must_use]
190    pub fn body(&self) -> &str {
191        &self.body
192    }
193}
194
195/// A content with draft support
196///
197/// Draft is a compact, human
198pub trait ContentWithDraft: Content {
199    fn to_draft(&self) -> Draft;
200
201    fn apply_draft(&self, body: &str) -> Result<Self>
202    where
203        Self: Sized;
204}
205
206pub trait ContentExt: Content {
207    fn serialize(&self) -> Result<String> {
208        let mut body = String::new();
209        self.serialize_to(&mut body)
210            .map_err(|e| crate::Error::YAMLFormat(e.to_string().into()))?;
211        Ok(body)
212    }
213
214    fn sign_by(&self, id: &crate::id::UnlockedId) -> Result<Proof> {
215        let body = self.serialize()?;
216        let signature = id.sign(body.as_bytes());
217        Ok(Proof {
218            digest: crev_common::blake2b256sum(body.as_bytes()),
219            body,
220            signature: crev_common::base64_encode(&signature),
221            common_content: self.common().clone(),
222        })
223    }
224
225    /// Ensure the proof generated from this `Content` is going to deserialize
226    fn ensure_serializes_to_valid_proof(&self) -> Result<()> {
227        let body = self.serialize()?;
228        let signature = "somefakesignature";
229        let proof = proof::Proof {
230            digest: crev_common::blake2b256sum(body.as_bytes()),
231            body,
232            signature: crev_common::base64_encode(&signature),
233            common_content: self.common().clone(),
234        };
235        let parsed = proof::Proof::parse_from(std::io::Cursor::new(proof.to_string().as_bytes()))?;
236
237        if parsed.len() != 1 {
238            return Err(Error::SerializedTooManyProofs(parsed.len()));
239        }
240
241        Ok(())
242    }
243}
244
245impl<T> ContentExt for T where T: Content {}