Prepare for ubuntu22 murdering openssl (#120)
authorfloatingghost <hannah@coffee-and-dreams.uk>
Wed, 27 Jul 2022 21:48:13 +0000 (21:48 +0000)
committerfloatingghost <hannah@coffee-and-dreams.uk>
Wed, 27 Jul 2022 21:48:13 +0000 (21:48 +0000)
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/120

15 files changed:
.woodpecker.yml [new file with mode: 0644]
.woodpecker/.docs.yml [deleted file]
.woodpecker/.release.yml [deleted file]
.woodpecker/.test.yml [deleted file]
CHANGELOG.md
Dockerfile
config/config.exs
config/description.exs
docs/docs/configuration/cheatsheet.md
docs/docs/installation/alpine_linux_en.md
lib/pleroma/user.ex
lib/pleroma/web/auth/ldap_authenticator.ex [new file with mode: 0644]
mix.exs
rel/files/bin/pleroma_ctl
test/pleroma/web/o_auth/ldap_authorization_test.exs [new file with mode: 0644]

diff --git a/.woodpecker.yml b/.woodpecker.yml
new file mode 100644 (file)
index 0000000..be497b5
--- /dev/null
@@ -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 (file)
index 61b369e..0000000
+++ /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 (file)
index 535cdd6..0000000
+++ /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 (file)
index f416550..0000000
+++ /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
index b75720f8d96eff5a49cdea47edef67bc8eca589b..98f434aaa00353532033328f0f5ec88a6dea3ff7 100644 (file)
@@ -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
 
index e6210affbf1600f6a2f00c1b0a408adfb308d3bf..42ba9616bb79a2f1feb3fabf50443cecff1d5e4d 100644 (file)
@@ -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 . .
 
index bac167c29af4dbd02c3bb008ff58318e95a8965a..197887c9378dc53eb74b4080946ee35601489c9f 100644 (file)
@@ -509,7 +509,6 @@ config :pleroma, Pleroma.User,
     "~",
     "about",
     "activities",
-    "akkoma",
     "api",
     "auth",
     "check_password",
@@ -590,6 +589,17 @@ config :pleroma, Pleroma.Formatter,
   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()
index b8a053c3c3c0684fcb40f777df555c97506a6d9b..e864f090c2db78b46157a334c6d307bc84c55bcd 100644 (file)
@@ -2140,6 +2140,104 @@ config :pleroma, :config_description, [
       }
     ]
   },
+  %{
+    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,
index fdc39c0de796829b1ad8f4c75fab10ba00de8f82..bac20070f3c78d1403dc4c7548cfaf7b55d0e69b 100644 (file)
@@ -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)
 
index 3be69af6ef9b8c45d9adb19c611b7c7daaca5042..f98998fb8cdf828594fc83cf6289eefd63210f44 100644 (file)
@@ -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:
index 275ad950622de860cc267e3938bb1bd3bbb6a997..2a1b5af94dad752970e084e1d63940bbfae22ec1 100644 (file)
@@ -660,6 +660,31 @@ defmodule Pleroma.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 (file)
index 0000000..f77e8d2
--- /dev/null
@@ -0,0 +1,129 @@
+# 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
diff --git a/mix.exs b/mix.exs
index ff54c79b4fc08e10297a5bb3d7d8817292114116..71384c755d3ba283ad48969f3292deb5a79f1591 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -9,6 +9,7 @@ defmodule Pleroma.Mixfile 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(),
index 5aebda2fb63ad587c52b890760021eb63e064030..176287fcb6f33f9ad573a8d7d09a5717b3bc68c7 100755 (executable)
@@ -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 (file)
index 0000000..61b9ce6
--- /dev/null
@@ -0,0 +1,135 @@
+# 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