[#923] Merge remote-tracking branch 'remotes/upstream/develop' into twitter_oauth
# Conflicts: # config/config.exs # lib/pleroma/web/auth/pleroma_authenticator.exstable
commit
2a96283efb
@ -0,0 +1,145 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
||||
alias Pleroma.User
|
||||
|
||||
require Logger
|
||||
|
||||
@behaviour Pleroma.Web.Auth.Authenticator
|
||||
|
||||
@connection_timeout 10_000
|
||||
@search_timeout 10_000
|
||||
|
||||
def get_user(%Plug.Conn{} = conn, params) do
|
||||
if Pleroma.Config.get([:ldap, :enabled]) do
|
||||
{name, password} =
|
||||
case params do
|
||||
%{"authorization" => %{"name" => name, "password" => password}} ->
|
||||
{name, password}
|
||||
|
||||
%{"grant_type" => "password", "username" => name, "password" => password} ->
|
||||
{name, password}
|
||||
end
|
||||
|
||||
case ldap_user(name, password) do
|
||||
%User{} = user ->
|
||||
{:ok, user}
|
||||
|
||||
{:error, {:ldap_connection_error, _}} ->
|
||||
# When LDAP is unavailable, try default authenticator
|
||||
Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn, params)
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
else
|
||||
# Fall back to default authenticator
|
||||
Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn, params)
|
||||
end
|
||||
end
|
||||
|
||||
def get_or_create_user_by_oauth(conn, params), do: get_user(conn, params)
|
||||
|
||||
def handle_error(%Plug.Conn{} = _conn, error) do
|
||||
error
|
||||
end
|
||||
|
||||
def auth_template, do: nil
|
||||
|
||||
defp ldap_user(name, password) do
|
||||
ldap = Pleroma.Config.get(:ldap, [])
|
||||
host = Keyword.get(ldap, :host, "localhost")
|
||||
port = Keyword.get(ldap, :port, 389)
|
||||
ssl = Keyword.get(ldap, :ssl, false)
|
||||
sslopts = Keyword.get(ldap, :sslopts, [])
|
||||
|
||||
options =
|
||||
[{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++
|
||||
if sslopts != [], do: [{:sslopts, sslopts}], else: []
|
||||
|
||||
case :eldap.open([to_charlist(host)], options) do
|
||||
{:ok, connection} ->
|
||||
try do
|
||||
if Keyword.get(ldap, :tls, false) do
|
||||
:application.ensure_all_started(:ssl)
|
||||
|
||||
case :eldap.start_tls(
|
||||
connection,
|
||||
Keyword.get(ldap, :tlsopts, []),
|
||||
@connection_timeout
|
||||
) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
error ->
|
||||
Logger.error("Could not start TLS: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
bind_user(connection, ldap, name, password)
|
||||
after
|
||||
:eldap.close(connection)
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("Could not open LDAP connection: #{inspect(error)}")
|
||||
{:error, {:ldap_connection_error, error}}
|
||||
end
|
||||
end
|
||||
|
||||
defp bind_user(connection, ldap, name, password) do
|
||||
uid = Keyword.get(ldap, :uid, "cn")
|
||||
base = Keyword.get(ldap, :base)
|
||||
|
||||
case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do
|
||||
:ok ->
|
||||
case User.get_by_nickname_or_email(name) do
|
||||
%User{} = user ->
|
||||
user
|
||||
|
||||
_ ->
|
||||
register_user(connection, base, uid, name, password)
|
||||
end
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
defp register_user(connection, base, uid, name, password) do
|
||||
case :eldap.search(connection, [
|
||||
{:base, to_charlist(base)},
|
||||
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
|
||||
{:scope, :eldap.wholeSubtree()},
|
||||
{:attributes, ['mail', 'email']},
|
||||
{:timeout, @search_timeout}
|
||||
]) do
|
||||
{:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} ->
|
||||
with {_, [mail]} <- List.keyfind(attributes, 'mail', 0) do
|
||||
params = %{
|
||||
email: :erlang.list_to_binary(mail),
|
||||
name: name,
|
||||
nickname: name,
|
||||
password: password,
|
||||
password_confirmation: password
|
||||
}
|
||||
|
||||
changeset = User.register_changeset(%User{}, params)
|
||||
|
||||
case User.register(changeset) do
|
||||
{:ok, user} -> user
|
||||
error -> error
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
Logger.error("Could not find LDAP attribute mail: #{inspect(attributes)}")
|
||||
{:error, :ldap_registration_missing_attributes}
|
||||
end
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,189 @@
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
Loading…
Reference in new issue