diff --git a/README.md b/README.md index bdf1d05..18f61d8 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Options: ## Examples -Serve current working directory in readonly mode +Serve current working directory in read-only mode ``` dufs @@ -206,46 +206,20 @@ curl http://192.168.8.10:5000/file --user user:pass --digest # digest aut Dufs supports account based access control. You can control who can do what on which path with `--auth`/`-a`. ``` -dufs -a user:pass@path1:rw,path2|user2:pass2@path1 -dufs -a user:pass@path1:rw,path2 -a user2:pass2@path1 +dufs -a user:pass@/path1:rw,/path2 -a user2:pass2@/path3 -a @/path4 ``` -1. Multiple rules are separated by "|" -2. User and pass are the account name and password, if omitted, it is an anonymous user -3. One rule can set multiple paths, separated by "," -4. Add `:rw` after the path to indicate that the path has read and write permissions, otherwise the path has readonly permissions. +1. Use `@` to separate the account and paths. No account means anonymous user. +2. Use `:` to separate the username and password of the account. +3. Use `,` to separate paths. +4. Use `:rw` suffix to indicate that the account has read-write permission on the path. -``` -dufs -A -a admin:admin@/:rw -``` -`admin` has all permissions for all paths. +- `-a admin:amdin@/:rw`: `admin` has complete permissions for all paths. +- `-a guest:guest@/`: `guest` has read-only permissions for all paths. +- `-a user:pass@/dir1:rw,/dir2`: `user` has complete permissions for `/dir1/*`, has read-only permissions for `/dir2/`. +- `-a @/`: All paths is publicly accessible, everyone can view/download it. -``` -dufs -A -a admin:admin@/:rw -a guest:guest@/ -``` -`guest` has readonly permissions for all paths. - -``` -dufs -A -a admin:admin@/:rw -a @/ -``` -All paths is public, everyone can view/download it. - -``` -dufs -A -a admin:admin@/:rw -a user1:pass1@/user1:rw -a user2:pass2@/user2 -dufs -A -a "admin:admin@/:rw|user1:pass1@/user1:rw|user2:pass2@/user2" -``` -`user1` has all permissions for `/user1/*` path. -`user2` has all permissions for `/user2/*` path. - -``` -dufs -A -a user:pass@/dir1:rw,/dir2:rw,dir3 -``` -`user` has all permissions for `/dir1/*` and `/dir2/*`, has readonly permissions for `/dir3/`. - -``` -dufs -A -a admin:admin@/ -``` -Since dufs only allows viewing/downloading, `admin` can only view/download files. +> There are no restrictions on using ':' and '@' characters in a password, `user:pa:ss@1@/:rw` is valid, and the password is `pa:ss@1`. #### Hashed Password @@ -261,13 +235,14 @@ $6$qCAVUG7yn7t/hH4d$BWm8r5MoDywNmDP/J3V2S2a6flmKHC1IpblfoqZfuK.LtLBZ0KFXP9QIfJP8 Use hashed password ``` -dufs -A -a 'admin:$6$qCAVUG7yn7t/hH4d$BWm8r5MoDywNmDP/J3V2S2a6flmKHC1IpblfoqZfuK.LtLBZ0KFXP9QIfJP8RqL8MCw4isdheoAMTuwOz.pAO/@/:rw' +dufs \ + -a 'admin:$6$qCAVUG7yn7t/hH4d$BWm8r5MoDywNmDP/J3V2S2a6flmKHC1IpblfoqZfuK.LtLBZ0KFXP9QIfJP8RqL8MCw4isdheoAMTuwOz.pAO/@/:rw' ``` Two important things for hashed passwords: -1. Dufs only supports SHA-512 hashed passwords, so ensure that the password string always starts with `$6$`. -2. Digest auth does not work with hashed passwords. +1. Dufs only supports sha-512 hashed passwords, so ensure that the password string always starts with `$6$`. +2. Digest authentication does not function properly with hashed passwords. ### Hide Paths diff --git a/src/args.rs b/src/args.rs index c7b00a4..79bf0c2 100644 --- a/src/args.rs +++ b/src/args.rs @@ -81,7 +81,6 @@ pub fn build_cli() -> Command { .long("auth") .help("Add auth roles, e.g. user:pass@/dir1:rw,/dir2") .action(ArgAction::Append) - .value_delimiter('|') .value_name("rules"), ) .arg( diff --git a/src/auth.rs b/src/auth.rs index b7f57d2..144446b 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -53,7 +53,7 @@ impl AccessControl { let mut anony_paths = vec![]; let mut users = IndexMap::new(); for rule in raw_rules { - let (user, list) = rule.split_once('@').ok_or_else(|| create_err(rule))?; + let (user, list) = split_rule(rule).ok_or_else(|| create_err(rule))?; if user.is_empty() && anony.is_some() { bail!("Invalid auth, duplicate anonymous rules"); } @@ -476,10 +476,25 @@ fn create_nonce() -> Result { Ok(n[..34].to_string()) } +fn split_rule(s: &str) -> Option<(&str, &str)> { + let i = s.find("@/")?; + Some((&s[0..i], &s[i + 1..])) +} + #[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"))); + assert_eq!( + split_rule("user:pass@1@/:rw"), + Some(("user:pass@1", "/:rw")) + ); + } + #[test] fn test_access_paths() { let mut paths = AccessPaths::default(); diff --git a/tests/auth.rs b/tests/auth.rs index fa206da..a630c65 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -18,13 +18,15 @@ fn no_auth(#[with(&["--auth", "user:pass@/:rw", "-A"])] server: TestServer) -> R } #[rstest] -fn auth(#[with(&["--auth", "user:pass@/:rw", "-A"])] server: TestServer) -> Result<(), Error> { +#[case(server(&["--auth", "user:pass@/:rw", "-A"]), "user", "pass")] +#[case(server(&["--auth", "user:pa:ss@1@/:rw", "-A"]), "user", "pa:ss@1")] +fn auth(#[case] server: TestServer, #[case] user: &str, #[case] pass: &str) -> Result<(), Error> { let url = format!("{}file1", server.url()); let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?; assert_eq!(resp.status(), 401); let resp = fetch!(b"PUT", &url) .body(b"abc".to_vec()) - .send_with_digest_auth("user", "pass")?; + .send_with_digest_auth(user, pass)?; assert_eq!(resp.status(), 201); Ok(()) } @@ -57,7 +59,7 @@ fn auth_hashed_password( #[rstest] fn auth_and_public( - #[with(&["--auth", "user:pass@/:rw|@/", "-A"])] server: TestServer, + #[with(&["-a", "user:pass@/:rw", "-a", "@/", "-A"])] server: TestServer, ) -> Result<(), Error> { let url = format!("{}file1", server.url()); let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?; @@ -91,7 +93,7 @@ fn auth_skip_on_options_method( #[rstest] fn auth_check( - #[with(&["--auth", "user:pass@/:rw|user2:pass2@/", "-A"])] server: TestServer, + #[with(&["--auth", "user:pass@/:rw", "--auth", "user2:pass2@/", "-A"])] server: TestServer, ) -> Result<(), Error> { let url = format!("{}index.html", server.url()); let resp = fetch!(b"WRITEABLE", &url).send()?; @@ -105,7 +107,7 @@ fn auth_check( #[rstest] fn auth_readonly( - #[with(&["--auth", "user:pass@/:rw|user2:pass2@/", "-A"])] server: TestServer, + #[with(&["--auth", "user:pass@/:rw", "--auth", "user2:pass2@/", "-A"])] server: TestServer, ) -> Result<(), Error> { let url = format!("{}index.html", server.url()); let resp = fetch!(b"GET", &url).send()?; @@ -122,7 +124,7 @@ fn auth_readonly( #[rstest] fn auth_nest( - #[with(&["--auth", "user:pass@/:rw|user2:pass2@/", "--auth", "user3:pass3@/dir1:rw", "-A"])] + #[with(&["--auth", "user:pass@/:rw", "--auth", "user2:pass2@/", "--auth", "user3:pass3@/dir1:rw", "-A"])] server: TestServer, ) -> Result<(), Error> { let url = format!("{}dir1/file1", server.url()); @@ -242,7 +244,7 @@ fn no_auth_propfind_dir( #[rstest] fn auth_data( - #[with(&["--auth", "user:pass@/:rw|@/", "-A"])] server: TestServer, + #[with(&["-a", "user:pass@/:rw", "-a", "@/", "-A"])] server: TestServer, ) -> Result<(), Error> { let resp = reqwest::blocking::get(server.url())?; let content = resp.text()?;