feat: deprecate the use of | to separate auth rules (#298)

This commit is contained in:
sigoden 2023-11-26 22:15:49 +08:00 committed by GitHub
parent 653cd167d0
commit 7584fe3d08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 86 additions and 18 deletions

View file

@ -47,38 +47,42 @@ impl AccessControl {
if raw_rules.is_empty() {
return Ok(Default::default());
}
let new_raw_rules = compact_split_rules(raw_rules);
if new_raw_rules.len() != raw_rules.len() {
eprintln!("Warning: deprecate the use of `|` to separate auth rules.")
}
let mut use_hashed_password = false;
let create_err = |v: &str| anyhow!("Invalid auth `{v}`");
let mut anony = None;
let mut anony_paths = vec![];
let mut users = IndexMap::new();
for rule in raw_rules {
let (user, list) = split_rule(rule).ok_or_else(|| create_err(rule))?;
if user.is_empty() && anony.is_some() {
for rule in &new_raw_rules {
let (account, paths) = split_account_paths(rule).ok_or_else(|| create_err(rule))?;
if account.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),
let mut access_paths = AccessPaths::default();
for item in paths.trim_matches(',').split(',') {
let (path, perm) = match item.split_once(':') {
None => (item, AccessPerm::ReadOnly),
Some((path, "rw")) => (path, AccessPerm::ReadWrite),
_ => return Err(create_err(rule)),
};
if user.is_empty() {
if account.is_empty() {
anony_paths.push((path, perm));
}
paths.add(path, perm);
access_paths.add(path, perm);
}
if user.is_empty() {
anony = Some(paths);
} else if let Some((user, pass)) = user.split_once(':') {
if account.is_empty() {
anony = Some(access_paths);
} else if let Some((user, pass)) = account.split_once(':') {
if user.is_empty() || pass.is_empty() {
return Err(create_err(rule));
}
if pass.starts_with("$6$") {
use_hashed_password = true;
}
users.insert(user.to_string(), (pass.to_string(), paths));
users.insert(user.to_string(), (pass.to_string(), access_paths));
} else {
return Err(create_err(rule));
}
@ -476,25 +480,75 @@ fn create_nonce() -> Result<String> {
Ok(n[..34].to_string())
}
fn split_rule(s: &str) -> Option<(&str, &str)> {
fn split_account_paths(s: &str) -> Option<(&str, &str)> {
let i = s.find("@/")?;
Some((&s[0..i], &s[i + 1..]))
}
/// Compatible with deprecated usage of `|` for role separation
fn compact_split_rules(rules: &[&str]) -> Vec<String> {
let mut output = vec![];
for rule in rules {
let parts: Vec<&str> = rule.split('|').collect();
let mut rules_list = vec![];
let mut concated_part = String::new();
for (i, part) in parts.iter().enumerate() {
if part.contains("@/") {
concated_part.push_str(part);
let mut concated_part_tmp = String::new();
std::mem::swap(&mut concated_part_tmp, &mut concated_part);
rules_list.push(concated_part_tmp);
continue;
}
concated_part.push_str(part);
if i < parts.len() - 1 {
concated_part.push('|');
}
}
if !concated_part.is_empty() {
rules_list.push(concated_part)
}
output.extend(rules_list);
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_split_rule() {
assert_eq!(split_rule("user:pass@/:rw"), Some(("user:pass", "/:rw")));
assert_eq!(split_rule("user:pass@@/:rw"), Some(("user:pass@", "/:rw")));
fn test_split_account_paths() {
assert_eq!(
split_rule("user:pass@1@/:rw"),
split_account_paths("user:pass@/:rw"),
Some(("user:pass", "/:rw"))
);
assert_eq!(
split_account_paths("user:pass@@/:rw"),
Some(("user:pass@", "/:rw"))
);
assert_eq!(
split_account_paths("user:pass@1@/:rw"),
Some(("user:pass@1", "/:rw"))
);
}
#[test]
fn test_compact_split_rules() {
assert_eq!(
compact_split_rules(&["user1:pass1@/:rw|user2:pass2@/:rw"]),
["user1:pass1@/:rw", "user2:pass2@/:rw"]
);
assert_eq!(
compact_split_rules(&["user1:pa|ss1@/:rw|user2:pa|ss2@/:rw"]),
["user1:pa|ss1@/:rw", "user2:pa|ss2@/:rw"]
);
assert_eq!(
compact_split_rules(&["user1:pa|ss1@/:rw|@/"]),
["user1:pa|ss1@/:rw", "@/"]
);
}
#[test]
fn test_access_paths() {
let mut paths = AccessPaths::default();

View file

@ -105,6 +105,20 @@ fn auth_check(
Ok(())
}
#[rstest]
fn auth_compact_rules(
#[with(&["--auth", "user:pass@/:rw|user2:pass2@/", "-A"])] server: TestServer,
) -> Result<(), Error> {
let url = format!("{}index.html", server.url());
let resp = fetch!(b"WRITEABLE", &url).send()?;
assert_eq!(resp.status(), 401);
let resp = fetch!(b"WRITEABLE", &url).send_with_digest_auth("user2", "pass2")?;
assert_eq!(resp.status(), 403);
let resp = fetch!(b"WRITEABLE", &url).send_with_digest_auth("user", "pass")?;
assert_eq!(resp.status(), 200);
Ok(())
}
#[rstest]
fn auth_readonly(
#[with(&["--auth", "user:pass@/:rw", "--auth", "user2:pass2@/", "-A"])] server: TestServer,