89 lines
2.8 KiB
Rust
89 lines
2.8 KiB
Rust
//! Durable, deployment-scoped consent records.
|
|
//!
|
|
//! This deliberately contains no UI code. Both the terminal client and the
|
|
//! daemon use the same record so a UI-local decision can never start services.
|
|
|
|
use crate::{Doc, TermsType as Type};
|
|
use std::fs;
|
|
use std::io;
|
|
use std::path::Path;
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
const FILE_NAME: &str = "terms-consent-v1";
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct ConsentRecord {
|
|
pub eula: Option<(String, String)>,
|
|
pub tos: Option<(String, String)>,
|
|
pub privacy: Option<(String, String)>,
|
|
}
|
|
|
|
impl ConsentRecord {
|
|
pub fn has_all_required(&self) -> bool {
|
|
self.eula.is_some() && self.tos.is_some() && self.privacy.is_some()
|
|
}
|
|
|
|
pub fn accepts(&self, eula: &Doc, tos: &Doc, privacy: &Doc) -> bool {
|
|
matches_doc(&self.eula, eula)
|
|
&& matches_doc(&self.tos, tos)
|
|
&& matches_doc(&self.privacy, privacy)
|
|
}
|
|
|
|
pub fn accept(&mut self, doc: &Doc) {
|
|
let value = Some((doc.get_version(), doc.get_hash()));
|
|
match doc.doc_type {
|
|
Type::EULA => self.eula = value,
|
|
Type::TOS => self.tos = value,
|
|
Type::PP => self.privacy = value,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn matches_doc(value: &Option<(String, String)>, doc: &Doc) -> bool {
|
|
matches!(value, Some((version, hash)) if version == &doc.get_version() && hash == &doc.get_hash())
|
|
}
|
|
|
|
pub fn load(state_dir: &Path) -> ConsentRecord {
|
|
let Ok(text) = fs::read_to_string(state_dir.join(FILE_NAME)) else {
|
|
return ConsentRecord::default();
|
|
};
|
|
let mut record = ConsentRecord::default();
|
|
for line in text.lines() {
|
|
let Some((key, value)) = line.split_once('=') else {
|
|
continue;
|
|
};
|
|
let Some((version, hash)) = value.split_once(':') else {
|
|
continue;
|
|
};
|
|
let value = Some((version.to_owned(), hash.to_owned()));
|
|
match key {
|
|
"eula" => record.eula = value,
|
|
"tos" => record.tos = value,
|
|
"privacy" => record.privacy = value,
|
|
_ => {}
|
|
}
|
|
}
|
|
record
|
|
}
|
|
|
|
pub fn save(state_dir: &Path, record: &ConsentRecord) -> io::Result<()> {
|
|
fs::create_dir_all(state_dir)?;
|
|
let timestamp = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs();
|
|
let mut text = format!("# Iota terms consent record; accepted_at_unix={timestamp}\n");
|
|
for (name, value) in [
|
|
("eula", &record.eula),
|
|
("tos", &record.tos),
|
|
("privacy", &record.privacy),
|
|
] {
|
|
if let Some((version, hash)) = value {
|
|
text.push_str(&format!("{name}={version}:{hash}\n"));
|
|
}
|
|
}
|
|
let path = state_dir.join(FILE_NAME);
|
|
let temporary = state_dir.join(format!(".{FILE_NAME}.{}.tmp", std::process::id()));
|
|
fs::write(&temporary, text)?;
|
|
fs::rename(temporary, path)
|
|
}
|