- `/api/v1/notifications/dismiss`
- `/api/v1/search`
- `/api/v1/statuses/{id}/card`
+- LDAP authenticator
## 2022.07
"~",
"about",
"activities",
+ "akkoma",
"api",
"auth",
"check_password",
extra: true,
validate_tld: :no_scheme
-config :pleroma, :ldap,
- enabled: System.get_env("LDAP_ENABLED") == "true",
- host: System.get_env("LDAP_HOST") || "localhost",
- port: String.to_integer(System.get_env("LDAP_PORT") || "389"),
- ssl: System.get_env("LDAP_SSL") == "true",
- sslopts: [],
- tls: System.get_env("LDAP_TLS") == "true",
- tlsopts: [],
- base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
- uid: System.get_env("LDAP_UID") || "cn"
-
oauth_consumer_strategies =
"OAUTH_CONSUMER_STRATEGIES"
|> System.get_env()
}
]
},
- %{
- group: :pleroma,
- key: :ldap,
- label: "LDAP",
- type: :group,
- description:
- "Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <>
- " will be verified by trying to authenticate (bind) to a LDAP server." <>
- " If a user exists in the LDAP directory but there is no account with the same name yet on the" <>
- " Pleroma instance then a new Pleroma account will be created with the same name as the LDAP user name.",
- children: [
- %{
- key: :enabled,
- type: :boolean,
- description: "Enables LDAP authentication"
- },
- %{
- key: :host,
- type: :string,
- description: "LDAP server hostname",
- suggestions: ["localhosts"]
- },
- %{
- key: :port,
- type: :integer,
- description: "LDAP port, e.g. 389 or 636",
- suggestions: [389, 636]
- },
- %{
- key: :ssl,
- label: "SSL",
- type: :boolean,
- description: "Enable to use SSL, usually implies the port 636"
- },
- %{
- key: :sslopts,
- label: "SSL options",
- type: :keyword,
- description: "Additional SSL options",
- suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer],
- children: [
- %{
- key: :cacertfile,
- type: :string,
- description: "Path to file with PEM encoded cacerts",
- suggestions: ["path/to/file/with/PEM/cacerts"]
- },
- %{
- key: :verify,
- type: :atom,
- description: "Type of cert verification",
- suggestions: [:verify_peer]
- }
- ]
- },
- %{
- key: :tls,
- label: "TLS",
- type: :boolean,
- description: "Enable to use STARTTLS, usually implies the port 389"
- },
- %{
- key: :tlsopts,
- label: "TLS options",
- type: :keyword,
- description: "Additional TLS options",
- suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer],
- children: [
- %{
- key: :cacertfile,
- type: :string,
- description: "Path to file with PEM encoded cacerts",
- suggestions: ["path/to/file/with/PEM/cacerts"]
- },
- %{
- key: :verify,
- type: :atom,
- description: "Type of cert verification",
- suggestions: [:verify_peer]
- }
- ]
- },
- %{
- key: :base,
- type: :string,
- description: "LDAP base, e.g. \"dc=example,dc=com\"",
- suggestions: ["dc=example,dc=com"]
- },
- %{
- key: :uid,
- label: "UID",
- type: :string,
- description:
- "LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
- suggestions: ["cn"]
- }
- ]
- },
%{
group: :pleroma,
key: :auth,
### Pleroma.Web.Auth.Authenticator
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator.
-* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication.
-
-### :ldap
-
-Use LDAP for user authentication. When a user logs in to the Akkoma
-instance, the name and password will be verified by trying to authenticate
-(bind) to an LDAP server. If a user exists in the LDAP directory but there
-is no account with the same name yet on the Akkoma instance then a new
-Akkoma account will be created with the same name as the LDAP user name.
-
-* `enabled`: enables LDAP authentication
-* `host`: LDAP server hostname
-* `port`: LDAP port, e.g. 389 or 636
-* `ssl`: true to use SSL, usually implies the port 636
-* `sslopts`: additional SSL options
-* `tls`: true to start TLS, usually implies the port 389
-* `tlsopts`: additional TLS options
-* `base`: LDAP base, e.g. "dc=example,dc=com"
-* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
-
-Note, if your LDAP server is an Active Directory server the correct value is commonly `uid: "cn"`, but if you use an
-OpenLDAP server the value may be `uid: "uid"`.
### :oauth2 (Akkoma as OAuth 2.0 provider settings)
doas apk add erlang elixir
```
-* Install `erlang-eldap` if you want to enable ldap authenticator
-
-```shell
-doas apk add erlang-eldap
-```
-
### Install PostgreSQL
* Install Postgresql server:
@spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def force_password_reset(user), do: update_password_reset_pending(user, true)
- # Used to auto-register LDAP accounts which won't have a password hash stored locally
- def register_changeset_ldap(struct, params = %{password: password})
- when is_nil(password) do
- params = Map.put_new(params, :accepts_chat_messages, true)
-
- params =
- if Map.has_key?(params, :email) do
- Map.put_new(params, :email, params[:email])
- else
- params
- end
-
- struct
- |> cast(params, [
- :name,
- :nickname,
- :email,
- :accepts_chat_messages
- ])
- |> validate_required([:name, :nickname])
- |> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
- |> validate_format(:nickname, local_nickname_regex())
- |> put_ap_id()
- |> unique_constraint(:ap_id)
- |> put_following_and_follower_and_featured_address()
- end
-
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
+++ /dev/null
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Auth.LDAPAuthenticator do
- alias Pleroma.User
-
- require Logger
-
- import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
-
- @behaviour Pleroma.Web.Auth.Authenticator
- @base Pleroma.Web.Auth.PleromaAuthenticator
-
- @connection_timeout 10_000
- @search_timeout 10_000
-
- defdelegate get_registration(conn), to: @base
- defdelegate create_from_registration(conn, registration), to: @base
- defdelegate handle_error(conn, error), to: @base
- defdelegate auth_template, to: @base
- defdelegate oauth_consumer_template, to: @base
-
- def get_user(%Plug.Conn{} = conn) do
- with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])},
- {:ok, {name, password}} <- fetch_credentials(conn),
- %User{} = user <- ldap_user(name, password) do
- {:ok, user}
- else
- {:ldap, _} ->
- @base.get_user(conn)
-
- error ->
- error
- end
- end
-
- 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 fetch_user(name) do
- %User{} = user ->
- user
-
- _ ->
- register_user(connection, base, uid, name)
- end
-
- error ->
- error
- end
- end
-
- defp register_user(connection, base, uid, name) do
- case :eldap.search(connection, [
- {:base, to_charlist(base)},
- {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
- {:scope, :eldap.wholeSubtree()},
- {:timeout, @search_timeout}
- ]) do
- {:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} ->
- params = %{
- name: name,
- nickname: name,
- password: nil
- }
-
- params =
- case List.keyfind(attributes, 'mail', 0) do
- {_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
- _ -> params
- end
-
- changeset = User.register_changeset_ldap(%User{}, params)
-
- case User.register(changeset) do
- {:ok, user} -> user
- error -> error
- end
-
- error ->
- error
- end
- end
-end
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors()],
- xref: [exclude: [:eldap]],
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps(),
releases: [
pleroma: [
include_executables_for: [:unix],
- applications: [ex_syslogger: :load, syslog: :load, eldap: :transient],
+ applications: [ex_syslogger: :load, syslog: :load],
steps: [:assemble, &put_otp_version/1, ©_files/1, ©_nginx_config/1],
config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}]
]
+++ /dev/null
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 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 Mock
-
- @skip if !Code.ensure_loaded?(:eldap), do: :skip
-
- setup_all do: clear_config([:ldap, :enabled], true)
-
- setup_all do: clear_config(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator)
-
- @tag @skip
- test "authorizes the existing user using LDAP credentials" do
- password = "testpassword"
- user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(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, '', []}], []}}
- 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 "disallow authorization for wrong LDAP credentials" do
- password = "testpassword"
- user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(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