Introduce support for 3rd-party auth (OAuth2 & LDAP)

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-06-27 19:12:45 +02:00
parent 59a538feba
commit 9a080c1f10
48 changed files with 1380 additions and 240 deletions

View File

@@ -0,0 +1,34 @@
defmodule Mobilizon.Service.Auth.AuthenticatorTest do
use Mobilizon.DataCase
alias Mobilizon.Service.Auth.Authenticator
alias Mobilizon.Users
alias Mobilizon.Users.User
import Mobilizon.Factory
@email "email@domain.tld"
@password "password"
describe "test authentification" do
test "authenticate/1 checks the user's password" do
{:ok, %User{} = user} = Users.register(%{email: @email, password: @password})
Users.update_user(user, %{confirmed_at: DateTime.utc_now()})
assert {:ok, _} = Authenticator.authenticate(@email, @password)
assert {:error, :bad_password} ==
Authenticator.authenticate(@email, "completely wrong password")
end
end
describe "fetch_user/1" do
test "returns user by email" do
user = insert(:user)
assert Authenticator.fetch_user(user.email).id == user.id
end
test "returns nil" do
assert Authenticator.fetch_user("email") == {:error, :user_not_found}
end
end
end

View File

@@ -0,0 +1,238 @@
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

View File

@@ -0,0 +1,29 @@
defmodule Mobilizon.Service.Auth.MobilizonAuthenticatorTest do
use Mobilizon.DataCase
alias Mobilizon.Service.Auth.MobilizonAuthenticator
alias Mobilizon.Users.User
import Mobilizon.Factory
setup do
password = "testpassword"
email = "someone@somewhere.tld"
user = insert(:user, email: email, password_hash: Argon2.hash_pwd_salt(password))
{:ok, [user: user, email: email, password: password]}
end
test "login", %{email: email, password: password, user: user} do
assert {:ok, %User{} = returned_user} = MobilizonAuthenticator.login(email, password)
assert returned_user.id == user.id
end
test "login with invalid password", %{email: email} do
assert {:error, :bad_password} == MobilizonAuthenticator.login(email, "invalid")
assert {:error, :bad_password} == MobilizonAuthenticator.login(email, nil)
end
test "login with no credentials" do
assert {:error, :user_not_found} == MobilizonAuthenticator.login("some@email.com", nil)
assert {:error, :user_not_found} == MobilizonAuthenticator.login(nil, nil)
end
end