defmodule Mobilizon.Service.Auth.LDAPAuthenticatorTest do use Mobilizon.Web.ConnCase use Mobilizon.Tests.Helpers alias Mobilizon.GraphQL.AbsintheHelpers alias Mobilizon.Service.Auth.{Authenticator, LDAPAuthenticator} alias Mobilizon.Users.User alias Mobilizon.Web.Auth.Guardian import Mobilizon.Factory import ExUnit.CaptureLog import Mock @skip if !Code.ensure_loaded?(:eldap), do: :skip @admin_password "admin_password" setup_all do clear_config([:ldap, :enabled], true) clear_config([:ldap, :bind_uid], "admin") clear_config([:ldap, :bind_password], @admin_password) end setup_all do: clear_config( Authenticator, LDAPAuthenticator ) @login_mutation """ mutation Login($email: String!, $password: String!) { login(email: $email, password: $password) { accessToken, refreshToken, user { id } } } """ describe "login" do @tag @skip test "authorizes the existing user using LDAP credentials", %{conn: conn} do user_password = "testpassword" admin_password = "admin_password" user = insert(:user, password_hash: Argon2.hash_pwd_salt(user_password)) host = [:ldap, :host] |> Mobilizon.Config.get() |> to_charlist port = Mobilizon.Config.get([:ldap, :port]) with_mocks [ {:eldap, [], [ open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, simple_bind: fn _connection, _dn, password -> case password do ^admin_password -> :ok ^user_password -> :ok end end, equalityMatch: fn _type, _value -> :ok end, wholeSubtree: fn -> :ok end, search: fn _connection, _options -> {:ok, {:eldap_search_result, [{:eldap_entry, '', [{'cn', [to_charlist("MyUser")]}]}], [], []}} end, close: fn _connection -> send(self(), :close_connection) :ok end ]} ] do res = conn |> AbsintheHelpers.graphql_query( query: @login_mutation, variables: %{email: user.email, password: user_password} ) assert is_nil(res["error"]) assert token = res["data"]["login"]["accessToken"] {:ok, %User{} = user_from_token, _claims} = Guardian.resource_from_token(token) assert user_from_token.id == user.id assert_received :close_connection end end @tag @skip test "creates a new user after successful LDAP authorization", %{conn: conn} do user_password = "testpassword" admin_password = "admin_password" user = build(:user) host = [:ldap, :host] |> Mobilizon.Config.get() |> to_charlist port = Mobilizon.Config.get([:ldap, :port]) with_mocks [ {:eldap, [], [ open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, simple_bind: fn _connection, _dn, password -> case password do ^admin_password -> :ok ^user_password -> :ok end end, equalityMatch: fn _type, _value -> :ok end, wholeSubtree: fn -> :ok end, search: fn _connection, _options -> {:ok, {:eldap_search_result, [{:eldap_entry, '', [{'cn', [to_charlist("MyUser")]}]}], [], []}} end, close: fn _connection -> send(self(), :close_connection) :ok end ]} ] do res = conn |> AbsintheHelpers.graphql_query( query: @login_mutation, variables: %{email: user.email, password: user_password} ) assert is_nil(res["error"]) assert token = res["data"]["login"]["accessToken"] {:ok, %User{} = user_from_token, _claims} = Guardian.resource_from_token(token) assert user_from_token.email == user.email assert_received :close_connection end end @tag @skip test "falls back to the default authorization when LDAP is unavailable", %{conn: conn} do user_password = "testpassword" admin_password = "admin_password" user = insert(:user, password_hash: Argon2.hash_pwd_salt(user_password)) host = [:ldap, :host] |> Mobilizon.Config.get() |> to_charlist port = Mobilizon.Config.get([:ldap, :port]) with_mocks [ {:eldap, [], [ open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:error, 'connect failed'} end, simple_bind: fn _connection, _dn, password -> case password do ^admin_password -> :ok ^user_password -> :ok end end, equalityMatch: fn _type, _value -> :ok end, wholeSubtree: fn -> :ok end, search: fn _connection, _options -> {:ok, {:eldap_search_result, [{:eldap_entry, '', [{'cn', [to_charlist("MyUser")]}]}], []}} end, close: fn _connection -> send(self(), :close_connection) :ok end ]} ] do log = capture_log(fn -> res = conn |> AbsintheHelpers.graphql_query( query: @login_mutation, variables: %{email: user.email, password: user_password} ) assert is_nil(res["error"]) assert token = res["data"]["login"]["accessToken"] {:ok, %User{} = user_from_token, _claims} = Guardian.resource_from_token(token) assert user_from_token.email == user.email 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", %{conn: conn} do user_password = "testpassword" user = insert(:user, password_hash: Argon2.hash_pwd_salt(user_password)) host = [:ldap, :host] |> Mobilizon.Config.get() |> to_charlist port = Mobilizon.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 res = conn |> AbsintheHelpers.graphql_query( query: @login_mutation, variables: %{email: user.email, password: user_password} ) refute is_nil(res["errors"]) assert assert hd(res["errors"])["message"] == "Impossible to authenticate, either your email or password are invalid." assert_received :close_connection end end end describe "can change" do test "password" do assert LDAPAuthenticator.can_change_password?(%User{provider: "ldap"}) == false assert LDAPAuthenticator.can_change_password?(%User{provider: nil}) == true end test "email" do assert LDAPAuthenticator.can_change_password?(%User{provider: "ldap"}) == false assert LDAPAuthenticator.can_change_password?(%User{provider: nil}) == true end end end