Skip to content

Commit 4d354e1

Browse files
committed
sentry - middleware - authenticate - rewrite tests
- add tests for `get_request_ip`
1 parent 60bf86d commit 4d354e1

File tree

1 file changed

+162
-71
lines changed

1 file changed

+162
-71
lines changed

sentry/src/middleware/auth.rs

Lines changed: 162 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ pub async fn authentication_required<C: Locked + 'static, B>(
106106
}
107107
}
108108

109+
/// Creates a [`Session`] and additionally [`Auth`] if a Bearer token was provided.
109110
pub async fn authenticate<C: Locked + 'static, B>(
110111
mut request: axum::http::Request<B>,
111112
next: Next<B>,
@@ -286,120 +287,210 @@ async fn for_request<C: Locked>(
286287
Ok(req)
287288
}
288289

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.
289292
fn get_request_ip<B>(req: &Request<B>) -> Option<String> {
290293
req.headers()
291294
.get("true-client-ip")
292295
.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+
})
295311
}
296312

297313
#[cfg(test)]
298314
mod test {
299315
use adapter::{
300-
dummy::{Dummy, HeaderToken, Options},
316+
dummy::{Dummy, HeaderToken},
301317
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},
309318
};
319+
use axum::{middleware::from_fn, routing::get, Extension, Router};
320+
use hyper::{Request, StatusCode};
321+
use primitives::test_util::{DUMMY_AUTH, LEADER};
310322

311-
use deadpool::managed::Object;
323+
use tower::Service;
312324

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};
317326

318327
use super::*;
319328

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-
331329
#[tokio::test]
332330
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);
336333

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, _>));
341341

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+
}
346358

347359
// 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+
}
359377

360378
// 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+
}
371395
}
372396

373397
#[tokio::test]
374398
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);
376401

377402
let header_token = HeaderToken {
378403
token: DUMMY_AUTH[&LEADER].clone(),
379404
chain_id: GANACHE_1.chain_id,
380405
};
381406

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+
382421
let auth_header = format!("Bearer {header_token}");
383-
let req = Request::builder()
422+
let request = Request::builder()
384423
.header(AUTHORIZATION, auth_header)
424+
.header("true-client-ip", "120.0.0.1")
425+
.extension(app.clone())
385426
.body(Body::empty())
386427
.unwrap();
387428

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)
389432
.await
390433
.expect("Valid requests should succeed");
391434

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+
};
396446

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+
}
398453

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+
}
404495
}
405496
}

0 commit comments

Comments
 (0)