345 lines
11 KiB
Rust
345 lines
11 KiB
Rust
use std::sync::Arc;
|
|
use tokio::sync::RwLock;
|
|
use ourdb::{OurDB, OurDBSetArgs};
|
|
use tst::TST;
|
|
use crate::error::Error;
|
|
use crate::acl::{ACL, ACLRight};
|
|
|
|
/// ACLDBTopic represents a database instance for a specific topic within a circle
|
|
pub struct ACLDBTopic {
|
|
/// Circle ID
|
|
circle_id: String,
|
|
/// Topic name
|
|
topic: String,
|
|
/// OurDB instance
|
|
db: Arc<RwLock<OurDB>>,
|
|
/// TST instance for key-to-id mapping
|
|
tst: Arc<RwLock<TST>>,
|
|
}
|
|
|
|
impl ACLDBTopic {
|
|
/// Creates a new ACLDBTopic instance
|
|
pub fn new(circle_id: String, topic: String, db: Arc<RwLock<OurDB>>, tst: Arc<RwLock<TST>>) -> Self {
|
|
ACLDBTopic {
|
|
circle_id,
|
|
topic,
|
|
db,
|
|
tst,
|
|
}
|
|
}
|
|
|
|
/// Sets a value in the database with optional ACL protection
|
|
pub async fn set(&self, key: &str, value: &[u8]) -> Result<u32, Error> {
|
|
self.set_with_acl(key, value, 0).await // 0 means no ACL protection
|
|
}
|
|
|
|
/// Sets a value in the database with ACL protection
|
|
pub async fn set_with_acl(&self, key: &str, value: &[u8], acl_id: u32) -> Result<u32, Error> {
|
|
// Create the TST key
|
|
let tst_key = format!("{}{}", self.topic, key);
|
|
|
|
// Check if the key already exists in TST
|
|
let id = {
|
|
// First try to get the ID from TST
|
|
let mut id_opt = None;
|
|
{
|
|
let mut tst = self.tst.write().await;
|
|
if let Ok(id_bytes) = tst.list(&tst_key) {
|
|
if !id_bytes.is_empty() {
|
|
let id_str = String::from_utf8_lossy(&id_bytes[0].as_bytes());
|
|
if let Ok(parsed_id) = id_str.parse::<u32>() {
|
|
id_opt = Some(parsed_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not found, get a new ID
|
|
match id_opt {
|
|
Some(id) => id,
|
|
None => {
|
|
let mut db = self.db.write().await;
|
|
db.get_next_id()?
|
|
}
|
|
}
|
|
};
|
|
|
|
// Prepare the data with ACL ID prefix if needed
|
|
let data = if acl_id > 0 {
|
|
// Add ACL ID as the first 4 bytes
|
|
let mut acl_data = acl_id.to_be_bytes().to_vec();
|
|
acl_data.extend_from_slice(value);
|
|
acl_data
|
|
} else {
|
|
value.to_vec()
|
|
};
|
|
|
|
// Store the data in OurDB
|
|
{
|
|
let mut db = self.db.write().await;
|
|
db.set(OurDBSetArgs {
|
|
id: Some(id),
|
|
data: &data,
|
|
})?;
|
|
}
|
|
|
|
// Store the ID in TST
|
|
{
|
|
let mut tst = self.tst.write().await;
|
|
tst.set(&tst_key, id.to_string().into_bytes())?;
|
|
}
|
|
|
|
Ok(id)
|
|
}
|
|
|
|
/// Gets a value from the database
|
|
pub async fn get(&self, key: &str) -> Result<Vec<u8>, Error> {
|
|
// Create the TST key
|
|
let tst_key = format!("{}{}", self.topic, key);
|
|
|
|
// Get the ID from TST
|
|
let id = {
|
|
let mut tst = self.tst.write().await;
|
|
let keys = tst.list(&tst_key)?;
|
|
if keys.is_empty() {
|
|
return Err(Error::NotFound);
|
|
}
|
|
let id_str = &keys[0];
|
|
id_str.parse::<u32>().map_err(|_| Error::InvalidOperation("Invalid ID format in TST".to_string()))?
|
|
};
|
|
|
|
// Get the data from OurDB
|
|
let data = {
|
|
let mut db = self.db.write().await;
|
|
db.get(id)?
|
|
};
|
|
|
|
// Check if the data has an ACL ID prefix
|
|
if data.len() >= 4 {
|
|
let (acl_id_bytes, actual_data) = data.split_at(4);
|
|
let acl_id = u32::from_be_bytes([acl_id_bytes[0], acl_id_bytes[1], acl_id_bytes[2], acl_id_bytes[3]]);
|
|
|
|
if acl_id > 0 {
|
|
// This record is ACL-protected, but we're not checking permissions here
|
|
// The permission check should be done at a higher level
|
|
return Ok(actual_data.to_vec());
|
|
}
|
|
}
|
|
|
|
Ok(data)
|
|
}
|
|
|
|
/// Gets a value from the database with permission check
|
|
pub async fn get_with_permission(&self, key: &str, caller_pubkey: &str, parent_acls: &[ACL]) -> Result<Vec<u8>, Error> {
|
|
// Create the TST key
|
|
let tst_key = format!("{}{}", self.topic, key);
|
|
|
|
// Get the ID from TST
|
|
let id = {
|
|
let mut tst = self.tst.write().await;
|
|
let keys = tst.list(&tst_key)?;
|
|
if keys.is_empty() {
|
|
return Err(Error::NotFound);
|
|
}
|
|
let id_str = &keys[0];
|
|
id_str.parse::<u32>().map_err(|_| Error::InvalidOperation("Invalid ID format in TST".to_string()))?
|
|
};
|
|
|
|
// Get the data from OurDB
|
|
let data = {
|
|
let mut db = self.db.write().await;
|
|
db.get(id)?
|
|
};
|
|
|
|
// Check if the data has an ACL ID prefix
|
|
if data.len() >= 4 {
|
|
let (acl_id_bytes, actual_data) = data.split_at(4);
|
|
let acl_id = u32::from_be_bytes([acl_id_bytes[0], acl_id_bytes[1], acl_id_bytes[2], acl_id_bytes[3]]);
|
|
|
|
if acl_id > 0 {
|
|
// This record is ACL-protected, check permissions
|
|
let acl_name = format!("acl_{}", acl_id);
|
|
|
|
// Find the ACL in the parent ACLs
|
|
let has_permission = parent_acls.iter()
|
|
.find(|acl| acl.name == acl_name)
|
|
.map(|acl| acl.has_permission(caller_pubkey, ACLRight::Read))
|
|
.unwrap_or(false);
|
|
|
|
if !has_permission {
|
|
return Err(Error::PermissionDenied);
|
|
}
|
|
|
|
return Ok(actual_data.to_vec());
|
|
}
|
|
}
|
|
|
|
Ok(data)
|
|
}
|
|
|
|
/// Gets a value by ID from the database
|
|
pub async fn get_by_id(&self, id: u32) -> Result<Vec<u8>, Error> {
|
|
// Get the data from OurDB
|
|
let data = {
|
|
let mut db = self.db.write().await;
|
|
db.get(id)?
|
|
};
|
|
|
|
// Check if the data has an ACL ID prefix
|
|
if data.len() >= 4 {
|
|
let (_, actual_data) = data.split_at(4);
|
|
return Ok(actual_data.to_vec());
|
|
}
|
|
|
|
Ok(data)
|
|
}
|
|
|
|
/// Gets a value by ID from the database with permission check
|
|
pub async fn get_by_id_with_permission(&self, id: u32, caller_pubkey: &str, parent_acls: &[ACL]) -> Result<Vec<u8>, Error> {
|
|
// Get the data from OurDB
|
|
let data = {
|
|
let mut db = self.db.write().await;
|
|
db.get(id)?
|
|
};
|
|
|
|
// Check if the data has an ACL ID prefix
|
|
if data.len() >= 4 {
|
|
let (acl_id_bytes, actual_data) = data.split_at(4);
|
|
let acl_id = u32::from_be_bytes([acl_id_bytes[0], acl_id_bytes[1], acl_id_bytes[2], acl_id_bytes[3]]);
|
|
|
|
if acl_id > 0 {
|
|
// This record is ACL-protected, check permissions
|
|
let acl_name = format!("acl_{}", acl_id);
|
|
|
|
// Find the ACL in the parent ACLs
|
|
let has_permission = parent_acls.iter()
|
|
.find(|acl| acl.name == acl_name)
|
|
.map(|acl| acl.has_permission(caller_pubkey, ACLRight::Read))
|
|
.unwrap_or(false);
|
|
|
|
if !has_permission {
|
|
return Err(Error::PermissionDenied);
|
|
}
|
|
|
|
return Ok(actual_data.to_vec());
|
|
}
|
|
}
|
|
|
|
Ok(data)
|
|
}
|
|
|
|
/// Deletes a value from the database
|
|
pub async fn delete(&self, key: &str) -> Result<(), Error> {
|
|
// Create the TST key
|
|
let tst_key = format!("{}{}", self.topic, key);
|
|
|
|
// Get the ID from TST
|
|
let id = {
|
|
let mut tst = self.tst.write().await;
|
|
let keys = tst.list(&tst_key)?;
|
|
if keys.is_empty() {
|
|
return Err(Error::NotFound);
|
|
}
|
|
let id_str = &keys[0];
|
|
id_str.parse::<u32>().map_err(|_| Error::InvalidOperation("Invalid ID format in TST".to_string()))?
|
|
};
|
|
|
|
// Delete from OurDB
|
|
{
|
|
let mut db = self.db.write().await;
|
|
db.delete(id)?;
|
|
}
|
|
|
|
// Delete from TST
|
|
{
|
|
let mut tst = self.tst.write().await;
|
|
tst.delete(&tst_key)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Deletes a value from the database with permission check
|
|
pub async fn delete_with_permission(&self, key: &str, caller_pubkey: &str, parent_acls: &[ACL]) -> Result<(), Error> {
|
|
// Create the TST key
|
|
let tst_key = format!("{}{}", self.topic, key);
|
|
|
|
// Get the ID from TST
|
|
let id = {
|
|
let mut tst = self.tst.write().await;
|
|
let keys = tst.list(&tst_key)?;
|
|
if keys.is_empty() {
|
|
return Err(Error::NotFound);
|
|
}
|
|
let id_str = &keys[0];
|
|
id_str.parse::<u32>().map_err(|_| Error::InvalidOperation("Invalid ID format in TST".to_string()))?
|
|
};
|
|
|
|
// Get the data to check ACL
|
|
let data = {
|
|
let mut db = self.db.write().await;
|
|
db.get(id)?
|
|
};
|
|
|
|
// Check if the data has an ACL ID prefix
|
|
if data.len() >= 4 {
|
|
let (acl_id_bytes, _) = data.split_at(4);
|
|
let acl_id = u32::from_be_bytes([acl_id_bytes[0], acl_id_bytes[1], acl_id_bytes[2], acl_id_bytes[3]]);
|
|
|
|
if acl_id > 0 {
|
|
// This record is ACL-protected, check permissions
|
|
let acl_name = format!("acl_{}", acl_id);
|
|
|
|
// Find the ACL in the parent ACLs
|
|
let has_permission = parent_acls.iter()
|
|
.find(|acl| acl.name == acl_name)
|
|
.map(|acl| acl.has_permission(caller_pubkey, ACLRight::Delete))
|
|
.unwrap_or(false);
|
|
|
|
if !has_permission {
|
|
return Err(Error::PermissionDenied);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete from OurDB
|
|
{
|
|
let mut db = self.db.write().await;
|
|
db.delete(id)?
|
|
};
|
|
|
|
// Delete from TST
|
|
{
|
|
let mut tst = self.tst.write().await;
|
|
tst.delete(&tst_key)?
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Gets all keys with a given prefix
|
|
pub async fn prefix(&self, prefix: &str) -> Result<Vec<String>, Error> {
|
|
// Create the TST prefix
|
|
let tst_prefix = format!("{}{}", self.topic, prefix);
|
|
|
|
// Get all keys with the prefix
|
|
let keys = {
|
|
let mut tst = self.tst.write().await;
|
|
tst.list(&tst_prefix)?
|
|
};
|
|
|
|
// Remove the topic prefix from the keys
|
|
let topic_prefix = format!("{}", self.topic);
|
|
let keys = keys.into_iter()
|
|
.map(|key| key.strip_prefix(&topic_prefix).unwrap_or(&key).to_string())
|
|
.collect();
|
|
|
|
Ok(keys)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
// Tests will be added here
|
|
}
|