actix-web icon indicating copy to clipboard operation
actix-web copied to clipboard

Extract path arguments in middleware before processing the request

Open tl8roy opened this issue 5 years ago • 6 comments

As per https://github.com/actix/actix-web/issues/1784#issue-739668855, the path arguments in middleware are not accessible until after the request is processed.

Expected Behavior

actix_web::web::Path::<String>::from_request(request, payload) in a service call returns path argument(s) at any point in the service call.

Current Behavior

Output of request.match_info(), request.match_pattern() and actix_web::web::Path::<String>::from_request(request, payload) from inside the service call: "wrong number of parameters: 0 expected 1".

Steps to Reproduce (for bugs)

  1. Implement a middleware service, with the Path extractor
  2. Call the middleware on a route which takes a path argument

Context

I am trying to implement guards on the URL path to ensure that users can only access what they are allowed to access. Only being able to access the patch arguments after the request means that it is not possible to use this method for endpoints that update information

Your Environment

Rust Version: 1.50.0
Actix Web Version: 3.3.2

tl8roy avatar Mar 20 '21 00:03 tl8roy

Wasn't able to reproduce. Make sure you've set your wrapper under a scope to capture parameters. my test code:

use actix_web::dev::{ResourceDef, Service};
use actix_web::{error, web, App, HttpResponse, HttpServer};
use actix_web_codegen::{get, post};

#[get("/")]
async fn dummy() -> &'static str {
    "OK\n"
}

#[actix_web::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let server = HttpServer::new(move || {
        App::new().service(
            web::scope("/{uid}") // <= Make sure you have set the upper scope
                .wrap_fn(|req, srv| {
                    dbg!(req.match_info().get("uid")); // <= Works as expected!
                    srv.call(req)
                })
                .service(dummy),
        )
    })
    .bind("127.0.0.1:7311")?
    .run()?
    .await?;
    Ok(())
}

aliemjay avatar Jun 05 '21 21:06 aliemjay

Sorry, I am talking middleware service added via App::new().wrap(Middleware::new())

tl8roy avatar Jun 05 '21 23:06 tl8roy

If you want to access a path argument in middleware, you you should add the middleware to a scope that defines such argument (as in the example above), not to the app root.

aliemjay avatar Jun 05 '21 23:06 aliemjay

After having a play, if I have to use scopes to access the path in the middleware, then I don't think what I want to do will work.

Basically I want the middleware to check every {uid} to ensure it is valid. IE the whole path might be /user/{uid} or /company/{uid} and the same middleware checks both. Is there another way?

tl8roy avatar Jun 06 '21 01:06 tl8roy

Using the examples you gave, you have many options: You can add the same middleware to multiple scopes. You can also merge these scopes into one: scope("/{domain:user|company}/{uid}") or, if you want more fine-grain contol you can add the middleware to every resource individually:

App::new().service(
    web::resource("/company/{uid}/say-hello")
        .wrap(MyWrapper::new())
        .to(say_hello_handler),
)

The rule still applies: "If you want to use a path parameter in a middleware, the parameter should've been defined before"

I hope this helped?

aliemjay avatar Jun 09 '21 16:06 aliemjay

Any update on this issue? I am trying to build a middleware that checks for path parameters named storage and repository

If they are available add them to the extensions. But currently. match_info is running this [api::authentication::middleware] Trace: Path { path: Url { uri: /api/admin/repositories/public, path: None }, skip: 4, segments: []}

wyatt-herkamp avatar Jun 01 '22 12:06 wyatt-herkamp