Access matched Route from phoenix Conn

phoenix
router

#1

Given a Plug.Conn that went through a Router like this:

get("/foo/:bar", FooController, :get_foo)

For a request like GET /foo/42, I can use conn.request_path to acess the String /foo/42, but is there a way to get the /foo/:bar string that was matched in the Router ?


Adding a match_path function like in Plug
#2

No public API exists for that today. What would you like to do with the info?


#3

This is not pretty and perhaps not even an appropriate use for the module, however, it will return what you are looking for:

route = conn.request_path
        |> Phoenix.Router.Resource.build(YourWebApp.FooController, [])
route.member[:path]

#4

I’m trying to write a plug that does authentication based on which route was matched.

In my mind, the plug would load a CSV table to know which role is needed to access a given route.(I tried using macro inside the router, but it lacks the lisibility of a CSV table.)

method,route,role_1,role_2,....
Get, /foo, yes,yes,...
Get,/too/:foo_id,no,yes,...
...

Then, given a Conn , knowing which route and method was matched would give me enough info to check the right row in my table.

I was able to half bake something by using a regex in the CSV file, but ideally, accessing the route would make my life easier :slight_smile:


#5

I gave you a solution above :grinning:


#6

Sorry, i havn’t been able to test it yet, I’ll give it a shot and tell you if it worked !


#7

If you have not already explored this, you can write a plug and break up the routes in pipelines within your routes module. Such a plug could set a role to be used by your authentication module within a controller, or even within the route itself – depending on how you have your authentication system setup:

pipeline :auth_user do
  plug MyWebApp.Plug.Auth, :user
end

pipeline :auth_employee do
  plug MyWeb.Plug.Auth, :employee
end

scope "/", MyWeb do
  pipe_through [:browser, :auth_user]

  get "/login/user", AuthenticationController, :new
end

scope "/", MyWeb do
  pipe_through [:browser, :auth_employee]

  get "/login/employee", AuthenticationController, :new
end

So perhaps in your auth plug you would assign the role set within the defined pipelines (:user, :employee).

You could also do this in the controller at the new method level as well. The options are quite limitless :slight_smile:


#8

The problem is in my case, I have a “matrix” of roles and endpoint, and there is no “simple” mapping between endpoints and which roles have access to them.

If I followed your example, I would have an explosion of different plug configuration (potentially, a different configuration for each endpoint !!)

pipeline role_1 do 
  # Endpoints availble only to users with role "role_1"
  plug MyApp.Plug.Auth, roles: ["role_1"]  
end

pipeline role_2 do
  #Endpoints available only to users with role "role_2"
  plug MyApp.Plug.auth, roles: ["role_1"]
end

....

pipeline role_1_or_role_2 do
  #Endpoints available only to users with role "role1", or, "role_2", but not "role_3"
  plug MyApp.Plug.auth, roles: ["role_1", "role_3"]
end

pipeline role_1_or_role_3 do
  #Endpoints available only to users with role "role1", or, "role_3", but not "role_2"
  plug MyApp.Plug.auth, roles: ["role_1", "role_2"]
end

...

At some point, I used a custom set of get_xxx macros that made it possible to write something like:

get_with_role("/foor/bar", FooController, :get_bar, role_in: ["role_1", "role_2"])

It worked, but it clearly lacked the visibility of a table with one row for each endpoint, and one column for each role. (Which is also much easier to reason about with our PM - arguably, our domain and permission model is a bit complex, but that’s for - good - business reason.)


#9

I’m afraid I’m not going to use your solution, since I don’t have access to the XxxController that implemented the route inside a plug (do I ?)


#10

Yes. you should be able to use that in a plug no problem at all. In fact you’re not calling anything from a controller, you’re just passing the controller module name, which expands to an atom.


#11

What I mean is that i’m writing a Plug like this :

defmodule RolePlug do

  def call(conn, _) do

    # I can get the Router through which the Conn was passed, but I could not find how to get 
    # the Controller.

    route = conn.request_path
        |> Phoenix.Router.Resource.build(???????, [])
    route.member[:path]


  end 

end

#12

Oh, I get it. So, where are you trying to get the “/foo/:bar” info at in your app? At which point, in a plug, controller?

And, yeah, I do not think the controller name is passed in the conn :neutral_face: