如何测试控制器授权

How test controller authorization

我有一个类似于这个简化代码的控制器:

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

当我用休息客户端检查它时它 return 403 但在测试中它 returns 200。我如何测试它?

测试不错,你的代码不行。

你的 authorize 函数 return 一个 conn,但你从未在 index 函数上使用它。

当您使用休息客户端请求它时,连接正确接收

conn
|> send_resp(403, "")

但在 ExUnit 中,它得到的是 index returns : render(conn, "index.json", users: users)

因为你还没有使用 connauthorize(conn) returns

我建议快速解决这个问题:

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

更好的解决方案是制作一个用于授权目的的插件,将其添加到路由器的管道中,如果连接未授权,它将无法到达您的控制器。

既然 phoenix 支持一个叫做插件的好东西,为什么不让它们工作呢? 从多个角度来看,您的方法很糟糕:

  1. 您可能想在多个控制器中调用授权。
  2. 您总是必须在您的控制器中实现授权行为, 会使您的代码非常混乱。
  3. 诸如未授权或禁止之类的事情应该由回退控制器处理(它是为这样的提议而设计的)

我现在有一个类似的项目,我必须在其中实现用户角色,我的解决方案是使用自定义插件。 插件结构如下:

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

然后您可以通过在控制器中声明来非常轻松地将此插件用于多个端点:

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

这个解决方案非常简单,非常可扩展并且非常实用