2023-02-21 12:23:24 +03:00
|
|
|
use anyhow::{anyhow, bail, Result};
|
2023-02-21 11:39:57 +03:00
|
|
|
use base64::{engine::general_purpose, Engine as _};
|
2022-06-04 19:09:21 +03:00
|
|
|
use headers::HeaderValue;
|
2022-06-19 06:26:03 +03:00
|
|
|
use hyper::Method;
|
2023-06-01 13:52:05 +03:00
|
|
|
use indexmap::IndexMap;
|
2022-06-04 19:09:21 +03:00
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use md5::Context;
|
2023-06-01 13:52:05 +03:00
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
};
|
2022-06-04 19:09:21 +03:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
2023-06-01 13:52:05 +03:00
|
|
|
use crate::utils::unix_now;
|
2022-06-04 19:09:21 +03:00
|
|
|
|
2022-06-19 17:53:51 +03:00
|
|
|
const REALM: &str = "DUFS";
|
2023-11-03 15:36:23 +03:00
|
|
|
const DIGEST_AUTH_TIMEOUT: u32 = 604800; // 7 days
|
2022-06-04 19:09:21 +03:00
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
static ref NONCESTARTHASH: Context = {
|
|
|
|
let mut h = Context::new();
|
|
|
|
h.consume(Uuid::new_v4().as_bytes());
|
|
|
|
h.consume(std::process::id().to_be_bytes());
|
|
|
|
h
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-06-01 13:52:05 +03:00
|
|
|
#[derive(Debug, Default)]
|
2022-06-19 06:26:03 +03:00
|
|
|
pub struct AccessControl {
|
2023-06-01 13:52:05 +03:00
|
|
|
users: IndexMap<String, (String, AccessPaths)>,
|
|
|
|
anony: Option<AccessPaths>,
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl AccessControl {
|
2023-06-01 13:52:05 +03:00
|
|
|
pub fn new(raw_rules: &[&str]) -> Result<Self> {
|
2022-06-19 06:26:03 +03:00
|
|
|
if raw_rules.is_empty() {
|
2023-06-01 13:52:05 +03:00
|
|
|
return Ok(AccessControl {
|
|
|
|
anony: Some(AccessPaths::new(AccessPerm::ReadWrite)),
|
|
|
|
users: IndexMap::new(),
|
|
|
|
});
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
2023-06-01 13:52:05 +03:00
|
|
|
|
|
|
|
let create_err = |v: &str| anyhow!("Invalid auth `{v}`");
|
|
|
|
let mut anony = None;
|
|
|
|
let mut anony_paths = vec![];
|
|
|
|
let mut users = IndexMap::new();
|
2022-06-19 06:26:03 +03:00
|
|
|
for rule in raw_rules {
|
2023-06-01 13:52:05 +03:00
|
|
|
let (user, list) = rule.split_once('@').ok_or_else(|| create_err(rule))?;
|
|
|
|
if user.is_empty() && anony.is_some() {
|
|
|
|
bail!("Invalid auth, duplicate anonymous rules");
|
|
|
|
}
|
|
|
|
let mut paths = AccessPaths::default();
|
|
|
|
for value in list.trim_matches(',').split(',') {
|
|
|
|
let (path, perm) = match value.split_once(':') {
|
|
|
|
None => (value, AccessPerm::ReadOnly),
|
|
|
|
Some((path, "rw")) => (path, AccessPerm::ReadWrite),
|
|
|
|
_ => return Err(create_err(rule)),
|
|
|
|
};
|
|
|
|
if user.is_empty() {
|
|
|
|
anony_paths.push((path, perm));
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
2023-06-01 13:52:05 +03:00
|
|
|
paths.add(path, perm);
|
|
|
|
}
|
|
|
|
if user.is_empty() {
|
|
|
|
anony = Some(paths);
|
|
|
|
} else if let Some((user, pass)) = user.split_once(':') {
|
|
|
|
if user.is_empty() || pass.is_empty() {
|
|
|
|
return Err(create_err(rule));
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
2023-06-01 13:52:05 +03:00
|
|
|
users.insert(user.to_string(), (pass.to_string(), paths));
|
|
|
|
} else {
|
|
|
|
return Err(create_err(rule));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (path, perm) in anony_paths {
|
|
|
|
for (_, (_, paths)) in users.iter_mut() {
|
|
|
|
paths.add(path, perm)
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
|
|
|
}
|
2023-06-01 13:52:05 +03:00
|
|
|
Ok(Self { users, anony })
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
|
|
|
|
2023-06-05 06:40:31 +03:00
|
|
|
pub fn exist(&self) -> bool {
|
|
|
|
!self.users.is_empty()
|
2023-02-21 07:42:40 +03:00
|
|
|
}
|
|
|
|
|
2022-06-19 06:26:03 +03:00
|
|
|
pub fn guard(
|
|
|
|
&self,
|
|
|
|
path: &str,
|
|
|
|
method: &Method,
|
|
|
|
authorization: Option<&HeaderValue>,
|
2023-06-01 13:52:05 +03:00
|
|
|
) -> (Option<String>, Option<AccessPaths>) {
|
|
|
|
if let Some(authorization) = authorization {
|
2023-11-03 15:36:23 +03:00
|
|
|
if let Some(user) = get_auth_user(authorization) {
|
2023-06-01 13:52:05 +03:00
|
|
|
if let Some((pass, paths)) = self.users.get(&user) {
|
|
|
|
if method == Method::OPTIONS {
|
|
|
|
return (Some(user), Some(AccessPaths::new(AccessPerm::ReadOnly)));
|
|
|
|
}
|
2023-11-03 15:36:23 +03:00
|
|
|
if check_auth(authorization, method.as_str(), &user, pass).is_some() {
|
2023-06-01 13:52:05 +03:00
|
|
|
return (Some(user), paths.find(path, !is_readonly_method(method)));
|
|
|
|
} else {
|
|
|
|
return (None, None);
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-06-01 13:52:05 +03:00
|
|
|
|
|
|
|
if method == Method::OPTIONS {
|
|
|
|
return (None, Some(AccessPaths::new(AccessPerm::ReadOnly)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(paths) = self.anony.as_ref() {
|
|
|
|
return (None, paths.find(path, !is_readonly_method(method)));
|
|
|
|
}
|
|
|
|
|
|
|
|
(None, None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
|
|
pub struct AccessPaths {
|
|
|
|
perm: AccessPerm,
|
|
|
|
children: IndexMap<String, AccessPaths>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AccessPaths {
|
|
|
|
pub fn new(perm: AccessPerm) -> Self {
|
|
|
|
Self {
|
|
|
|
perm,
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn perm(&self) -> AccessPerm {
|
|
|
|
self.perm
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_perm(&mut self, perm: AccessPerm) {
|
|
|
|
if self.perm < perm {
|
|
|
|
self.perm = perm
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add(&mut self, path: &str, perm: AccessPerm) {
|
|
|
|
let path = path.trim_matches('/');
|
|
|
|
if path.is_empty() {
|
|
|
|
self.set_perm(perm);
|
|
|
|
} else {
|
|
|
|
let parts: Vec<&str> = path.split('/').collect();
|
|
|
|
self.add_impl(&parts, perm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_impl(&mut self, parts: &[&str], perm: AccessPerm) {
|
|
|
|
let parts_len = parts.len();
|
|
|
|
if parts_len == 0 {
|
|
|
|
self.set_perm(perm);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let child = self.children.entry(parts[0].to_string()).or_default();
|
|
|
|
child.add_impl(&parts[1..], perm)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn find(&self, path: &str, writable: bool) -> Option<AccessPaths> {
|
|
|
|
let parts: Vec<&str> = path
|
|
|
|
.trim_matches('/')
|
|
|
|
.split('/')
|
|
|
|
.filter(|v| !v.is_empty())
|
|
|
|
.collect();
|
|
|
|
let target = self.find_impl(&parts, self.perm)?;
|
|
|
|
if writable && !target.perm().readwrite() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some(target)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_impl(&self, parts: &[&str], perm: AccessPerm) -> Option<AccessPaths> {
|
|
|
|
let perm = self.perm.max(perm);
|
|
|
|
if parts.is_empty() {
|
|
|
|
if perm.indexonly() {
|
|
|
|
return Some(self.clone());
|
|
|
|
} else {
|
|
|
|
return Some(AccessPaths::new(perm));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let child = match self.children.get(parts[0]) {
|
|
|
|
Some(v) => v,
|
|
|
|
None => {
|
|
|
|
if perm.indexonly() {
|
|
|
|
return None;
|
|
|
|
} else {
|
|
|
|
return Some(AccessPaths::new(perm));
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
|
|
|
}
|
2023-06-01 13:52:05 +03:00
|
|
|
};
|
|
|
|
child.find_impl(&parts[1..], perm)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn child_paths(&self) -> Vec<&String> {
|
|
|
|
self.children.keys().collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn leaf_paths(&self, base: &Path) -> Vec<PathBuf> {
|
|
|
|
if !self.perm().indexonly() {
|
|
|
|
return vec![base.to_path_buf()];
|
|
|
|
}
|
|
|
|
let mut output = vec![];
|
|
|
|
self.leaf_paths_impl(&mut output, base);
|
|
|
|
output
|
|
|
|
}
|
|
|
|
|
|
|
|
fn leaf_paths_impl(&self, output: &mut Vec<PathBuf>, base: &Path) {
|
|
|
|
for (name, child) in self.children.iter() {
|
|
|
|
let base = base.join(name);
|
|
|
|
if child.perm().indexonly() {
|
|
|
|
child.leaf_paths_impl(output, &base);
|
|
|
|
} else {
|
|
|
|
output.push(base)
|
|
|
|
}
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-01 13:52:05 +03:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
|
|
|
|
pub enum AccessPerm {
|
|
|
|
#[default]
|
|
|
|
IndexOnly,
|
2022-06-19 06:26:03 +03:00
|
|
|
ReadOnly,
|
2023-06-02 13:38:59 +03:00
|
|
|
ReadWrite,
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
|
|
|
|
2023-06-01 13:52:05 +03:00
|
|
|
impl AccessPerm {
|
|
|
|
pub fn readwrite(&self) -> bool {
|
|
|
|
self == &AccessPerm::ReadWrite
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
|
|
|
|
2023-06-01 13:52:05 +03:00
|
|
|
pub fn indexonly(&self) -> bool {
|
|
|
|
self == &AccessPerm::IndexOnly
|
|
|
|
}
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
|
|
|
|
2023-11-03 15:36:23 +03:00
|
|
|
pub fn www_authenticate() -> Result<HeaderValue> {
|
|
|
|
let nonce = create_nonce()?;
|
|
|
|
let value = format!(
|
|
|
|
"Digest realm=\"{}\", nonce=\"{}\", qop=\"auth\", Basic realm=\"{}\"",
|
|
|
|
REALM, nonce, REALM
|
|
|
|
);
|
|
|
|
Ok(HeaderValue::from_str(&value)?)
|
2022-06-19 06:26:03 +03:00
|
|
|
}
|
|
|
|
|
2023-11-03 15:36:23 +03:00
|
|
|
pub fn get_auth_user(authorization: &HeaderValue) -> Option<String> {
|
|
|
|
if let Some(value) = strip_prefix(authorization.as_bytes(), b"Basic ") {
|
|
|
|
let value: Vec<u8> = general_purpose::STANDARD.decode(value).ok()?;
|
|
|
|
let parts: Vec<&str> = std::str::from_utf8(&value).ok()?.split(':').collect();
|
|
|
|
Some(parts[0].to_string())
|
|
|
|
} else if let Some(value) = strip_prefix(authorization.as_bytes(), b"Digest ") {
|
|
|
|
let digest_map = to_headermap(value).ok()?;
|
|
|
|
let username = digest_map.get(b"username".as_ref())?;
|
|
|
|
std::str::from_utf8(username).map(|v| v.to_string()).ok()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2022-06-04 19:09:21 +03:00
|
|
|
}
|
|
|
|
|
2023-11-03 15:36:23 +03:00
|
|
|
pub fn check_auth(
|
|
|
|
authorization: &HeaderValue,
|
|
|
|
method: &str,
|
|
|
|
auth_user: &str,
|
|
|
|
auth_pass: &str,
|
|
|
|
) -> Option<()> {
|
|
|
|
if let Some(value) = strip_prefix(authorization.as_bytes(), b"Basic ") {
|
|
|
|
let basic_value: Vec<u8> = general_purpose::STANDARD.decode(value).ok()?;
|
|
|
|
let parts: Vec<&str> = std::str::from_utf8(&basic_value).ok()?.split(':').collect();
|
|
|
|
|
|
|
|
if parts[0] != auth_user {
|
|
|
|
return None;
|
2022-06-04 19:09:21 +03:00
|
|
|
}
|
2023-06-01 13:52:05 +03:00
|
|
|
|
2023-11-03 15:36:23 +03:00
|
|
|
if parts[1] == auth_pass {
|
|
|
|
return Some(());
|
2022-07-31 03:27:09 +03:00
|
|
|
}
|
2022-06-20 06:25:09 +03:00
|
|
|
|
2023-11-03 15:36:23 +03:00
|
|
|
None
|
|
|
|
} else if let Some(value) = strip_prefix(authorization.as_bytes(), b"Digest ") {
|
|
|
|
let digest_map = to_headermap(value).ok()?;
|
|
|
|
if let (Some(username), Some(nonce), Some(user_response)) = (
|
|
|
|
digest_map
|
|
|
|
.get(b"username".as_ref())
|
|
|
|
.and_then(|b| std::str::from_utf8(b).ok()),
|
|
|
|
digest_map.get(b"nonce".as_ref()),
|
|
|
|
digest_map.get(b"response".as_ref()),
|
|
|
|
) {
|
|
|
|
match validate_nonce(nonce) {
|
|
|
|
Ok(true) => {}
|
|
|
|
_ => return None,
|
|
|
|
}
|
|
|
|
if auth_user != username {
|
|
|
|
return None;
|
2022-06-20 06:25:09 +03:00
|
|
|
}
|
2023-06-01 13:52:05 +03:00
|
|
|
|
2023-11-03 15:36:23 +03:00
|
|
|
let mut h = Context::new();
|
|
|
|
h.consume(format!("{}:{}:{}", auth_user, REALM, auth_pass).as_bytes());
|
|
|
|
let auth_pass = format!("{:x}", h.compute());
|
2023-06-01 13:52:05 +03:00
|
|
|
|
2023-11-03 15:36:23 +03:00
|
|
|
let mut ha = Context::new();
|
|
|
|
ha.consume(method);
|
|
|
|
ha.consume(b":");
|
|
|
|
if let Some(uri) = digest_map.get(b"uri".as_ref()) {
|
|
|
|
ha.consume(uri);
|
|
|
|
}
|
|
|
|
let ha = format!("{:x}", ha.compute());
|
|
|
|
let mut correct_response = None;
|
|
|
|
if let Some(qop) = digest_map.get(b"qop".as_ref()) {
|
|
|
|
if qop == &b"auth".as_ref() || qop == &b"auth-int".as_ref() {
|
|
|
|
correct_response = Some({
|
|
|
|
let mut c = Context::new();
|
|
|
|
c.consume(&auth_pass);
|
|
|
|
c.consume(b":");
|
|
|
|
c.consume(nonce);
|
|
|
|
c.consume(b":");
|
|
|
|
if let Some(nc) = digest_map.get(b"nc".as_ref()) {
|
|
|
|
c.consume(nc);
|
2022-06-20 06:25:09 +03:00
|
|
|
}
|
2023-11-03 15:36:23 +03:00
|
|
|
c.consume(b":");
|
|
|
|
if let Some(cnonce) = digest_map.get(b"cnonce".as_ref()) {
|
|
|
|
c.consume(cnonce);
|
2022-06-20 06:25:09 +03:00
|
|
|
}
|
2023-11-03 15:36:23 +03:00
|
|
|
c.consume(b":");
|
|
|
|
c.consume(qop);
|
|
|
|
c.consume(b":");
|
|
|
|
c.consume(&*ha);
|
|
|
|
format!("{:x}", c.compute())
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let correct_response = match correct_response {
|
|
|
|
Some(r) => r,
|
|
|
|
None => {
|
|
|
|
let mut c = Context::new();
|
|
|
|
c.consume(&auth_pass);
|
|
|
|
c.consume(b":");
|
|
|
|
c.consume(nonce);
|
|
|
|
c.consume(b":");
|
|
|
|
c.consume(&*ha);
|
|
|
|
format!("{:x}", c.compute())
|
2022-06-20 06:25:09 +03:00
|
|
|
}
|
2023-11-03 15:36:23 +03:00
|
|
|
};
|
|
|
|
if correct_response.as_bytes() == *user_response {
|
|
|
|
return Some(());
|
2022-06-04 19:09:21 +03:00
|
|
|
}
|
|
|
|
}
|
2023-11-03 15:36:23 +03:00
|
|
|
None
|
|
|
|
} else {
|
|
|
|
None
|
2022-06-04 19:09:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Check if a nonce is still valid.
|
|
|
|
/// Return an error if it was never valid
|
2023-02-21 12:23:24 +03:00
|
|
|
fn validate_nonce(nonce: &[u8]) -> Result<bool> {
|
2022-06-04 19:09:21 +03:00
|
|
|
if nonce.len() != 34 {
|
2023-02-21 12:23:24 +03:00
|
|
|
bail!("invalid nonce");
|
2022-06-04 19:09:21 +03:00
|
|
|
}
|
|
|
|
//parse hex
|
|
|
|
if let Ok(n) = std::str::from_utf8(nonce) {
|
|
|
|
//get time
|
|
|
|
if let Ok(secs_nonce) = u32::from_str_radix(&n[..8], 16) {
|
|
|
|
//check time
|
2023-02-21 12:23:24 +03:00
|
|
|
let now = unix_now()?;
|
2022-06-04 19:09:21 +03:00
|
|
|
let secs_now = now.as_secs() as u32;
|
|
|
|
|
|
|
|
if let Some(dur) = secs_now.checked_sub(secs_nonce) {
|
|
|
|
//check hash
|
|
|
|
let mut h = NONCESTARTHASH.clone();
|
|
|
|
h.consume(secs_nonce.to_be_bytes());
|
|
|
|
let h = format!("{:x}", h.compute());
|
|
|
|
if h[..26] == n[8..34] {
|
2022-07-21 06:47:47 +03:00
|
|
|
return Ok(dur < DIGEST_AUTH_TIMEOUT);
|
2022-06-04 19:09:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-21 12:23:24 +03:00
|
|
|
bail!("invalid nonce");
|
2022-06-04 19:09:21 +03:00
|
|
|
}
|
|
|
|
|
2023-11-03 15:36:23 +03:00
|
|
|
fn is_readonly_method(method: &Method) -> bool {
|
|
|
|
method == Method::GET
|
|
|
|
|| method == Method::OPTIONS
|
|
|
|
|| method == Method::HEAD
|
|
|
|
|| method.as_str() == "PROPFIND"
|
|
|
|
}
|
|
|
|
|
2022-06-04 19:09:21 +03:00
|
|
|
fn strip_prefix<'a>(search: &'a [u8], prefix: &[u8]) -> Option<&'a [u8]> {
|
|
|
|
let l = prefix.len();
|
|
|
|
if search.len() < l {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
if &search[..l] == prefix {
|
|
|
|
Some(&search[l..])
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_headermap(header: &[u8]) -> Result<HashMap<&[u8], &[u8]>, ()> {
|
|
|
|
let mut sep = Vec::new();
|
2022-08-03 03:51:12 +03:00
|
|
|
let mut assign = Vec::new();
|
2022-06-04 19:09:21 +03:00
|
|
|
let mut i: usize = 0;
|
|
|
|
let mut esc = false;
|
|
|
|
for c in header {
|
|
|
|
match (c, esc) {
|
2022-08-03 03:51:12 +03:00
|
|
|
(b'=', false) => assign.push(i),
|
2022-06-04 19:09:21 +03:00
|
|
|
(b',', false) => sep.push(i),
|
|
|
|
(b'"', false) => esc = true,
|
|
|
|
(b'"', true) => esc = false,
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
i += 1;
|
|
|
|
}
|
2022-12-11 10:18:44 +03:00
|
|
|
sep.push(i);
|
2022-06-04 19:09:21 +03:00
|
|
|
|
|
|
|
i = 0;
|
|
|
|
let mut ret = HashMap::new();
|
2022-08-03 03:51:12 +03:00
|
|
|
for (&k, &a) in sep.iter().zip(assign.iter()) {
|
2022-06-04 19:09:21 +03:00
|
|
|
while header[i] == b' ' {
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
if a <= i || k <= 1 + a {
|
2022-12-11 10:18:44 +03:00
|
|
|
//keys and values must contain one char
|
2022-06-04 19:09:21 +03:00
|
|
|
return Err(());
|
|
|
|
}
|
|
|
|
let key = &header[i..a];
|
|
|
|
let val = if header[1 + a] == b'"' && header[k - 1] == b'"' {
|
|
|
|
//escaped
|
|
|
|
&header[2 + a..k - 1]
|
|
|
|
} else {
|
|
|
|
//not escaped
|
|
|
|
&header[1 + a..k]
|
|
|
|
};
|
|
|
|
i = 1 + k;
|
|
|
|
ret.insert(key, val);
|
|
|
|
}
|
|
|
|
Ok(ret)
|
|
|
|
}
|
|
|
|
|
2023-02-21 12:23:24 +03:00
|
|
|
fn create_nonce() -> Result<String> {
|
|
|
|
let now = unix_now()?;
|
2022-06-04 19:09:21 +03:00
|
|
|
let secs = now.as_secs() as u32;
|
|
|
|
let mut h = NONCESTARTHASH.clone();
|
|
|
|
h.consume(secs.to_be_bytes());
|
|
|
|
|
|
|
|
let n = format!("{:08x}{:032x}", secs, h.compute());
|
2023-02-21 12:23:24 +03:00
|
|
|
Ok(n[..34].to_string())
|
2022-06-04 19:09:21 +03:00
|
|
|
}
|
2023-06-01 13:52:05 +03:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_access_paths() {
|
|
|
|
let mut paths = AccessPaths::default();
|
|
|
|
paths.add("/dir1", AccessPerm::ReadWrite);
|
|
|
|
paths.add("/dir2/dir1", AccessPerm::ReadWrite);
|
|
|
|
paths.add("/dir2/dir2", AccessPerm::ReadOnly);
|
|
|
|
paths.add("/dir2/dir3/dir1", AccessPerm::ReadWrite);
|
|
|
|
assert_eq!(
|
|
|
|
paths.leaf_paths(Path::new("/tmp")),
|
|
|
|
[
|
|
|
|
"/tmp/dir1",
|
|
|
|
"/tmp/dir2/dir1",
|
|
|
|
"/tmp/dir2/dir2",
|
|
|
|
"/tmp/dir2/dir3/dir1"
|
|
|
|
]
|
|
|
|
.iter()
|
|
|
|
.map(PathBuf::from)
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
paths
|
|
|
|
.find("dir2", false)
|
|
|
|
.map(|v| v.leaf_paths(Path::new("/tmp/dir2"))),
|
|
|
|
Some(
|
|
|
|
["/tmp/dir2/dir1", "/tmp/dir2/dir2", "/tmp/dir2/dir3/dir1"]
|
|
|
|
.iter()
|
|
|
|
.map(PathBuf::from)
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
)
|
|
|
|
);
|
|
|
|
assert_eq!(paths.find("dir2", true), None);
|
|
|
|
assert!(paths.find("dir1/file", true).is_some());
|
|
|
|
}
|
2023-06-02 13:38:59 +03:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_access_paths_perm() {
|
|
|
|
let mut paths = AccessPaths::default();
|
|
|
|
assert_eq!(paths.perm(), AccessPerm::IndexOnly);
|
|
|
|
paths.set_perm(AccessPerm::ReadOnly);
|
|
|
|
assert_eq!(paths.perm(), AccessPerm::ReadOnly);
|
|
|
|
paths.set_perm(AccessPerm::ReadWrite);
|
|
|
|
assert_eq!(paths.perm(), AccessPerm::ReadWrite);
|
|
|
|
paths.set_perm(AccessPerm::ReadOnly);
|
|
|
|
assert_eq!(paths.perm(), AccessPerm::ReadWrite);
|
|
|
|
}
|
2023-06-01 13:52:05 +03:00
|
|
|
}
|