I followed the guide at http://rny.io/elixir/phoenix/ldap/2016/09/20/ldap-authenication-with-phoenix.html to setup LDAP authentication with Guardian in Phoenix. I am fairly new to Phoenix and Elixir so I am going through the motions of setting things up and testing.
I have everything working as per the guide but, I cannot figure out how to get past Guardian.Plug.EnsureAuthenticated when writing controller tests. I have followed several guides found on here but nothing seems to be working.
Has anyone setup LDAP authentication with exLDAP and Guardian and the proper user logins for testing that can offer some guidance? Any help would be appreciated.
Below is my setup:
lib/ldap_example/guardian_serializer.ex
defmodule LdapExample.GuardianSerializer do
@behaviour Guardian.Serializer
alias LdapExample.User
alias LdapExample.Repo
def for_token(user = %User{}), do: { :ok, "User:#{user.id}" }
def for_token(_), do: { :error, "Unknown resource type" }
def from_token("User:" <> id), do: { :ok, Repo.get(User, id) }
def from_token(_), do: { :error, "Unknown resource type" }
end
lib/ldap_example/ldap.ex
defmodule LdapExample.Ldap do
def authenticate(uid, password) do
{:ok, ldap_conn} = Exldap.open
bind = "uid=#{uid},dc=example,dc=com"
case Exldap.verify_credentials(ldap_conn, bind, password) do
:ok -> :ok
_ -> {:error, "Invalid username / password"}
end
end
def get_by_uid(uid) do
{:ok, ldap_conn} = Exldap.connect
{:ok, search_results} = Exldap.search_field(ldap_conn, "uid", uid)
case search_results do
[] -> {:error, "Could not find user with uid #{uid}"}
_ -> search_results |> Enum.fetch(0)
end
end
def to_map(entry) do
username = Exldap.search_attributes(entry, "uid")
name = Exldap.search_attributes(entry, "cn")
email = Exldap.search_attributes(entry, "mail")
%{username: username, name: name, email: email}
end
end
web/controllers/session_controller.ex
defmodule LdapExample.SessionController do
use LdapExample.Web, :controller
alias LdapExample.{User, Repo, Ldap}
def new(conn, _params) do
render conn, "new.html", changeset: User.login_changeset
end
def create(conn, %{"user" => params}) do
username = params["username"]
password = params["password"]
case Ldap.authenticate(username, password) do
:ok -> handle_sign_in(conn, username)
_ -> handle_error(conn)
end
end
defp handle_sign_in(conn, username) do
{:ok, user} = insert_or_update_user(username)
conn
|> put_flash(:info, "Logged in.")
|> Guardian.Plug.sign_in(user)
|> redirect(to: page_path(conn, :index))
end
defp insert_or_update_user(username) do
{:ok, ldap_entry} = Ldap.get_by_uid(username)
user_attributes = Ldap.to_map(ldap_entry)
user = Repo.get_by(User, username: username)
changeset =
case user do
nil -> User.changeset(%User{}, user_attributes)
_ -> User.changeset(user, user_attributes)
end
Repo.insert_or_update changeset
end
defp handle_error(conn) do
conn
|> put_flash(:error, "Wrong username or password")
|> redirect(to: session_path(conn, :new))
end
def delete(conn, _params) do
Guardian.Plug.sign_out(conn)
|> put_flash(:info, "Logged out successfully.")
|> redirect(to: "/")
end
end
web/model/user.ex
defmodule LdapExample.User do
use LdapExample.Web, :model
schema "users" do
field :username, :string
field :name, :string
field :email, :string
field :password, :string, virtual: true
timestamps()
end
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:username, :name, :email])
|> validate_required([:username, :name, :email])
end
def login_changeset do
%__MODULE__{} |> cast(%{}, ~w(username password), ~w())
end
end
I created a session_controller_text.ex as follows:
defmodule LdapExample.SessionControllerTest do
use LdapExample.ConnCase
alias LdapExample.User
test "Get to Login page", %{conn: conn} do
conn = get conn, session_path(conn, :new)
assert html_response(conn, 200) =~ "Sign In"
end
test "shows page only when logged in", %{conn: conn} do
conn = get conn, page_path(conn, :index)
assert html_response(conn, 200) =~ "Hello"
end
end
The last test fails as it redirects me to the login page.
Certainly you should use
bypass_through
to skip the authentication process for your test purposes. Read more in docs.