feat: password can contain :
@
|
(#297)
This commit is contained in:
parent
ab29e39148
commit
653cd167d0
4 changed files with 40 additions and 49 deletions
55
README.md
55
README.md
|
@ -81,7 +81,7 @@ Options:
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Serve current working directory in readonly mode
|
Serve current working directory in read-only mode
|
||||||
|
|
||||||
```
|
```
|
||||||
dufs
|
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 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@/path3 -a @/path4
|
||||||
dufs -a user:pass@path1:rw,path2 -a user2:pass2@path1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Multiple rules are separated by "|"
|
1. Use `@` to separate the account and paths. No account means anonymous user.
|
||||||
2. User and pass are the account name and password, if omitted, it is an anonymous user
|
2. Use `:` to separate the username and password of the account.
|
||||||
3. One rule can set multiple paths, separated by ","
|
3. Use `,` to separate paths.
|
||||||
4. Add `:rw` after the path to indicate that the path has read and write permissions, otherwise the path has readonly permissions.
|
4. Use `:rw` suffix to indicate that the account has read-write permission on the path.
|
||||||
|
|
||||||
```
|
- `-a admin:amdin@/:rw`: `admin` has complete permissions for all paths.
|
||||||
dufs -A -a admin:admin@/:rw
|
- `-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/`.
|
||||||
`admin` has all permissions for all paths.
|
- `-a @/`: All paths is publicly accessible, everyone can view/download it.
|
||||||
|
|
||||||
```
|
> 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`.
|
||||||
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.
|
|
||||||
|
|
||||||
#### Hashed Password
|
#### Hashed Password
|
||||||
|
|
||||||
|
@ -261,13 +235,14 @@ $6$qCAVUG7yn7t/hH4d$BWm8r5MoDywNmDP/J3V2S2a6flmKHC1IpblfoqZfuK.LtLBZ0KFXP9QIfJP8
|
||||||
|
|
||||||
Use hashed password
|
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:
|
Two important things for hashed passwords:
|
||||||
|
|
||||||
1. Dufs only supports SHA-512 hashed passwords, so ensure that the password string always starts with `$6$`.
|
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.
|
2. Digest authentication does not function properly with hashed passwords.
|
||||||
|
|
||||||
|
|
||||||
### Hide Paths
|
### Hide Paths
|
||||||
|
|
|
@ -81,7 +81,6 @@ pub fn build_cli() -> Command {
|
||||||
.long("auth")
|
.long("auth")
|
||||||
.help("Add auth roles, e.g. user:pass@/dir1:rw,/dir2")
|
.help("Add auth roles, e.g. user:pass@/dir1:rw,/dir2")
|
||||||
.action(ArgAction::Append)
|
.action(ArgAction::Append)
|
||||||
.value_delimiter('|')
|
|
||||||
.value_name("rules"),
|
.value_name("rules"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
|
17
src/auth.rs
17
src/auth.rs
|
@ -53,7 +53,7 @@ impl AccessControl {
|
||||||
let mut anony_paths = vec![];
|
let mut anony_paths = vec![];
|
||||||
let mut users = IndexMap::new();
|
let mut users = IndexMap::new();
|
||||||
for rule in raw_rules {
|
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() {
|
if user.is_empty() && anony.is_some() {
|
||||||
bail!("Invalid auth, duplicate anonymous rules");
|
bail!("Invalid auth, duplicate anonymous rules");
|
||||||
}
|
}
|
||||||
|
@ -476,10 +476,25 @@ fn create_nonce() -> Result<String> {
|
||||||
Ok(n[..34].to_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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn test_access_paths() {
|
fn test_access_paths() {
|
||||||
let mut paths = AccessPaths::default();
|
let mut paths = AccessPaths::default();
|
||||||
|
|
|
@ -18,13 +18,15 @@ fn no_auth(#[with(&["--auth", "user:pass@/:rw", "-A"])] server: TestServer) -> R
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[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 url = format!("{}file1", server.url());
|
||||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||||
assert_eq!(resp.status(), 401);
|
assert_eq!(resp.status(), 401);
|
||||||
let resp = fetch!(b"PUT", &url)
|
let resp = fetch!(b"PUT", &url)
|
||||||
.body(b"abc".to_vec())
|
.body(b"abc".to_vec())
|
||||||
.send_with_digest_auth("user", "pass")?;
|
.send_with_digest_auth(user, pass)?;
|
||||||
assert_eq!(resp.status(), 201);
|
assert_eq!(resp.status(), 201);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -57,7 +59,7 @@ fn auth_hashed_password(
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn auth_and_public(
|
fn auth_and_public(
|
||||||
#[with(&["--auth", "user:pass@/:rw|@/", "-A"])] server: TestServer,
|
#[with(&["-a", "user:pass@/:rw", "-a", "@/", "-A"])] server: TestServer,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let url = format!("{}file1", server.url());
|
let url = format!("{}file1", server.url());
|
||||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||||
|
@ -91,7 +93,7 @@ fn auth_skip_on_options_method(
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn auth_check(
|
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> {
|
) -> Result<(), Error> {
|
||||||
let url = format!("{}index.html", server.url());
|
let url = format!("{}index.html", server.url());
|
||||||
let resp = fetch!(b"WRITEABLE", &url).send()?;
|
let resp = fetch!(b"WRITEABLE", &url).send()?;
|
||||||
|
@ -105,7 +107,7 @@ fn auth_check(
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn auth_readonly(
|
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> {
|
) -> Result<(), Error> {
|
||||||
let url = format!("{}index.html", server.url());
|
let url = format!("{}index.html", server.url());
|
||||||
let resp = fetch!(b"GET", &url).send()?;
|
let resp = fetch!(b"GET", &url).send()?;
|
||||||
|
@ -122,7 +124,7 @@ fn auth_readonly(
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn auth_nest(
|
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,
|
server: TestServer,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let url = format!("{}dir1/file1", server.url());
|
let url = format!("{}dir1/file1", server.url());
|
||||||
|
@ -242,7 +244,7 @@ fn no_auth_propfind_dir(
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn auth_data(
|
fn auth_data(
|
||||||
#[with(&["--auth", "user:pass@/:rw|@/", "-A"])] server: TestServer,
|
#[with(&["-a", "user:pass@/:rw", "-a", "@/", "-A"])] server: TestServer,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let resp = reqwest::blocking::get(server.url())?;
|
let resp = reqwest::blocking::get(server.url())?;
|
||||||
let content = resp.text()?;
|
let content = resp.text()?;
|
||||||
|
|
Loading…
Reference in a new issue