# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
  use Pleroma.Web.ConnCase
  alias Pleroma.Repo
  alias Pleroma.Web.OAuth.Token
  import Pleroma.Factory
  import ExUnit.CaptureLog
  import Mock

  @skip if !Code.ensure_loaded?(:eldap), do: :skip

  setup_all do
    ldap_authenticator =
      Pleroma.Config.get(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator)

    ldap_enabled = Pleroma.Config.get([:ldap, :enabled])

    on_exit(fn ->
      Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, ldap_authenticator)
      Pleroma.Config.put([:ldap, :enabled], ldap_enabled)
    end)

    Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator)
    Pleroma.Config.put([:ldap, :enabled], true)

    :ok
  end

  @tag @skip
  test "authorizes the existing user using LDAP credentials" do
    password = "testpassword"
    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
    app = insert(:oauth_app, scopes: ["read", "write"])

    host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
    port = Pleroma.Config.get([:ldap, :port])

    with_mocks [
      {:eldap, [],
       [
         open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
         simple_bind: fn _connection, _dn, ^password -> :ok end,
         close: fn _connection ->
           send(self(), :close_connection)
           :ok
         end
       ]}
    ] do
      conn =
        build_conn()
        |> post("/oauth/token", %{
          "grant_type" => "password",
          "username" => user.nickname,
          "password" => password,
          "client_id" => app.client_id,
          "client_secret" => app.client_secret
        })

      assert %{"access_token" => token} = json_response(conn, 200)

      token = Repo.get_by(Token, token: token)

      assert token.user_id == user.id
      assert_received :close_connection
    end
  end

  @tag @skip
  test "creates a new user after successful LDAP authorization" do
    password = "testpassword"
    user = build(:user)
    app = insert(:oauth_app, scopes: ["read", "write"])

    host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
    port = Pleroma.Config.get([:ldap, :port])

    with_mocks [
      {:eldap, [],
       [
         open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
         simple_bind: fn _connection, _dn, ^password -> :ok end,
         equalityMatch: fn _type, _value -> :ok end,
         wholeSubtree: fn -> :ok end,
         search: fn _connection, _options ->
           {:ok,
            {:eldap_search_result, [{:eldap_entry, '', [{'mail', [to_charlist(user.email)]}]}],
             []}}
         end,
         close: fn _connection ->
           send(self(), :close_connection)
           :ok
         end
       ]}
    ] do
      conn =
        build_conn()
        |> post("/oauth/token", %{
          "grant_type" => "password",
          "username" => user.nickname,
          "password" => password,
          "client_id" => app.client_id,
          "client_secret" => app.client_secret
        })

      assert %{"access_token" => token} = json_response(conn, 200)

      token = Repo.get_by(Token, token: token) |> Repo.preload(:user)

      assert token.user.nickname == user.nickname
      assert_received :close_connection
    end
  end

  @tag @skip
  test "falls back to the default authorization when LDAP is unavailable" do
    password = "testpassword"
    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
    app = insert(:oauth_app, scopes: ["read", "write"])

    host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
    port = Pleroma.Config.get([:ldap, :port])

    with_mocks [
      {:eldap, [],
       [
         open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:error, 'connect failed'} end,
         simple_bind: fn _connection, _dn, ^password -> :ok end,
         close: fn _connection ->
           send(self(), :close_connection)
           :ok
         end
       ]}
    ] do
      log =
        capture_log(fn ->
          conn =
            build_conn()
            |> post("/oauth/token", %{
              "grant_type" => "password",
              "username" => user.nickname,
              "password" => password,
              "client_id" => app.client_id,
              "client_secret" => app.client_secret
            })

          assert %{"access_token" => token} = json_response(conn, 200)

          token = Repo.get_by(Token, token: token)

          assert token.user_id == user.id
        end)

      assert log =~ "Could not open LDAP connection: 'connect failed'"
      refute_received :close_connection
    end
  end

  @tag @skip
  test "disallow authorization for wrong LDAP credentials" do
    password = "testpassword"
    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
    app = insert(:oauth_app, scopes: ["read", "write"])

    host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
    port = Pleroma.Config.get([:ldap, :port])

    with_mocks [
      {:eldap, [],
       [
         open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
         simple_bind: fn _connection, _dn, ^password -> {:error, :invalidCredentials} end,
         close: fn _connection ->
           send(self(), :close_connection)
           :ok
         end
       ]}
    ] do
      conn =
        build_conn()
        |> post("/oauth/token", %{
          "grant_type" => "password",
          "username" => user.nickname,
          "password" => password,
          "client_id" => app.client_id,
          "client_secret" => app.client_secret
        })

      assert %{"error" => "Invalid credentials"} = json_response(conn, 400)
      assert_received :close_connection
    end
  end
end
