@ -1,4 +1,5 @@
@@ -1,4 +1,5 @@
use headers ::HeaderValue ;
use hyper ::Method ;
use lazy_static ::lazy_static ;
use md5 ::Context ;
use std ::{
@ -7,6 +8,7 @@ use std::{
@@ -7,6 +8,7 @@ use std::{
} ;
use uuid ::Uuid ;
use crate ::utils ::encode_uri ;
use crate ::BoxResult ;
const REALM : & str = "DUF" ;
@ -20,6 +22,151 @@ lazy_static! {
@@ -20,6 +22,151 @@ lazy_static! {
} ;
}
#[ derive(Debug, Clone) ]
pub struct AccessControl {
rules : HashMap < String , PathControl > ,
}
#[ derive(Debug, Clone) ]
pub struct PathControl {
readwrite : Account ,
readonly : Option < Account > ,
share : bool ,
}
impl AccessControl {
pub fn new ( raw_rules : & [ & str ] , uri_prefix : & str ) -> BoxResult < Self > {
let mut rules = HashMap ::default ( ) ;
if raw_rules . is_empty ( ) {
return Ok ( Self { rules } ) ;
}
for rule in raw_rules {
let parts : Vec < & str > = rule . split ( '@' ) . collect ( ) ;
let create_err = | | format! ( "Invalid auth `{}`" , rule ) . into ( ) ;
match parts . as_slice ( ) {
[ path , readwrite ] = > {
let control = PathControl {
readwrite : Account ::new ( readwrite ) . ok_or_else ( create_err ) ? ,
readonly : None ,
share : false ,
} ;
rules . insert ( sanitize_path ( path , uri_prefix ) , control ) ;
}
[ path , readwrite , readonly ] = > {
let ( readonly , share ) = if * readonly = = "*" {
( None , true )
} else {
( Some ( Account ::new ( readonly ) . ok_or_else ( create_err ) ? ) , false )
} ;
let control = PathControl {
readwrite : Account ::new ( readwrite ) . ok_or_else ( create_err ) ? ,
readonly ,
share ,
} ;
rules . insert ( sanitize_path ( path , uri_prefix ) , control ) ;
}
_ = > return Err ( create_err ( ) ) ,
}
}
Ok ( Self { rules } )
}
pub fn guard (
& self ,
path : & str ,
method : & Method ,
authorization : Option < & HeaderValue > ,
) -> GuardType {
if self . rules . is_empty ( ) {
return GuardType ::ReadWrite ;
}
let mut controls = vec! [ ] ;
for path in walk_path ( path ) {
if let Some ( control ) = self . rules . get ( path ) {
controls . push ( control ) ;
if let Some ( authorization ) = authorization {
let Account { user , pass } = & control . readwrite ;
if valid_digest ( authorization , method . as_str ( ) , user , pass ) . is_some ( ) {
return GuardType ::ReadWrite ;
}
}
}
}
if is_readonly_method ( method ) {
for control in controls . into_iter ( ) {
if control . share {
return GuardType ::ReadOnly ;
}
if let Some ( authorization ) = authorization {
if let Some ( Account { user , pass } ) = & control . readonly {
if valid_digest ( authorization , method . as_str ( ) , user , pass ) . is_some ( ) {
return GuardType ::ReadOnly ;
}
}
}
}
}
GuardType ::Reject
}
}
#[ derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord) ]
pub enum GuardType {
Reject ,
ReadWrite ,
ReadOnly ,
}
impl GuardType {
pub fn is_reject ( & self ) -> bool {
* self = = GuardType ::Reject
}
}
fn sanitize_path ( path : & str , uri_prefix : & str ) -> String {
encode_uri ( & format! ( "{}{}" , uri_prefix , path . trim_matches ( '/' ) ) )
}
fn walk_path ( path : & str ) -> impl Iterator < Item = & str > {
let mut idx = 0 ;
path . split ( '/' ) . enumerate ( ) . map ( move | ( i , part ) | {
let end = if i = = 0 { 1 } else { idx + part . len ( ) + i } ;
let value = & path [ .. end ] ;
idx + = part . len ( ) ;
value
} )
}
fn is_readonly_method ( method : & Method ) -> bool {
method = = Method ::GET
| | method = = Method ::OPTIONS
| | method = = Method ::HEAD
| | method . as_str ( ) = = "PROPFIND"
}
#[ derive(Debug, Clone) ]
struct Account {
user : String ,
pass : String ,
}
impl Account {
fn new ( data : & str ) -> Option < Self > {
let p : Vec < & str > = data . trim ( ) . split ( ':' ) . collect ( ) ;
if p . len ( ) ! = 2 {
return None ;
}
let user = p [ 0 ] ;
let pass = p [ 1 ] ;
let mut h = Context ::new ( ) ;
h . consume ( format! ( "{}:{}:{}" , user , REALM , pass ) . as_bytes ( ) ) ;
Some ( Account {
user : user . to_owned ( ) ,
pass : format ! ( "{:x}" , h . compute ( ) ) ,
} )
}
}
pub fn generate_www_auth ( stale : bool ) -> String {
let str_stale = if stale { "stale=true," } else { "" } ;
format! (
@ -30,26 +177,13 @@ pub fn generate_www_auth(stale: bool) -> String {
@@ -30,26 +177,13 @@ pub fn generate_www_auth(stale: bool) -> String {
)
}
pub fn parse_auth ( auth : & str ) -> BoxResult < ( String , String ) > {
let p : Vec < & str > = auth . trim ( ) . split ( ':' ) . collect ( ) ;
let err = "Invalid auth value" ;
if p . len ( ) ! = 2 {
return Err ( err . into ( ) ) ;
}
let user = p [ 0 ] ;
let pass = p [ 1 ] ;
let mut h = Context ::new ( ) ;
h . consume ( format! ( "{}:{}:{}" , user , REALM , pass ) . as_bytes ( ) ) ;
Ok ( ( user . to_owned ( ) , format! ( "{:x}" , h . compute ( ) ) ) )
}
pub fn valid_digest (
header_value : & HeaderValue ,
authorization : & HeaderValue ,
method : & str ,
auth_user : & str ,
auth_pass : & str ,
) -> Option < ( ) > {
let digest_value = strip_prefix ( header_value . as_bytes ( ) , b" Digest " ) ? ;
let digest_value = strip_prefix ( authorization . as_bytes ( ) , b" Digest " ) ? ;
let user_vals = to_headermap ( digest_value ) . ok ( ) ? ;
if let ( Some ( username ) , Some ( nonce ) , Some ( user_response ) ) = (
user_vals