@@ -106,6 +106,7 @@ pub async fn authentication_required<C: Locked + 'static, B>(
106
106
}
107
107
}
108
108
109
+ /// Creates a [`Session`] and additionally [`Auth`] if a Bearer token was provided.
109
110
pub async fn authenticate < C : Locked + ' static , B > (
110
111
mut request : axum:: http:: Request < B > ,
111
112
next : Next < B > ,
@@ -286,120 +287,210 @@ async fn for_request<C: Locked>(
286
287
Ok ( req)
287
288
}
288
289
290
+ /// Get's the Request IP from either `true-client-ip` or `x-forwarded-for`,
291
+ /// splits the IPs separated by `,` (comma) and returns the first one.
289
292
fn get_request_ip < B > ( req : & Request < B > ) -> Option < String > {
290
293
req. headers ( )
291
294
. get ( "true-client-ip" )
292
295
. or_else ( || req. headers ( ) . get ( "x-forwarded-for" ) )
293
- . and_then ( |hv| hv. to_str ( ) . map ( ToString :: to_string) . ok ( ) )
294
- . and_then ( |token| token. split ( ',' ) . next ( ) . map ( ToString :: to_string) )
296
+ . and_then ( |hv| {
297
+ hv. to_str ( )
298
+ // filter out empty headers
299
+ . map ( ToString :: to_string)
300
+ . ok ( )
301
+ . filter ( |ip| !ip. is_empty ( ) )
302
+ } )
303
+ . and_then ( |token| {
304
+ token
305
+ . split ( ',' )
306
+ . next ( )
307
+ // filter out empty IP
308
+ . filter ( |ip| !ip. is_empty ( ) )
309
+ . map ( ToString :: to_string)
310
+ } )
295
311
}
296
312
297
313
#[ cfg( test) ]
298
314
mod test {
299
315
use adapter:: {
300
- dummy:: { Dummy , HeaderToken , Options } ,
316
+ dummy:: { Dummy , HeaderToken } ,
301
317
ethereum:: test_util:: GANACHE_1 ,
302
- Adapter ,
303
- } ;
304
- use hyper:: Request ;
305
- use primitives:: {
306
- config:: GANACHE_CONFIG ,
307
- test_util:: IDS ,
308
- test_util:: { DUMMY_AUTH , LEADER } ,
309
318
} ;
319
+ use axum:: { middleware:: from_fn, routing:: get, Extension , Router } ;
320
+ use hyper:: { Request , StatusCode } ;
321
+ use primitives:: test_util:: { DUMMY_AUTH , LEADER } ;
310
322
311
- use deadpool :: managed :: Object ;
323
+ use tower :: Service ;
312
324
313
- use crate :: {
314
- db:: redis_pool:: { Manager , TESTS_POOL } ,
315
- Session ,
316
- } ;
325
+ use crate :: { middleware:: body_to_string, test_util:: setup_dummy_app, Session } ;
317
326
318
327
use super :: * ;
319
328
320
- async fn setup ( ) -> ( Adapter < Dummy > , Object < Manager > ) {
321
- let connection = TESTS_POOL . get ( ) . await . expect ( "Should return Object" ) ;
322
- let adapter_options = Options {
323
- dummy_identity : IDS [ & LEADER ] ,
324
- dummy_auth_tokens : DUMMY_AUTH . clone ( ) ,
325
- dummy_chains : GANACHE_CONFIG . chains . values ( ) . cloned ( ) . collect ( ) ,
326
- } ;
327
-
328
- ( Adapter :: new ( Dummy :: init ( adapter_options) ) , connection)
329
- }
330
-
331
329
#[ tokio:: test]
332
330
async fn no_authentication_or_incorrect_value_should_not_add_session ( ) {
333
- let no_auth_req = Request :: builder ( )
334
- . body ( Body :: empty ( ) )
335
- . expect ( "should never fail!" ) ;
331
+ let app_guard = setup_dummy_app ( ) . await ;
332
+ let app = Arc :: new ( app_guard. app ) ;
336
333
337
- let ( dummy_adapter, database) = setup ( ) . await ;
338
- let no_auth = for_request ( no_auth_req, & dummy_adapter, & database)
339
- . await
340
- . expect ( "Handling the Request shouldn't have failed" ) ;
334
+ async fn handle ( ) -> String {
335
+ "Ok" . into ( )
336
+ }
337
+
338
+ let mut router = Router :: new ( )
339
+ . route ( "/" , get ( handle) )
340
+ . layer ( from_fn ( authenticate :: < Dummy , _ > ) ) ;
341
341
342
- assert ! (
343
- no_auth. extensions( ) . get:: <Auth >( ) . is_none( ) ,
344
- "There shouldn't be a Session in the extensions"
345
- ) ;
342
+ {
343
+ let no_auth_req = Request :: builder ( )
344
+ . extension ( app. clone ( ) )
345
+ . body ( Body :: empty ( ) )
346
+ . expect ( "should never fail!" ) ;
347
+
348
+ let no_auth = router
349
+ . call ( no_auth_req)
350
+ . await
351
+ . expect ( "Handling the Request shouldn't have failed" ) ;
352
+
353
+ assert ! (
354
+ no_auth. extensions( ) . get:: <Auth >( ) . is_none( ) ,
355
+ "There shouldn't be a Auth in the extensions"
356
+ ) ;
357
+ }
346
358
347
359
// there is a Header, but it has wrong format
348
- let incorrect_auth_req = Request :: builder ( )
349
- . header ( AUTHORIZATION , "Wrong Header" )
350
- . body ( Body :: empty ( ) )
351
- . unwrap ( ) ;
352
- let incorrect_auth = for_request ( incorrect_auth_req, & dummy_adapter, & database)
353
- . await
354
- . expect ( "Handling the Request shouldn't have failed" ) ;
355
- assert ! (
356
- incorrect_auth. extensions( ) . get:: <Auth >( ) . is_none( ) ,
357
- "There shouldn't be a Session in the extensions"
358
- ) ;
360
+ {
361
+ let incorrect_auth_req = Request :: builder ( )
362
+ . header ( AUTHORIZATION , "Wrong Header" )
363
+ . extension ( app. clone ( ) )
364
+ . body ( Body :: empty ( ) )
365
+ . expect ( "should never fail!" ) ;
366
+
367
+ let incorrect_auth = router
368
+ . call ( incorrect_auth_req)
369
+ . await
370
+ . expect ( "Handling the Request shouldn't have failed" ) ;
371
+
372
+ assert ! (
373
+ incorrect_auth. extensions( ) . get:: <Auth >( ) . is_none( ) ,
374
+ "There shouldn't be an Auth in the extensions"
375
+ ) ;
376
+ }
359
377
360
378
// Token doesn't exist in the Adapter nor in Redis
361
- let non_existent_token_req = Request :: builder ( )
362
- . header ( AUTHORIZATION , "Bearer wrong-token" )
363
- . body ( Body :: empty ( ) )
364
- . unwrap ( ) ;
365
- match for_request ( non_existent_token_req, & dummy_adapter, & database) . await {
366
- Err ( error) => {
367
- assert_eq ! ( error. to_string( ) , "Authentication: Dummy Authentication token format should be in the format: `{Auth Token}:chain_id:{Chain Id}` but 'wrong-token' was provided" ) ;
368
- }
369
- _ => panic ! ( "We shouldn't get a success response nor a different Error than BadRequest for this call" ) ,
370
- } ;
379
+ {
380
+ let non_existent_token_req = Request :: builder ( )
381
+ . header ( AUTHORIZATION , "Bearer wrong-token" )
382
+ . extension ( app. clone ( ) )
383
+ . body ( Body :: empty ( ) )
384
+ . unwrap ( ) ;
385
+
386
+ let response = router
387
+ . call ( non_existent_token_req)
388
+ . await
389
+ . expect ( "Handling the Request shouldn't have failed" ) ;
390
+
391
+ assert_eq ! ( response. status( ) , StatusCode :: BAD_REQUEST ) ;
392
+ let response_body = body_to_string ( response) . await ;
393
+ assert_eq ! ( "Authentication: Dummy Authentication token format should be in the format: `{Auth Token}:chain_id:{Chain Id}` but 'wrong-token' was provided" , response_body)
394
+ }
371
395
}
372
396
373
397
#[ tokio:: test]
374
398
async fn session_from_correct_authentication_token ( ) {
375
- let ( dummy_adapter, database) = setup ( ) . await ;
399
+ let app_guard = setup_dummy_app ( ) . await ;
400
+ let app = Arc :: new ( app_guard. app ) ;
376
401
377
402
let header_token = HeaderToken {
378
403
token : DUMMY_AUTH [ & LEADER ] . clone ( ) ,
379
404
chain_id : GANACHE_1 . chain_id ,
380
405
} ;
381
406
407
+ async fn handle (
408
+ Extension ( auth) : Extension < Auth > ,
409
+ Extension ( session) : Extension < Session > ,
410
+ ) -> String {
411
+ assert_eq ! ( Some ( "120.0.0.1" . to_string( ) ) , session. ip) ;
412
+ assert_eq ! ( * LEADER , auth. uid. to_address( ) ) ;
413
+
414
+ "Ok" . into ( )
415
+ }
416
+
417
+ let mut router = Router :: new ( )
418
+ . route ( "/" , get ( handle) )
419
+ . layer ( from_fn ( authenticate :: < Dummy , _ > ) ) ;
420
+
382
421
let auth_header = format ! ( "Bearer {header_token}" ) ;
383
- let req = Request :: builder ( )
422
+ let request = Request :: builder ( )
384
423
. header ( AUTHORIZATION , auth_header)
424
+ . header ( "true-client-ip" , "120.0.0.1" )
425
+ . extension ( app. clone ( ) )
385
426
. body ( Body :: empty ( ) )
386
427
. unwrap ( ) ;
387
428
388
- let altered_request = for_request ( req, & dummy_adapter, & database)
429
+ // The handle takes care of the assertions for the Extensions for us
430
+ let response = router
431
+ . call ( request)
389
432
. await
390
433
. expect ( "Valid requests should succeed" ) ;
391
434
392
- let auth = altered_request
393
- . extensions ( )
394
- . get :: < Auth > ( )
395
- . expect ( "There should be a Session set inside the request" ) ;
435
+ assert_eq ! ( "Ok" , body_to_string( response) . await ) ;
436
+ }
437
+
438
+ #[ test]
439
+ fn test_get_request_ip_headers ( ) {
440
+ let build_request = |header : & str , ips : & str | -> Request < Body > {
441
+ Request :: builder ( )
442
+ . header ( header, ips)
443
+ . body ( Body :: empty ( ) )
444
+ . unwrap ( )
445
+ } ;
396
446
397
- assert_eq ! ( * LEADER , auth. uid. to_address( ) ) ;
447
+ // No set headers
448
+ {
449
+ let request = Request :: builder ( ) . body ( Body :: empty ( ) ) . unwrap ( ) ;
450
+ let no_headers = get_request_ip ( & request) ;
451
+ assert_eq ! ( None , no_headers) ;
452
+ }
398
453
399
- let session = altered_request
400
- . extensions ( )
401
- . get :: < Session > ( )
402
- . expect ( "There should be a Session set inside the request" ) ;
403
- assert ! ( session. ip. is_none( ) ) ;
454
+ // Empty headers
455
+ {
456
+ let true_client_ip = build_request ( "true-client-ip" , "" ) ;
457
+ let x_forwarded_for = build_request ( "x-forwarded-for" , "" ) ;
458
+
459
+ let actual_true_client = get_request_ip ( & true_client_ip) ;
460
+ let actual_x_forwarded = get_request_ip ( & x_forwarded_for) ;
461
+
462
+ assert_eq ! ( None , actual_true_client) ;
463
+ assert_eq ! ( None , actual_x_forwarded) ;
464
+ }
465
+
466
+ // Empty IPs `","`
467
+ {
468
+ let true_client_ip = build_request ( "true-client-ip" , "," ) ;
469
+ let x_forwarded_for = build_request ( "x-forwarded-for" , "," ) ;
470
+
471
+ let actual_true_client = get_request_ip ( & true_client_ip) ;
472
+ let actual_x_forwarded = get_request_ip ( & x_forwarded_for) ;
473
+
474
+ assert_eq ! ( None , actual_true_client) ;
475
+ assert_eq ! ( None , actual_x_forwarded) ;
476
+ }
477
+
478
+ // "true-client-ip" - Single IP
479
+ {
480
+ let ips = "120.0.0.1" ;
481
+ let true_client_ip = build_request ( "true-client-ip" , ips) ;
482
+ let actual_ips = get_request_ip ( & true_client_ip) ;
483
+
484
+ assert_eq ! ( Some ( ips. to_string( ) ) , actual_ips) ;
485
+ }
486
+
487
+ // "x-forwarded-for" - Multiple IPs
488
+ {
489
+ let ips = "192.168.0.1,120.0.0.1,10.0.0.10" ;
490
+ let true_client_ip = build_request ( "x-forwarded-for" , ips) ;
491
+ let actual_ips = get_request_ip ( & true_client_ip) ;
492
+
493
+ assert_eq ! ( Some ( "192.168.0.1" . to_string( ) ) , actual_ips) ;
494
+ }
404
495
}
405
496
}
0 commit comments