Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: password can contain : @ | #297

Merged
merged 2 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 15 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Options:

## Examples

Serve current working directory in readonly mode
Serve current working directory in read-only mode

```
dufs
Expand Down Expand Up @@ -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.

```
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.
- `-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 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

Expand All @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
17 changes: 16 additions & 1 deletion src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down Expand Up @@ -476,10 +476,25 @@ fn create_nonce() -> Result<String> {
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();
Expand Down
16 changes: 9 additions & 7 deletions tests/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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()?;
Expand Down Expand Up @@ -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()?;
Expand All @@ -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()?;
Expand All @@ -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());
Expand Down Expand Up @@ -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()?;
Expand Down