defmodule ReaderACLTest do
  use CouchTestCase

  @moduletag :authentication

  @users_db_name "custom-users"
  @password "funnybone"

  @moduletag config: [
               {
                 "chttpd_auth",
                 "authentication_db",
                 @users_db_name
               },
               {
                 "couch_httpd_auth",
                 "authentication_db",
                 @users_db_name
               }
             ]
  setup do
    reset_db(@users_db_name)

    # create a user with top-secret-clearance
    user_doc =
      prepare_user_doc([
        {:name, "bond@apache.org"},
        {:password, @password},
        {:roles, ["top-secret"]}
      ])

    {:ok, _} = create_doc(@users_db_name, user_doc)

    # create a user with top-secret-clearance
    user_doc =
      prepare_user_doc([
        {:name, "juanjo@apache.org"},
        {:password, @password}
      ])

    {:ok, _} = create_doc(@users_db_name, user_doc)

    on_exit(&tear_down/0)

    :ok
  end

  defp tear_down do
    delete_db(@users_db_name)
  end

  defp login(user, password) do
    sess = Couch.login(user, password)
    assert sess.cookie, "Login correct is expected"
    sess
  end

  defp logout(session) do
    assert Couch.Session.logout(session).body["ok"]
  end

  defp open_as(db_name, doc_id, options) do
    use_session = Keyword.get(options, :use_session)
    user = Keyword.get(options, :user)
    expect_response = Keyword.get(options, :expect_response, 200)
    expect_message = Keyword.get(options, :error_message)

    session = use_session || login(user, @password)

    resp =
      Couch.Session.get(
        session,
        "/#{db_name}/#{URI.encode(doc_id)}"
      )

    if use_session == nil do
      logout(session)
    end

    assert resp.status_code == expect_response

    if expect_message != nil do
      assert resp.body["error"] == expect_message
    end

    resp.body
  end

  defp set_security(db_name, security, expect_response \\ 200) do
    resp = Couch.put("/#{db_name}/_security", body: security)
    assert resp.status_code == expect_response
  end

  @tag :with_db
  test "unrestricted db can be read", context do
    db_name = context[:db_name]

    doc = %{_id: "baz", foo: "bar"}
    {:ok, _} = create_doc(db_name, doc)

    # any user can read unrestricted db
    open_as(db_name, "baz", user: "juanjo@apache.org")
    open_as(db_name, "baz", user: "bond@apache.org")
  end

  @tag :with_db
  test "restricted db can be read by authorized users", context do
    db_name = context[:db_name]

    doc = %{_id: "baz", foo: "bar"}
    {:ok, _} = create_doc(db_name, doc)

    security = %{
      members: %{
        roles: ["super-secret-club"],
        names: ["joe", "barb"]
      }
    }

    set_security(db_name, security)

    # can't read it as bond is missing the needed role
    open_as(db_name, "baz", user: "bond@apache.org", expect_response: 403)

    # make anyone with the top-secret role an admin
    # db admins are automatically members
    security = %{
      admins: %{
        roles: ["top-secret"],
        names: []
      },
      members: %{
        roles: ["super-secret-club"],
        names: ["joe", "barb"]
      }
    }

    set_security(db_name, security)

    # db admin can read
    open_as(db_name, "baz", user: "bond@apache.org")

    # admin now adds the top-secret role to the db's members
    # and removes db-admins
    security = %{
      admins: %{
        roles: [],
        names: []
      },
      members: %{
        roles: ["super-secret-club", "top-secret"],
        names: ["joe", "barb"]
      }
    }

    set_security(db_name, security)

    # server _admin can always read
    resp = Couch.get("/#{db_name}/baz")
    assert resp.status_code == 200

    open_as(db_name, "baz", user: "bond@apache.org")
  end

  @tag :with_db
  test "works with readers (backwards compat with 1.0)", context do
    db_name = context[:db_name]

    doc = %{_id: "baz", foo: "bar"}
    {:ok, _} = create_doc(db_name, doc)

    security = %{
      admins: %{
        roles: [],
        names: []
      },
      readers: %{
        roles: ["super-secret-club", "top-secret"],
        names: ["joe", "barb"]
      }
    }

    set_security(db_name, security)
    open_as(db_name, "baz", user: "bond@apache.org")
  end

  @tag :with_db
  test "can't set non string reader names or roles", context do
    db_name = context[:db_name]

    security = %{
      members: %{
        roles: ["super-secret-club", %{"top-secret": "awesome"}],
        names: ["joe", "barb"]
      }
    }

    set_security(db_name, security, 500)

    security = %{
      members: %{
        roles: ["super-secret-club", "top-secret"],
        names: ["joe", 22]
      }
    }

    set_security(db_name, security, 500)

    security = %{
      members: %{
        roles: ["super-secret-club", "top-secret"],
        names: "joe"
      }
    }

    set_security(db_name, security, 500)
  end

  @tag :with_db
  test "members can query views", context do
    db_name = context[:db_name]

    doc = %{_id: "baz", foo: "bar"}
    {:ok, _} = create_doc(db_name, doc)

    security = %{
      admins: %{
        roles: [],
        names: []
      },
      members: %{
        roles: ["super-secret-club", "top-secret"],
        names: ["joe", "barb"]
      }
    }

    set_security(db_name, security)

    view = %{
      _id: "_design/foo",
      views: %{
        bar: %{
          map: "function(doc){emit(null, null)}"
        }
      }
    }

    {:ok, _} = create_doc(db_name, view)

    # members can query views
    open_as(db_name, "_design/foo/_view/bar", user: "bond@apache.org")
  end
end
