Merge branch 'fix/mediaproxy-capture-content-disposition-filename' into 'develop'
authorkaniini <nenolod@gmail.com>
Fri, 15 Mar 2019 20:06:08 +0000 (20:06 +0000)
committerkaniini <nenolod@gmail.com>
Fri, 15 Mar 2019 20:06:08 +0000 (20:06 +0000)
MediaProxy: parse filename from content-disposition for non-whitelisted types

See merge request pleroma/pleroma!936

22 files changed:
config/config.exs
config/test.exs
docs/Custom-Emoji.md [new file with mode: 0644]
docs/Differences-in-MastodonAPI-Responses.md
docs/Message-Rewrite-Facility-configuration.md [new file with mode: 0644]
docs/config.md
docs/static_dir.md [new file with mode: 0644]
lib/mix/tasks/pleroma/robotstxt.ex [new file with mode: 0644]
lib/pleroma/plugs/instance_static.ex
lib/pleroma/web/auth/ldap_authenticator.ex [new file with mode: 0644]
lib/pleroma/web/auth/pleroma_authenticator.ex
lib/pleroma/web/endpoint.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/notification_view.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/oauth/oauth_controller.ex
lib/pleroma/web/streamer.ex
mix.exs
priv/static/robots.txt [new file with mode: 0644]
test/web/mastodon_api/notification_view_test.exs [new file with mode: 0644]
test/web/mastodon_api/status_view_test.exs
test/web/oauth/ldap_authorization_test.exs [new file with mode: 0644]

index f889e3259f4568384982a460afcbfd54f4aa5e4c..ccdd357771dc0057c0f8723eaa53a60895dbae2c 100644 (file)
@@ -34,7 +34,7 @@ config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.
 # Upload configuration
 config :pleroma, Pleroma.Upload,
   uploader: Pleroma.Uploaders.Local,
-  filters: [],
+  filters: [Pleroma.Upload.Filter.Dedupe],
   link_name: true,
   proxy_remote: false,
   proxy_opts: [
@@ -370,6 +370,17 @@ config :auto_linker,
     rel: false
   ]
 
+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"
+
 # Import environment specific config. This must remain at the bottom
 # of this file so it overrides the configuration defined above.
 import_config "#{Mix.env()}.exs"
index a3f36c9e169ff142ca00e14a588de81e8eb27712..3691e5bd1070af80c04b8bcf260adf47074b9d57 100644 (file)
@@ -17,7 +17,7 @@ config :pleroma, Pleroma.Captcha,
 # Print only warnings and errors during test
 config :logger, level: :warn
 
-config :pleroma, Pleroma.Upload, link_name: false
+config :pleroma, Pleroma.Upload, filters: [], link_name: false
 
 config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
 
diff --git a/docs/Custom-Emoji.md b/docs/Custom-Emoji.md
new file mode 100644 (file)
index 0000000..9d90e58
--- /dev/null
@@ -0,0 +1,18 @@
+# Custom emoji
+
+To add custom emoji:
+* Add the image file(s) to `priv/static/emoji/custom`
+* In case of conflicts: add the desired shortcode with the path to `config/custom_emoji.txt`, comma-separated and one per line
+* Force recompilation (``mix clean && mix compile``)
+
+Example:
+
+image files (in `/priv/static/emoji/custom`): `happy.png` and `sad.png`
+
+content of `config/custom_emoji.txt`:
+```
+happy, /emoji/custom/happy.png
+sad, /emoji/custom/sad.png
+```
+
+The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
index 7b11fe90f6d4ab843f094fde8db3349a1522a81a..621de66030fee31361fba9adaf86bdf1eba67b40 100644 (file)
@@ -20,6 +20,12 @@ Has these additional fields under the `pleroma` object:
 
 - `local`: true if the post was made on the local instance.
 
+## Attachments
+
+Has these additional fields under the `pleroma` object:
+
+- `mime_type`: mime type of the attachment.
+
 ## Accounts
 
 - `/api/v1/accounts/:id`: The `id` parameter can also be the `nickname` of the user. This only works in this endpoint, not the deeper nested ones for following etc.
diff --git a/docs/Message-Rewrite-Facility-configuration.md b/docs/Message-Rewrite-Facility-configuration.md
new file mode 100644 (file)
index 0000000..35ce52e
--- /dev/null
@@ -0,0 +1,119 @@
+# Message Rewrite Facility configuration
+The Message Rewrite Facility (MRF) is a subsystem that is implemented as a series of hooks that allows the administrator to rewrite or discard messages.
+
+Possible uses include:
+
+* marking incoming messages with media from a given account or instance as sensitive
+* rejecting messages from a specific instance
+* removing/unlisting messages from the public timelines
+* removing media from messages
+* sending only public messages to a specific instance
+
+The MRF provides user-configurable policies.  The default policy is `NoOpPolicy`, which disables the MRF functionality.  Pleroma also includes an easy to use policy called `SimplePolicy` which maps messages matching certain pre-defined criterion to actions built into the policy module.  
+It is possible to use multiple, active MRF policies at the same time.
+
+## Quarantine Instances
+
+You have the ability to prevent from private / followers-only messages from federating with specific instances. Which means they will only get the public or unlisted messages from your instance.
+
+If, for example, you're using `MIX_ENV=prod` aka using production mode, you would open your configuration file located in `config/prod.secret.exs` and edit or add the option under your `:instance` config object. Then you would specify the instance within quotes.
+```
+config :pleroma, :instance,
+  [...]
+  quarantined_instances: ["instance.example", "other.example"]
+```
+
+## Using `SimplePolicy`
+
+`SimplePolicy` is capable of handling most common admin tasks.
+
+To use `SimplePolicy`, you must enable it.  Do so by adding the following to your `:instance` config object, so that it looks like this:
+
+```
+config :pleroma, :instance,
+  [...]
+  rewrite_policy: Pleroma.Web.ActivityPub.MRF.SimplePolicy
+```
+
+Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_simple` config object.  These groups are:
+
+* `media_removal`: Servers in this group will have media stripped from incoming messages.
+* `media_nsfw`: Servers in this group will have the #nsfw tag and sensitive setting injected into incoming messages which contain media.
+* `reject`: Servers in this group will have their messages rejected.
+* `federated_timeline_removal`: Servers in this group will have their messages unlisted from the public timelines by flipping the `to` and `cc` fields.
+
+Servers should be configured as lists.
+
+### Example
+
+This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com` and remove messages from `spam.university` from the federated timeline:
+
+```
+config :pleroma, :instance,
+  rewrite_policy: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
+
+config :pleroma, :mrf_simple,
+  media_removal: ["illegalporn.biz"],
+  media_nsfw: ["porn.biz", "porn.business"],
+  reject: ["spam.com"],
+  federated_timeline_removal: ["spam.university"]
+
+```
+
+### Use with Care
+
+The effects of MRF policies can be very drastic.  It is important to use this functionality carefully.  Always try to talk to an admin before writing an MRF policy concerning their instance.
+
+## Writing your own MRF Policy
+
+As discussed above, the MRF system is a modular system that supports pluggable policies.  This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `rewrite_policy` config setting.
+
+For example, here is a sample policy module which rewrites all messages to "new message content":
+
+```!elixir
+# This is a sample MRF policy which rewrites all Notes to have "new message
+# content."
+defmodule Site.RewritePolicy do
+  @behavior Pleroma.Web.ActivityPub.MRF
+
+  # Catch messages which contain Note objects with actual data to filter.
+  # Capture the object as `object`, the message content as `content` and the
+  # message itself as `message`.
+  @impl true
+  def filter(%{"type" => Create", "object" => {"type" => "Note", "content" => content} = object} = message)
+      when is_binary(content) do
+    # Subject / CW is stored as summary instead of `name` like other AS2 objects
+    # because of Mastodon doing it that way.
+    summary = object["summary"]
+
+    # Message edits go here.
+    content = "new message content"
+
+    # Assemble the mutated object.
+    object =
+      object
+      |> Map.put("content", content)
+      |> Map.put("summary", summary)
+
+    # Assemble the mutated message.
+    message = Map.put(message, "object", object)
+    {:ok, message}
+  end
+
+  # Let all other messages through without modifying them.
+  @impl true
+  def filter(message), do: {:ok, message}
+end
+```
+
+If you save this file as `lib/site/mrf/rewrite_policy.ex`, it will be included when you next rebuild Pleroma.  You can enable it in the configuration like so:
+
+```
+config :pleroma, :instance,
+  rewrite_policy: [
+    Pleroma.Web.ActivityPub.MRF.SimplePolicy,
+    Site.RewritePolicy
+  ]
+```
+
+Please note that the Pleroma developers consider custom MRF policy modules to fall under the purview of the AGPL.  As such, you are obligated to release the sources to your custom MRF policy modules upon request.
\ No newline at end of file
index e34ffe980442cfe14fe267ba26714474e23e9f32..20118037359c7f9a2a00d64b127e25b2fe1a1d05 100644 (file)
@@ -331,3 +331,26 @@ config :auto_linker,
     rel: false
   ]
 ```
+
+## :ldap
+
+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 an 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.
+
+* `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"
+
+## Pleroma.Web.Auth.Authenticator
+
+* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
+* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
diff --git a/docs/static_dir.md b/docs/static_dir.md
new file mode 100644 (file)
index 0000000..0cc52b9
--- /dev/null
@@ -0,0 +1,20 @@
+# Static Directory
+
+Static frontend files are shipped in `priv/static/` and tracked by version control in this repository. If you want to overwrite or update these without the possibility of merge conflicts, you can write your custom versions to `instance/static/`.
+
+```
+config :pleroma, :instance,
+  static_dir: "instance/static/",
+```
+
+You can overwrite this value in your configuration to use a different static instance directory.
+
+## robots.txt
+
+By default, the `robots.txt` that ships in `priv/static/` is permissive. It allows well-behaved search engines to index all of your instance's URIs.
+
+If you want to generate a restrictive `robots.txt`, you can run the following mix task. The generated `robots.txt` will be written in your instance static directory.
+
+```
+mix pleroma.robots_txt disallow_all
+```
diff --git a/lib/mix/tasks/pleroma/robotstxt.ex b/lib/mix/tasks/pleroma/robotstxt.ex
new file mode 100644 (file)
index 0000000..2128e1c
--- /dev/null
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.RobotsTxt do
+  use Mix.Task
+
+  @shortdoc "Generate robots.txt"
+  @moduledoc """
+  Generates robots.txt
+
+  ## Overwrite robots.txt to disallow all
+
+      mix pleroma.robots_txt disallow_all
+
+  This will write a robots.txt that will hide all paths on your instance
+  from search engines and other robots that obey robots.txt
+
+  """
+  def run(["disallow_all"]) do
+    static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
+
+    if !File.exists?(static_dir) do
+      File.mkdir_p!(static_dir)
+    end
+
+    robots_txt_path = Path.join(static_dir, "robots.txt")
+    robots_txt_content = "User-Agent: *\nDisallow: /\n"
+
+    File.write!(robots_txt_path, robots_txt_content, [:write])
+  end
+end
index 41125921a9818ae1a5a045bfa85ffae3dd445f9f..a64f1ea80ff2088404192fb5821534f6ba01a0a3 100644 (file)
@@ -21,7 +21,8 @@ defmodule Pleroma.Plugs.InstanceStatic do
     end
   end
 
-  @only ~w(index.html static emoji packs sounds images instance favicon.png sw.js sw-pleroma.js)
+  @only ~w(index.html robots.txt static emoji packs sounds images instance favicon.png sw.js
+  sw-pleroma.js)
 
   def init(opts) do
     opts
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..88217aa
--- /dev/null
@@ -0,0 +1,143 @@
+# 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) do
+    if Pleroma.Config.get([:ldap, :enabled]) do
+      {name, password} =
+        case conn.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)
+
+        error ->
+          error
+      end
+    else
+      # Fall back to default authenticator
+      Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
+    end
+  end
+
+  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
index 333446befc681c7e8d9df2b6a95f66dd3eaaa151..94a19ad49d97eaf0dd21b543e561036d2fdb2cce 100644 (file)
@@ -9,7 +9,14 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
   @behaviour Pleroma.Web.Auth.Authenticator
 
   def get_user(%Plug.Conn{} = conn) do
-    %{"authorization" => %{"name" => name, "password" => password}} = conn.params
+    {name, password} =
+      case conn.params do
+        %{"authorization" => %{"name" => name, "password" => password}} ->
+          {name, password}
+
+        %{"grant_type" => "password", "username" => name, "password" => password} ->
+          {name, password}
+      end
 
     with {_, %User{} = user} <- {:user, User.get_by_nickname_or_email(name)},
          {_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do
index 697b1bc3a7357ecccbf48879aedb760b64473ac4..fa2d1cbe7f6034d592c0f28e3d49c61b615f8670 100644 (file)
@@ -25,7 +25,7 @@ defmodule Pleroma.Web.Endpoint do
     at: "/",
     from: :pleroma,
     only:
-      ~w(index.html static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
+      ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
     # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
   )
 
index e578f707e0a081073a94483bb82109840b623cbf..265bf837efd6fff2a3fb351a31a700434ae4c6c1 100644 (file)
@@ -22,6 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   alias Pleroma.Web.MastodonAPI.ListView
   alias Pleroma.Web.MastodonAPI.MastodonAPI
   alias Pleroma.Web.MastodonAPI.MastodonView
+  alias Pleroma.Web.MastodonAPI.NotificationView
   alias Pleroma.Web.MastodonAPI.ReportView
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.MediaProxy
@@ -503,19 +504,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   def notifications(%{assigns: %{user: user}} = conn, params) do
     notifications = Notification.for_user(user, params)
 
-    result =
-      notifications
-      |> Enum.map(fn x -> render_notification(user, x) end)
-      |> Enum.filter(& &1)
-
     conn
     |> add_link_headers(:notifications, notifications)
-    |> json(result)
+    |> put_view(NotificationView)
+    |> render("index.json", %{notifications: notifications, for: user})
   end
 
   def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
     with {:ok, notification} <- Notification.get(user, id) do
-      json(conn, render_notification(user, notification))
+      conn
+      |> put_view(NotificationView)
+      |> render("show.json", %{notification: notification, for: user})
     else
       {:error, reason} ->
         conn
@@ -1309,45 +1308,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     json(conn, %{})
   end
 
-  def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
-    actor = User.get_cached_by_ap_id(activity.data["actor"])
-    parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
-    mastodon_type = Activity.mastodon_notification_type(activity)
-
-    response = %{
-      id: to_string(id),
-      type: mastodon_type,
-      created_at: CommonAPI.Utils.to_masto_date(created_at),
-      account: AccountView.render("account.json", %{user: actor, for: user})
-    }
-
-    case mastodon_type do
-      "mention" ->
-        response
-        |> Map.merge(%{
-          status: StatusView.render("status.json", %{activity: activity, for: user})
-        })
-
-      "favourite" ->
-        response
-        |> Map.merge(%{
-          status: StatusView.render("status.json", %{activity: parent_activity, for: user})
-        })
-
-      "reblog" ->
-        response
-        |> Map.merge(%{
-          status: StatusView.render("status.json", %{activity: parent_activity, for: user})
-        })
-
-      "follow" ->
-        response
-
-      _ ->
-        nil
-    end
-  end
-
   def get_filters(%{assigns: %{user: user}} = conn, _) do
     filters = Filter.get_filters(user)
     res = FilterView.render("filters.json", filters: filters)
diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex
new file mode 100644 (file)
index 0000000..27e9cab
--- /dev/null
@@ -0,0 +1,64 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.NotificationView do
+  use Pleroma.Web, :view
+
+  alias Pleroma.Activity
+  alias Pleroma.Notification
+  alias Pleroma.User
+  alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.MastodonAPI.AccountView
+  alias Pleroma.Web.MastodonAPI.NotificationView
+  alias Pleroma.Web.MastodonAPI.StatusView
+
+  def render("index.json", %{notifications: notifications, for: user}) do
+    render_many(notifications, NotificationView, "show.json", %{for: user})
+  end
+
+  def render("show.json", %{
+        notification: %Notification{activity: activity} = notification,
+        for: user
+      }) do
+    actor = User.get_cached_by_ap_id(activity.data["actor"])
+    parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
+    mastodon_type = Activity.mastodon_notification_type(activity)
+
+    response = %{
+      id: to_string(notification.id),
+      type: mastodon_type,
+      created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
+      account: AccountView.render("account.json", %{user: actor, for: user}),
+      pleroma: %{
+        is_seen: notification.seen
+      }
+    }
+
+    case mastodon_type do
+      "mention" ->
+        response
+        |> Map.merge(%{
+          status: StatusView.render("status.json", %{activity: activity, for: user})
+        })
+
+      "favourite" ->
+        response
+        |> Map.merge(%{
+          status: StatusView.render("status.json", %{activity: parent_activity, for: user})
+        })
+
+      "reblog" ->
+        response
+        |> Map.merge(%{
+          status: StatusView.render("status.json", %{activity: parent_activity, for: user})
+        })
+
+      "follow" ->
+        response
+
+      _ ->
+        nil
+    end
+  end
+end
index bf3aaf025f9a91bbc577fbcb2b8e84db71dd2bb4..209119dd51a2268cc0e9b9f419856f595a762ec2 100644 (file)
@@ -257,7 +257,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       preview_url: href,
       text_url: href,
       type: type,
-      description: attachment["name"]
+      description: attachment["name"],
+      pleroma: %{mime_type: media_type}
     }
   end
 
index ec70b7ccc9379482510a4919cd5e2e30b55be1b8..d69383d400151df020f26711de0e714ec649db59 100644 (file)
@@ -5,7 +5,6 @@
 defmodule Pleroma.Web.OAuth.OAuthController do
   use Pleroma.Web, :controller
 
-  alias Comeonin.Pbkdf2
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.Auth.Authenticator
@@ -126,11 +125,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
 
   def token_exchange(
         conn,
-        %{"grant_type" => "password", "username" => name, "password" => password} = params
+        %{"grant_type" => "password"} = params
       ) do
-    with %App{} = app <- get_app_from_request(conn, params),
-         %User{} = user <- User.get_by_nickname_or_email(name),
-         true <- Pbkdf2.checkpw(password, user.password_hash),
+    with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
+         %App{} = app <- get_app_from_request(conn, params),
          {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
          scopes <- oauth_scopes(params, app.scopes),
          [] <- scopes -- app.scopes,
index aec11a79f6ba2ff016416ea853502fd4896951c5..5850a9579decd78bba61b9d92b0e43f01215d27a 100644 (file)
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.Streamer do
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.Visibility
+  alias Pleroma.Web.MastodonAPI.NotificationView
 
   @keepalive_interval :timer.seconds(30)
 
@@ -106,10 +107,10 @@ defmodule Pleroma.Web.Streamer do
         %{
           event: "notification",
           payload:
-            Pleroma.Web.MastodonAPI.MastodonAPIController.render_notification(
-              socket.assigns["user"],
-              item
-            )
+            NotificationView.render("show.json", %{
+              notification: item,
+              for: socket.assigns["user"]
+            })
             |> Jason.encode!()
         }
         |> Jason.encode!()
diff --git a/mix.exs b/mix.exs
index 70b5e4bd6ff006ffc8960c1b0f6cebc078d3dce9..efdf15d3ab8a055d8454b94a8509debe4766bcbc 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -23,11 +23,14 @@ defmodule Pleroma.Mixfile do
         logo: "priv/static/static/logo.png",
         extras: [
           "README.md",
-          "docs/config.md",
-          "docs/Pleroma-API.md",
           "docs/Admin-API.md",
           "docs/Clients.md",
-          "docs/Differences-in-MastodonAPI-Responses.md"
+          "docs/config.md",
+          "docs/Custom-Emoji.md",
+          "docs/Differences-in-MastodonAPI-Responses.md",
+          "docs/Message-Rewrite-Facility-configuration.md",
+          "docs/Pleroma-API.md",
+          "docs/static_dir.md"
         ],
         main: "readme",
         output: "priv/static/doc"
diff --git a/priv/static/robots.txt b/priv/static/robots.txt
new file mode 100644 (file)
index 0000000..25781b7
--- /dev/null
@@ -0,0 +1,2 @@
+User-Agent: *
+Disallow: 
diff --git a/test/web/mastodon_api/notification_view_test.exs b/test/web/mastodon_api/notification_view_test.exs
new file mode 100644 (file)
index 0000000..b826a7e
--- /dev/null
@@ -0,0 +1,104 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.Activity
+  alias Pleroma.Notification
+  alias Pleroma.Repo
+  alias Pleroma.User
+  alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.CommonAPI.Utils
+  alias Pleroma.Web.MastodonAPI.AccountView
+  alias Pleroma.Web.MastodonAPI.NotificationView
+  alias Pleroma.Web.MastodonAPI.StatusView
+  import Pleroma.Factory
+
+  test "Mention notification" do
+    user = insert(:user)
+    mentioned_user = insert(:user)
+    {:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{mentioned_user.nickname}"})
+    {:ok, [notification]} = Notification.create_notifications(activity)
+    user = Repo.get(User, user.id)
+
+    expected = %{
+      id: to_string(notification.id),
+      pleroma: %{is_seen: false},
+      type: "mention",
+      account: AccountView.render("account.json", %{user: user, for: mentioned_user}),
+      status: StatusView.render("status.json", %{activity: activity, for: mentioned_user}),
+      created_at: Utils.to_masto_date(notification.inserted_at)
+    }
+
+    result =
+      NotificationView.render("index.json", %{notifications: [notification], for: mentioned_user})
+
+    assert [expected] == result
+  end
+
+  test "Favourite notification" do
+    user = insert(:user)
+    another_user = insert(:user)
+    {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
+    {:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user)
+    {:ok, [notification]} = Notification.create_notifications(favorite_activity)
+    create_activity = Repo.get(Activity, create_activity.id)
+
+    expected = %{
+      id: to_string(notification.id),
+      pleroma: %{is_seen: false},
+      type: "favourite",
+      account: AccountView.render("account.json", %{user: another_user, for: user}),
+      status: StatusView.render("status.json", %{activity: create_activity, for: user}),
+      created_at: Utils.to_masto_date(notification.inserted_at)
+    }
+
+    result = NotificationView.render("index.json", %{notifications: [notification], for: user})
+
+    assert [expected] == result
+  end
+
+  test "Reblog notification" do
+    user = insert(:user)
+    another_user = insert(:user)
+    {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
+    {:ok, reblog_activity, _object} = CommonAPI.repeat(create_activity.id, another_user)
+    {:ok, [notification]} = Notification.create_notifications(reblog_activity)
+    reblog_activity = Repo.get(Activity, create_activity.id)
+
+    expected = %{
+      id: to_string(notification.id),
+      pleroma: %{is_seen: false},
+      type: "reblog",
+      account: AccountView.render("account.json", %{user: another_user, for: user}),
+      status: StatusView.render("status.json", %{activity: reblog_activity, for: user}),
+      created_at: Utils.to_masto_date(notification.inserted_at)
+    }
+
+    result = NotificationView.render("index.json", %{notifications: [notification], for: user})
+
+    assert [expected] == result
+  end
+
+  test "Follow notification" do
+    follower = insert(:user)
+    followed = insert(:user)
+    {:ok, follower, followed, _activity} = CommonAPI.follow(follower, followed)
+    notification = Notification |> Repo.one() |> Repo.preload(:activity)
+
+    expected = %{
+      id: to_string(notification.id),
+      pleroma: %{is_seen: false},
+      type: "follow",
+      account: AccountView.render("account.json", %{user: follower, for: followed}),
+      created_at: Utils.to_masto_date(notification.inserted_at)
+    }
+
+    result =
+      NotificationView.render("index.json", %{notifications: [notification], for: followed})
+
+    assert [expected] == result
+  end
+end
index 3eec2cb5beea469e3508d874dee749f26397857c..ade0ca9f9e8a6c3683e8836ddf650aaf0dd4c382 100644 (file)
@@ -196,7 +196,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
       remote_url: "someurl",
       preview_url: "someurl",
       text_url: "someurl",
-      description: nil
+      description: nil,
+      pleroma: %{mime_type: "image/png"}
     }
 
     assert expected == StatusView.render("attachment.json", %{attachment: object})
diff --git a/test/web/oauth/ldap_authorization_test.exs b/test/web/oauth/ldap_authorization_test.exs
new file mode 100644 (file)
index 0000000..570e41f
--- /dev/null
@@ -0,0 +1,187 @@
+# 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])
+    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