diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 000000000..be497b531 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,187 @@ +variables: + - &scw-secrets + - SCW_ACCESS_KEY + - SCW_SECRET_KEY + - SCW_DEFAULT_ORGANIZATION_ID + - &setup-hex "mix local.hex --force && mix local.rebar --force" + - &on-release + when: + event: + - push + - tag + branch: + - develop + - stable + - refs/tags/v* + - refs/tags/stable-* + - &on-point-release + when: + event: + - push + branch: + - develop + - stable + - &on-pr-open + when: + event: + - pull_request + + - &tag-build "export BUILD_TAG=$${CI_COMMIT_TAG:-\"$CI_COMMIT_BRANCH\"} && export PLEROMA_BUILD_BRANCH=$BUILD_TAG" + + - &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)" + +services: + postgres: + image: postgres:13 + when: + event: + - pull_request + environment: + POSTGRES_DB: pleroma_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + +pipeline: + lint: + <<: *on-pr-open + image: akkoma/ci-base:latest + commands: + - mix local.hex --force + - mix local.rebar --force + - mix format --check-formatted + + build: + image: akkoma/ci-base:latest + <<: *on-pr-open + environment: + MIX_ENV: test + POSTGRES_DB: pleroma_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + DB_HOST: postgres + commands: + - mix local.hex --force + - mix local.rebar --force + - mix deps.get + - mix compile + + test: + image: akkoma/ci-base:latest + <<: *on-pr-open + environment: + MIX_ENV: test + POSTGRES_DB: pleroma_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + DB_HOST: postgres + commands: + - mix local.hex --force + - mix local.rebar --force + - mix deps.get + - mix compile + - mix ecto.drop -f -q + - mix ecto.create + - mix ecto.migrate + - mix test --preload-modules --exclude erratic --exclude federated --max-cases 4 + + # Canonical amd64 + ubuntu22: + image: hexpm/elixir:1.13.4-erlang-25.0.2-ubuntu-jammy-20220428 + <<: *on-release + environment: + MIX_ENV: prod + DEBIAN_FRONTEND: noninteractive + commands: + - rm config/emoji.txt + - apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git build-essential g++ wget + - *clean + - echo "import Config" > config/prod.secret.exs + - *setup-hex + - *tag-build + - mix deps.get --only prod + - mix release --path release + - zip akkoma-amd64.zip -r release + + release-ubuntu22: + image: akkoma/releaser + <<: *on-release + secrets: *scw-secrets + commands: + - export SOURCE=akkoma-amd64.zip + - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64.zip + - /bin/sh /entrypoint.sh + environment: + SOURCE: akkoma-amd64.zip + DEST: scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64.zip + + debian-bullseye: + image: elixir:1.13.4 + <<: *on-release + environment: + MIX_ENV: prod + DEBIAN_FRONTEND: noninteractive + commands: + - apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git build-essential gcc make g++ wget + - *clean + - echo "import Config" > config/prod.secret.exs + - *setup-hex + - *tag-build + - mix deps.get --only prod + - mix release --path release + - zip akkoma-amd64.zip -r release + + release-debian: + image: akkoma/releaser + <<: *on-release + secrets: *scw-secrets + commands: + - export SOURCE=akkoma-amd64.zip + - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-debian-bullseye.zip + - /bin/sh /entrypoint.sh + + # Canonical amd64-musl + musl: + image: elixir:1.13.4-alpine + <<: *on-release + environment: + MIX_ENV: prod + commands: + - apk add git gcc g++ musl-dev make cmake file-dev rclone wget zip imagemagick + - *clean + - *setup-hex + - mix deps.clean --all + - *tag-build + - mix deps.get --only prod + - mix release --path release + - zip akkoma-amd64-musl.zip -r release + + release-musl: + image: akkoma/releaser + <<: *on-release + secrets: *scw-secrets + commands: + - export SOURCE=akkoma-amd64-musl.zip + - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-musl.zip + - /bin/sh /entrypoint.sh + + docs: + <<: *on-point-release + secrets: + - SCW_ACCESS_KEY + - SCW_SECRET_KEY + - SCW_DEFAULT_ORGANIZATION_ID + environment: + CI: "true" + image: python:3.10-slim + commands: + - apt-get update && apt-get install -y rclone wget git zip + - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64 + - mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli + - chmod +x scaleway-cli + - ./scaleway-cli object config install type=rclone + - cd docs + - pip install -r requirements.txt + - mkdocs build + - zip -r docs.zip site/* + - cd site + - rclone copy . scaleway:akkoma-docs/$CI_COMMIT_BRANCH/ diff --git a/.woodpecker/.docs.yml b/.woodpecker/.docs.yml deleted file mode 100644 index 61b369e51..000000000 --- a/.woodpecker/.docs.yml +++ /dev/null @@ -1,27 +0,0 @@ -pipeline: - build: - when: - event: - - push - branch: - - develop - - stable - secrets: - - SCW_ACCESS_KEY - - SCW_SECRET_KEY - - SCW_DEFAULT_ORGANIZATION_ID - environment: - CI: "true" - image: python:3.10-slim - commands: - - apt-get update && apt-get install -y rclone wget git zip - - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64 - - mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli - - chmod +x scaleway-cli - - ./scaleway-cli object config install type=rclone - - cd docs - - pip install -r requirements.txt - - mkdocs build - - zip -r docs.zip site/* - - cd site - - rclone copy . scaleway:akkoma-docs/$CI_COMMIT_BRANCH/ diff --git a/.woodpecker/.release.yml b/.woodpecker/.release.yml deleted file mode 100644 index 535cdd65b..000000000 --- a/.woodpecker/.release.yml +++ /dev/null @@ -1,59 +0,0 @@ -variables: - - &scw-secrets - - SCW_ACCESS_KEY - - SCW_SECRET_KEY - - SCW_DEFAULT_ORGANIZATION_ID - - &setup-scw-s3 "wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64 && mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli && chmod +x scaleway-cli && ./scaleway-cli object config install type=rclone" - - - &setup-hex "mix local.hex --force && mix local.rebar --force" - - &build-on - when: - event: - - push - - tag - branch: - - develop - - stable - - refs/tags/v* - - refs/tags/stable-* - - &tag-build 'export BUILD_TAG=$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"} && export PLEROMA_BUILD_BRANCH=$BUILD_TAG' - - - &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix) && (rm scaleway-cli || true)" - - -pipeline: - glibc: - image: elixir:1.13 - <<: *build-on - secrets: *scw-secrets - environment: - MIX_ENV: prod - commands: - - apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git - - *clean - - *setup-scw-s3 - - echo "import Mix.Config" > config/prod.secret.exs - - *setup-hex - - *tag-build - - mix deps.get --only prod - - mix release --path release - - zip akkoma-amd64.zip -r release - - rclone copyto akkoma-amd64.zip scaleway:akkoma-updates/$BUILD_TAG/akkoma-amd64.zip - - musl: - image: elixir:1.13-alpine - <<: *build-on - secrets: *scw-secrets - environment: - MIX_ENV: prod - commands: - - apk add git gcc g++ musl-dev make cmake file-dev rclone wget zip imagemagick - - *clean - - *setup-scw-s3 - - *setup-hex - - mix deps.clean --all - - *tag-build - - mix deps.get --only prod - - mix release --path release - - zip akkoma-amd64.zip -r release - - rclone copyto akkoma-amd64.zip scaleway:akkoma-updates/$BUILD_TAG/akkoma-amd64-musl.zip diff --git a/.woodpecker/.test.yml b/.woodpecker/.test.yml deleted file mode 100644 index f41655029..000000000 --- a/.woodpecker/.test.yml +++ /dev/null @@ -1,59 +0,0 @@ -matrix: - ELIXIR_VERSION: - - 1.13 - -pipeline: - lint: - when: - event: - - pull_request - image: pleromaforkci/ci-base:1.13 - commands: - - mix local.hex --force - - mix local.rebar --force - - mix format --check-formatted - - build: - image: pleromaforkci/ci-base:${ELIXIR_VERSION} - when: - event: - - pull_request - environment: - MIX_ENV: test - commands: - - mix local.hex --force - - mix local.rebar --force - - mix deps.get - - mix compile - - test: - group: test - image: pleromaforkci/ci-base:${ELIXIR_VERSION} - when: - event: - - pull_request - environment: - MIX_ENV: test - POSTGRES_DB: pleroma_test - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - DB_HOST: postgres - commands: - - mix local.hex --force - - mix local.rebar --force - - mix deps.get - - mix ecto.drop -f -q - - mix ecto.create - - mix ecto.migrate - - mix test --preload-modules --exclude erratic --exclude federated --max-cases 4 - -services: - postgres: - image: postgres:13 - when: - event: - - pull_request - environment: - POSTGRES_DB: pleroma_test - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres diff --git a/CHANGELOG.md b/CHANGELOG.md index b75720f8d..98f434aaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `/api/v1/notifications/dismiss` - `/api/v1/search` - `/api/v1/statuses/{id}/card` -- LDAP authenticator (use the akkoma-contrib-authenticator-ldap runtime module) - Chats, they were half-baked. Just use PMs. - Prometheus, it causes massive slowdown diff --git a/Dockerfile b/Dockerfile index e6210affb..42ba9616b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM hexpm/elixir:1.13.4-erlang-24.3.4.2-alpine-3.16.0 as build +FROM elixir:1.13.4-alpine as build COPY . . diff --git a/config/config.exs b/config/config.exs index bac167c29..197887c93 100644 --- a/config/config.exs +++ b/config/config.exs @@ -509,7 +509,6 @@ "~", "about", "activities", - "akkoma", "api", "auth", "check_password", @@ -590,6 +589,17 @@ 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() diff --git a/config/description.exs b/config/description.exs index b8a053c3c..e864f090c 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2140,6 +2140,104 @@ } ] }, + %{ + 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, diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index fdc39c0de..bac20070f 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -891,6 +891,28 @@ Authentication / authorization settings. ### 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) diff --git a/docs/docs/installation/alpine_linux_en.md b/docs/docs/installation/alpine_linux_en.md index 3be69af6e..f98998fb8 100644 --- a/docs/docs/installation/alpine_linux_en.md +++ b/docs/docs/installation/alpine_linux_en.md @@ -41,6 +41,12 @@ doas apk add git build-base cmake file-dev 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: diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 275ad9506..2a1b5af94 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -660,6 +660,31 @@ def force_password_reset_async(user) do @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 = + if Map.has_key?(params, :email) do + Map.put_new(params, :email, params[:email]) + else + params + end + + struct + |> cast(params, [ + :name, + :nickname, + :email + ]) + |> 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) diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex new file mode 100644 index 000000000..f77e8d203 --- /dev/null +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -0,0 +1,129 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# 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 diff --git a/mix.exs b/mix.exs index ff54c79b4..71384c755 100644 --- a/mix.exs +++ b/mix.exs @@ -9,6 +9,7 @@ def project do 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(), diff --git a/rel/files/bin/pleroma_ctl b/rel/files/bin/pleroma_ctl index 5aebda2fb..176287fcb 100755 --- a/rel/files/bin/pleroma_ctl +++ b/rel/files/bin/pleroma_ctl @@ -2,6 +2,7 @@ # XXX: This should be removed when elixir's releases get custom command support detect_flavour() { + echo "Trying to autodetect flavour, you may want to override this with --flavour" arch="$(uname -m)" if [ "$arch" = "x86_64" ]; then arch="amd64" @@ -101,6 +102,7 @@ update() { echo "Restoring erlang cookie" echo $erlang_cookie > $RELEASE_ROOT/releases/COOKIE echo "Done! Please refer to the changelog/release notes for changes and update instructions" + echo "You probably also want to update your frontend!" set +e } diff --git a/test/pleroma/web/o_auth/ldap_authorization_test.exs b/test/pleroma/web/o_auth/ldap_authorization_test.exs new file mode 100644 index 000000000..61b9ce6b7 --- /dev/null +++ b/test/pleroma/web/o_auth/ldap_authorization_test.exs @@ -0,0 +1,135 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# 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