use serde::{Deserialize, Serialize};
use crate::session::{PermissionRuleScope, permission::RememberedRule};
use super::{
super::store::PermissionRuleStore, FileRuntimeStore, RuntimeError, SCHEMA_VERSION, fs_util,
lock_unpoisoned, parse_versioned, to_pretty_json,
};
#[derive(Serialize, Deserialize)]
struct RulesFile {
schema: u32,
rules: Vec<StoredRule>,
}
#[derive(Serialize, Deserialize, PartialEq)]
struct StoredRule {
session_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
project_id: Option<String>,
rule: RememberedRule,
}
impl PermissionRuleStore for FileRuntimeStore {
fn save_rules(
&self,
session_id: &str,
project_id: Option<&str>,
rules: &[RememberedRule],
) -> Result<(), RuntimeError> {
let _guard = lock_unpoisoned(&self.rules_lock);
let mut stored = dedup_rows(self.read_rules()?);
stored.retain(|entry| {
!(entry.session_id == session_id && entry.rule.scope == PermissionRuleScope::Session)
});
for rule in rules {
let row = StoredRule {
session_id: session_id.to_string(),
project_id: project_id.map(str::to_string),
rule: rule.clone(),
};
if !stored.contains(&row) {
stored.push(row);
}
}
self.write_rules(stored)
}
fn load_rules(
&self,
session_id: &str,
project_id: Option<&str>,
) -> Result<Vec<RememberedRule>, RuntimeError> {
let applicable = self
.read_rules()?
.into_iter()
.filter(|entry| match entry.rule.scope {
PermissionRuleScope::Session => entry.session_id == session_id,
PermissionRuleScope::Project => {
project_id.is_some() && entry.project_id.as_deref() == project_id
}
PermissionRuleScope::Global => true,
})
.map(|entry| entry.rule);
let mut rules: Vec<RememberedRule> = Vec::new();
for rule in applicable {
if !rules.contains(&rule) {
rules.push(rule);
}
}
Ok(rules)
}
fn clear_rules(&self, session_id: &str) -> Result<(), RuntimeError> {
let _guard = lock_unpoisoned(&self.rules_lock);
let mut stored = self.read_rules()?;
stored.retain(|entry| entry.session_id != session_id);
self.write_rules(stored)
}
}
impl FileRuntimeStore {
fn read_rules(&self) -> Result<Vec<StoredRule>, RuntimeError> {
let Some(contents) = fs_util::read_optional(&self.rules_path())? else {
return Ok(Vec::new());
};
let file: RulesFile = parse_versioned(&contents, "rules.json")?;
Ok(file.rules)
}
fn write_rules(&self, rules: Vec<StoredRule>) -> Result<(), RuntimeError> {
let file = RulesFile {
schema: SCHEMA_VERSION,
rules,
};
fs_util::atomic_replace(&self.rules_path(), to_pretty_json(&file)?.as_bytes())
}
}
fn dedup_rows(rows: Vec<StoredRule>) -> Vec<StoredRule> {
let mut out: Vec<StoredRule> = Vec::with_capacity(rows.len());
for row in rows {
if !out.contains(&row) {
out.push(row);
}
}
out
}