24
24
//! For an example see example directory.
25
25
//!
26
26
//! TODO:
27
- //! - Implement authentication process
28
27
//! - Implement a [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface)
29
28
//! version of the HttpsFSServer.
30
29
//! * This would allow a user to use any webserver provided by its
@@ -53,7 +52,7 @@ use async_stream::stream;
53
52
use chrono:: prelude:: * ;
54
53
use core:: task:: { Context , Poll } ;
55
54
use futures_util:: stream:: Stream ;
56
- use hyper:: header:: { COOKIE , SET_COOKIE } ;
55
+ use hyper:: header:: { AUTHORIZATION , COOKIE , SET_COOKIE , WWW_AUTHENTICATE } ;
57
56
use hyper:: service:: { make_service_fn, service_fn} ;
58
57
use hyper:: { Body , Method , Request , Response , Server , StatusCode } ;
59
58
use rand:: prelude:: * ;
@@ -70,19 +69,24 @@ use tokio_rustls::server::TlsStream;
70
69
use tokio_rustls:: TlsAcceptor ;
71
70
72
71
mod httpsfserror;
72
+ use httpsfserror:: AuthError ;
73
73
use httpsfserror:: HttpsFSError ;
74
74
75
75
/// A file system exposed over https
76
76
pub struct HttpsFS {
77
77
addr : String ,
78
78
client : std:: sync:: Arc < reqwest:: blocking:: Client > ,
79
+ /// Will be called to get login credentials for the authentication process.
80
+ /// Return value is a tuple: The first part is the user name, the second part the password.
81
+ credentials : Option < fn ( realm : & str ) -> ( String , String ) > ,
79
82
}
80
83
81
84
/// Helper structure for building HttpsFS structs
82
85
pub struct HttpsFSBuilder {
83
86
port : u16 ,
84
87
domain : String ,
85
88
root_certs : Vec < reqwest:: Certificate > ,
89
+ credentials : Option < fn ( realm : & str ) -> ( String , String ) > ,
86
90
}
87
91
88
92
/// A https server providing a interface for HttpsFS
@@ -92,11 +96,13 @@ pub struct HttpsFSServer<T: FileSystem> {
92
96
private_key : rustls:: PrivateKey ,
93
97
file_system : std:: sync:: Arc < std:: sync:: Mutex < T > > ,
94
98
client_data : std:: sync:: Arc < std:: sync:: Mutex < HashMap < String , HttpsFSServerClientData > > > ,
99
+ credential_validator : fn ( user : & str , password : & str ) -> bool ,
95
100
}
96
101
97
102
#[ derive( Debug ) ]
98
103
struct HttpsFSServerClientData {
99
104
last_use : DateTime < Local > ,
105
+ authorized : bool ,
100
106
}
101
107
102
108
struct WritableFile {
@@ -326,11 +332,50 @@ impl HttpsFS {
326
332
327
333
fn exec_command ( & self , cmd : & Command ) -> Result < CommandResponse , HttpsFSError > {
328
334
let req = serde_json:: to_string ( & cmd) ?;
329
- let result = self . client . post ( & self . addr ) . body ( req) . send ( ) ?;
335
+ let mut result = self . client . post ( & self . addr ) . body ( req) . send ( ) ?;
336
+ if result. status ( ) == StatusCode :: UNAUTHORIZED {
337
+ let req = serde_json:: to_string ( & cmd) ?;
338
+ result = self
339
+ . authorize ( & result, self . client . post ( & self . addr ) . body ( req) ) ?
340
+ . send ( ) ?;
341
+ if result. status ( ) != StatusCode :: OK {
342
+ return Err ( HttpsFSError :: Auth ( AuthError :: Failed ) ) ;
343
+ }
344
+ }
330
345
let result = result. text ( ) ?;
331
346
let result: CommandResponse = serde_json:: from_str ( & result) ?;
332
347
Ok ( result)
333
348
}
349
+
350
+ fn authorize (
351
+ & self ,
352
+ prev_response : & reqwest:: blocking:: Response ,
353
+ new_request : reqwest:: blocking:: RequestBuilder ,
354
+ ) -> Result < reqwest:: blocking:: RequestBuilder , HttpsFSError > {
355
+ if self . credentials . is_none ( ) {
356
+ return Err ( HttpsFSError :: Auth ( AuthError :: NoCredentialSource ) ) ;
357
+ }
358
+ let prev_headers = prev_response. headers ( ) ;
359
+ let auth_method = prev_headers
360
+ . get ( WWW_AUTHENTICATE )
361
+ . ok_or ( HttpsFSError :: Auth ( AuthError :: NoMethodSpecified ) ) ?;
362
+ let auth_method = String :: from (
363
+ auth_method
364
+ . to_str ( )
365
+ . map_err ( |_| HttpsFSError :: InvalidHeader ( WWW_AUTHENTICATE . to_string ( ) ) ) ?,
366
+ ) ;
367
+ // TODO: this is a fix hack since we currently only support one method. If we start to
368
+ // support more than one authentication method, we have to properly parse this header.
369
+ // Furthermore, currently only the 'PME'-Realm is supported.
370
+ let start_with = "Basic realm=\" PME\" " ;
371
+ if !auth_method. starts_with ( start_with) {
372
+ return Err ( HttpsFSError :: Auth ( AuthError :: MethodNotSupported ) ) ;
373
+ }
374
+ let get_cred = self . credentials . unwrap ( ) ;
375
+ let ( username, password) = get_cred ( & "PME" ) ;
376
+ let new_request = new_request. basic_auth ( username, Some ( password) ) ;
377
+ Ok ( new_request)
378
+ }
334
379
}
335
380
336
381
impl HttpsFSBuilder {
@@ -339,6 +384,7 @@ impl HttpsFSBuilder {
339
384
port : 443 ,
340
385
domain : String :: from ( domain) ,
341
386
root_certs : Vec :: new ( ) ,
387
+ credentials : None ,
342
388
}
343
389
}
344
390
@@ -357,7 +403,20 @@ impl HttpsFSBuilder {
357
403
self
358
404
}
359
405
406
+ pub fn set_credential_provider (
407
+ mut self ,
408
+ c_provider : fn ( realm : & str ) -> ( String , String ) ,
409
+ ) -> Self {
410
+ self . credentials = Some ( c_provider) ;
411
+ self
412
+ }
413
+
360
414
pub fn build ( self ) -> VfsResult < HttpsFS > {
415
+ if self . credentials . is_none ( ) {
416
+ return Err ( VfsError :: Other {
417
+ message : format ! ( "HttpsFSBuilder: No credential provider set." ) ,
418
+ } ) ;
419
+ }
361
420
let mut client = Client :: builder ( ) . https_only ( true ) . cookie_store ( true ) ;
362
421
for cert in self . root_certs {
363
422
client = client. add_root_certificate ( cert) ;
@@ -367,6 +426,7 @@ impl HttpsFSBuilder {
367
426
Ok ( HttpsFS {
368
427
client : std:: sync:: Arc :: new ( client) ,
369
428
addr : format ! ( "https://{}:{}/" , self . domain, self . port) ,
429
+ credentials : self . credentials ,
370
430
} )
371
431
}
372
432
}
@@ -399,6 +459,7 @@ impl HttpsFSServerClientData {
399
459
fn new ( ) -> Self {
400
460
HttpsFSServerClientData {
401
461
last_use : Local :: now ( ) ,
462
+ authorized : false ,
402
463
}
403
464
}
404
465
}
@@ -409,6 +470,7 @@ impl<T: FileSystem> HttpsFSServer<T> {
409
470
certs : Vec < rustls:: Certificate > ,
410
471
private_key : rustls:: PrivateKey ,
411
472
file_system : T ,
473
+ credential_validator : fn ( user : & str , password : & str ) -> bool ,
412
474
) -> Self {
413
475
// Initially i tried to store a hyper::server::Server object in HttpsFSServer.
414
476
// I failed, since this type is a very complicated generic and i could
@@ -435,6 +497,7 @@ impl<T: FileSystem> HttpsFSServer<T> {
435
497
private_key,
436
498
file_system : std:: sync:: Arc :: new ( std:: sync:: Mutex :: new ( file_system) ) ,
437
499
client_data : std:: sync:: Arc :: new ( std:: sync:: Mutex :: new ( HashMap :: new ( ) ) ) ,
500
+ credential_validator,
438
501
}
439
502
}
440
503
@@ -444,6 +507,7 @@ impl<T: FileSystem> HttpsFSServer<T> {
444
507
let addr = format ! ( "127.0.0.1:{}" , self . port) ;
445
508
let fs = self . file_system . clone ( ) ;
446
509
let cd = self . client_data . clone ( ) ;
510
+ let cv = self . credential_validator . clone ( ) ;
447
511
448
512
let mut cfg = rustls:: ServerConfig :: new ( rustls:: NoClientAuth :: new ( ) ) ;
449
513
cfg. set_single_cert ( self . certs . clone ( ) , self . private_key . clone ( ) )
@@ -507,7 +571,7 @@ impl<T: FileSystem> HttpsFSServer<T> {
507
571
move |request| {
508
572
let fs = fs. clone ( ) ;
509
573
let cd = cd. clone ( ) ;
510
- HttpsFSServer :: https_fs_service ( fs, cd, request)
574
+ HttpsFSServer :: https_fs_service ( fs, cd, cv , request)
511
575
} ,
512
576
) )
513
577
}
@@ -529,12 +593,32 @@ impl<T: FileSystem> HttpsFSServer<T> {
529
593
async fn https_fs_service (
530
594
file_system : std:: sync:: Arc < std:: sync:: Mutex < T > > ,
531
595
client_data : std:: sync:: Arc < std:: sync:: Mutex < HashMap < String , HttpsFSServerClientData > > > ,
596
+ credential_validator : fn ( user : & str , pass : & str ) -> bool ,
532
597
req : Request < Body > ,
533
598
) -> Result < Response < Body > , hyper:: Error > {
599
+ // TODO: Separate Session, authorization and content handling in different methods.
534
600
let mut response = Response :: new ( Body :: empty ( ) ) ;
535
601
536
602
HttpsFSServer :: < T > :: clean_up_client_data ( & client_data) ;
537
603
let sess_id = HttpsFSServer :: < T > :: get_session_id ( & client_data, & req, & mut response) ;
604
+ let auth_res =
605
+ HttpsFSServer :: < T > :: try_auth ( & client_data, & sess_id, & credential_validator, & req) ;
606
+ match auth_res {
607
+ Err ( ( ) ) => {
608
+ * response. status_mut ( ) = StatusCode :: INTERNAL_SERVER_ERROR ;
609
+ return Ok ( response) ;
610
+ }
611
+ Ok ( value) => {
612
+ if !value {
613
+ * response. status_mut ( ) = StatusCode :: UNAUTHORIZED ;
614
+ response. headers_mut ( ) . insert (
615
+ WWW_AUTHENTICATE ,
616
+ "Basic realm=\" PME\" , charset=\" UTF-8\" " . parse ( ) . unwrap ( ) ,
617
+ ) ;
618
+ return Ok ( response) ;
619
+ }
620
+ }
621
+ }
538
622
539
623
match ( req. method ( ) , req. uri ( ) . path ( ) ) {
540
624
( & Method :: POST , "/" ) => {
@@ -728,6 +812,69 @@ impl<T: FileSystem> HttpsFSServer<T> {
728
812
729
813
return sess_id;
730
814
}
815
+
816
+ fn try_auth (
817
+ client_data : & std:: sync:: Arc < std:: sync:: Mutex < HashMap < String , HttpsFSServerClientData > > > ,
818
+ sess_id : & str ,
819
+ credential_validator : & fn ( user : & str , pass : & str ) -> bool ,
820
+ request : & Request < Body > ,
821
+ ) -> Result < bool , ( ) > {
822
+ let mut client_data = client_data. lock ( ) . unwrap ( ) ;
823
+ let sess_data = client_data. get_mut ( sess_id) ;
824
+ if let None = sess_data {
825
+ return Err ( ( ) ) ;
826
+ }
827
+ let sess_data = sess_data. unwrap ( ) ;
828
+
829
+ // try to authenticate client
830
+ if !sess_data. authorized {
831
+ let headers = request. headers ( ) ;
832
+ let auth = headers. get ( AUTHORIZATION ) ;
833
+ if let None = auth {
834
+ return Ok ( false ) ;
835
+ }
836
+ let auth = auth. unwrap ( ) . to_str ( ) ;
837
+ if let Err ( _) = auth {
838
+ return Ok ( false ) ;
839
+ }
840
+ let auth = auth. unwrap ( ) ;
841
+ let starts = "Basic " ;
842
+ if !auth. starts_with ( starts) {
843
+ return Ok ( false ) ;
844
+ }
845
+ let auth = base64:: decode ( & auth[ starts. len ( ) ..] ) ;
846
+ if let Err ( _) = auth {
847
+ return Ok ( false ) ;
848
+ }
849
+ let auth = auth. unwrap ( ) ;
850
+ let auth = String :: from_utf8 ( auth) ;
851
+ if let Err ( _) = auth {
852
+ return Ok ( false ) ;
853
+ }
854
+ let auth = auth. unwrap ( ) ;
855
+ let mut auth_it = auth. split ( ":" ) ;
856
+ let username = auth_it. next ( ) ;
857
+ if let None = username {
858
+ return Ok ( false ) ;
859
+ }
860
+ let username = username. unwrap ( ) ;
861
+ let pass = auth_it. next ( ) ;
862
+ if let None = pass {
863
+ return Ok ( false ) ;
864
+ }
865
+ let pass = pass. unwrap ( ) ;
866
+ if credential_validator ( username, pass) {
867
+ sess_data. authorized = true ;
868
+ }
869
+ }
870
+
871
+ // if not authenticated, than inform client about it.
872
+ if sess_data. authorized {
873
+ return Ok ( true ) ;
874
+ }
875
+
876
+ return Ok ( false ) ;
877
+ }
731
878
}
732
879
733
880
/// Load public certificate from file
@@ -903,6 +1050,7 @@ impl Seek for ReadableFile {
903
1050
let fs = HttpsFS {
904
1051
addr : self . addr . clone ( ) ,
905
1052
client : self . client . clone ( ) ,
1053
+ credentials : None ,
906
1054
} ;
907
1055
let meta = fs. metadata ( & self . file_name ) ;
908
1056
if let Err ( e) = meta {
@@ -1038,7 +1186,7 @@ impl FileSystem for HttpsFS {
1038
1186
} ) ;
1039
1187
let result = self . exec_command ( & req) ;
1040
1188
if let Err ( e) = result {
1041
- println ! ( "Error: {:? }" , e) ;
1189
+ println ! ( "Error: {}" , e) ;
1042
1190
return false ;
1043
1191
}
1044
1192
match result. unwrap ( ) {
@@ -1124,7 +1272,10 @@ mod tests {
1124
1272
let fs = MemoryFS :: new( ) ;
1125
1273
let cert = load_certs( "examples/cert/cert.crt" ) . unwrap( ) ;
1126
1274
let private_key = load_private_key( "examples/cert/private-key.key" ) . unwrap( ) ;
1127
- let mut server = HttpsFSServer :: new( server_port, cert, private_key, fs) ;
1275
+ let credential_validator =
1276
+ |username: & str , password: & str | username == "user" && password == "pass" ;
1277
+ let mut server =
1278
+ HttpsFSServer :: new( server_port, cert, private_key, fs, credential_validator) ;
1128
1279
let result = server. run( ) ;
1129
1280
if let Err ( e) = result {
1130
1281
println!( "WARNING: {:?}" , e) ;
@@ -1142,6 +1293,7 @@ mod tests {
1142
1293
HttpsFS :: builder( "localhost" )
1143
1294
. set_port( server_port)
1144
1295
. add_root_certificate( cert)
1296
+ . set_credential_provider( |_| ( String :: from( "user" ) , String :: from( "pass" ) ) )
1145
1297
. build( )
1146
1298
. unwrap( )
1147
1299
} ) ;
0 commit comments