rjurado01
rjurado01

Reputation: 5535

How test controller authorization

I have a controller similar to this simplified code:

defmodule Web.UserController do
  use Web, :controller

  alias App.User

  action_fallback Web.FallbackController

  def authorize(conn) do
    # in my code I have somo checks here

    conn
      |> send_resp(403, "")
      |> halt()
  end

  def index(conn, _params) do
    authorize(conn)

    users = User.all
    render(conn, "index.json", users: users)
  end
end
  test "lists all users", %{conn: conn} do
    conn = get(conn, Routes.user_path(conn, :index))
    users = User.all

    assert conn.halted
    assert json_response(conn, 403)
  end

When I check it with rest client it return 403 but in the test it returns 200. How I can test it?

Upvotes: 0

Views: 252

Answers (2)

Daniel
Daniel

Reputation: 2554

Since phoenix supports a wonderful thing called plugs, why not put them to work? Your approach is bad from multiple points of view:

  1. You might want to call authorize in multiple controllers.
  2. You always have to implement the behavior of authorize in your controller, witch makes your code very messy.
  3. Things such as unauthorized or forbidden should be handled by fallback controller (it was designed for such propose)

I have a similar project now where I have to implement user roles and the solution I came with is to use a custom plug. The structure of the plug is the following:

defmodule WebApp.Plugs.Roles do
  import Plug.Conn
  import Phoenix.Controller

  alias WebApp.ErrorView

  def init(default), do: default

  def call(conn, acl) do
    # check here for authorization here, in my case whether a user has a specific acl

    case :authorized do
      :not_authorized ->
        conn
        |> put_status(403)
        |> put_view(ErrorView)
        |> render(:"403")
        |> halt()
      _ -> conn
    end
  end

end

And then you can use this plug very easily for multiple endpoints by declaring in your controller :

plug Roles, "create_users" when action in [:create]

This solution is very easy, very scalable and ver

Upvotes: 1

TheSquad
TheSquad

Reputation: 7506

The test is good, your code is not.

your authorize function return a conn, but you never use it on the index function.

When you request it with a rest client, the connection receives correctly the

conn
|> send_resp(403, "")

But in ExUnit, it gets what index returns : render(conn, "index.json", users: users)

Since you haven't use the conn that authorize(conn) returns

My suggestion to fix this issue rapidly :

defmodule Web.UserController do
  use Web, :controller

  alias App.User

  action_fallback Web.FallbackController

  def authorize(conn) do
    # in my code I have somo checks here

    :not_authorized
  end

  def index(conn, _params) do
    case authorize(conn) do
      :not_authorized -> 
        conn
          |> send_resp(403, "")
          |> halt()      # not necessary since send_resp already does it
      :authorized ->
        users = User.all
        render(conn, "index.json", users: users)
    end
  end
end

A better solution would be to make a Plug for authorization purpose, add it to a pipeline in your router, it will not reach your controller if the connection is not authorized.

Upvotes: 1

Related Questions