Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
authorsadposter <hannah+pleroma@coffee-and-dreams.uk>
Mon, 6 May 2019 19:40:42 +0000 (20:40 +0100)
committersadposter <hannah+pleroma@coffee-and-dreams.uk>
Mon, 6 May 2019 19:40:42 +0000 (20:40 +0100)
61 files changed:
.gitignore
.gitlab-ci.yml
CHANGELOG.md
config/config.exs
docs/api/differences_in_mastoapi_responses.md
docs/config.md
installation/download-mastofe-build.sh [new file with mode: 0755]
lib/mix/tasks/benchmark.ex [new file with mode: 0644]
lib/mix/tasks/pleroma/user.ex
lib/pleroma/activity.ex
lib/pleroma/bbs/authenticator.ex [new file with mode: 0644]
lib/pleroma/bbs/handler.ex [new file with mode: 0644]
lib/pleroma/formatter.ex
lib/pleroma/html.ex
lib/pleroma/plugs/http_security_plug.ex
lib/pleroma/plugs/oauth_plug.ex
lib/pleroma/repo.ex
lib/pleroma/user.ex
lib/pleroma/user/info.ex
lib/pleroma/user_invite_token.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/activity_pub_controller.ex
lib/pleroma/web/activity_pub/relay.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/views/user_view.ex
lib/pleroma/web/auth/authenticator.ex
lib/pleroma/web/auth/ldap_authenticator.ex
lib/pleroma/web/auth/pleroma_authenticator.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/oauth/app.ex
lib/pleroma/web/oauth/authorization.ex
lib/pleroma/web/oauth/oauth_controller.ex
lib/pleroma/web/oauth/token.ex
lib/pleroma/web/oauth/token/strategy/refresh_token.ex [new file with mode: 0644]
lib/pleroma/web/oauth/token/strategy/revoke.ex [new file with mode: 0644]
lib/pleroma/web/oauth/token/utils.ex [new file with mode: 0644]
lib/pleroma/web/push/impl.ex
lib/pleroma/web/twitter_api/controllers/util_controller.ex
lib/pleroma/web/twitter_api/twitter_api.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
lib/pleroma/web/twitter_api/views/user_view.ex
mix.exs
mix.lock
priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs
priv/repo/migrations/20190501125843_add_fts_index_to_objects.exs [new file with mode: 0644]
priv/repo/migrations/20190501133552_add_refresh_token_index_to_token.exs [new file with mode: 0644]
test/bbs/handler_test.exs [new file with mode: 0644]
test/formatter_test.exs
test/media_proxy_test.exs
test/plugs/oauth_plug_test.exs
test/repo_test.exs [new file with mode: 0644]
test/user_test.exs
test/web/activity_pub/transmogrifier_test.exs
test/web/auth/authenticator_test.exs [new file with mode: 0644]
test/web/mastodon_api/mastodon_api_controller_test.exs
test/web/oauth/oauth_controller_test.exs
test/web/push/impl_test.exs
test/web/twitter_api/twitter_api_controller_test.exs
test/web/twitter_api/views/activity_view_test.exs
test/web/twitter_api/views/user_view_test.exs

index a1e79e4be2a7a34ac0211ad65ae0df60cda1cb96..082c7491b6c15dcbd4779005a7dc2989b2c9d554 100644 (file)
@@ -10,6 +10,7 @@
 /test/tmp/
 /doc
 /instance
+/priv/ssh_keys
 
 # Prevent committing custom emojis
 /priv/static/emoji/custom/*
index c07f1a5d34f9028945a997fe65ffbc776eb40491..dc99b81ee4cd25dc0332fe77425d66f52ee99e6a 100644 (file)
@@ -48,6 +48,7 @@ unit-testing:
   - name: postgres:9.6.2
     command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
   script:
+    - mix deps.get
     - mix ecto.create
     - mix ecto.migrate
     - mix test --trace --preload-modules
@@ -77,4 +78,4 @@ docs-deploy:
     - echo "${SSH_HOST_KEY}" > ~/.ssh/known_hosts
     - eval $(ssh-agent -s)
     - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
-    - rsync -hrvz --delete -e "ssh -p ${SSH_PORT}" priv/static/doc/ "${SSH_USER_HOST_LOCATION}/${CI_COMMIT_REF_NAME}" 
+    - rsync -hrvz --delete -e "ssh -p ${SSH_PORT}" priv/static/doc/ "${SSH_USER_HOST_LOCATION}/${CI_COMMIT_REF_NAME}"
index 67b9649e1b9ad198509d5021dbf614f4260e1360..210aae2e4e954cd6c65421a6d5ecfa2ab14000e6 100644 (file)
@@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
 - ActivityPub C2S: OAuth endpoints
 - Metadata RelMe provider
+- OAuth: added support for refresh tokens
 - Emoji packs and emoji pack manager
 
 ### Changed
@@ -61,10 +62,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Added support max_id & since_id for bookmark timeline endpoints.
 
 ### Fixed
+- Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
 - Followers counter not being updated when a follower is blocked
 - Deactivated users being able to request an access token
 - Limit on request body in rich media/relme parsers being ignored resulting in a possible memory leak
 - proper Twitter Card generation instead of a dummy
+- Deletions failing for users with a large number of posts
 - NodeInfo: Include admins in `staffAccounts`
 - ActivityPub: Crashing when requesting empty local user's outbox
 - Federation: Handling of objects without `summary` property
index 1a9738cff4676551ea2f72c7c8b50d562be37195..946ba9adf1d6062f1da88660835c4d20e32b94ae 100644 (file)
@@ -232,7 +232,8 @@ config :pleroma, :instance,
   welcome_message: nil,
   max_report_comment_size: 1000,
   safe_dm_mentions: false,
-  healthcheck: false
+  healthcheck: false,
+  repo_batch_size: 500
 
 config :pleroma, :markup,
   # XXX - unfortunately, inline images must be enabled by default right now, because
@@ -416,7 +417,8 @@ config :pleroma_job_queue, :queues,
   web_push: 50,
   mailer: 10,
   transmogrifier: 20,
-  scheduled_activities: 10
+  scheduled_activities: 10,
+  background: 5
 
 config :pleroma, :fetch_initial_posts,
   enabled: false,
@@ -443,6 +445,9 @@ config :pleroma, :ldap,
   base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
   uid: System.get_env("LDAP_UID") || "cn"
 
+config :esshd,
+  enabled: false
+
 oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
 
 ueberauth_providers =
@@ -468,6 +473,10 @@ config :pleroma, Pleroma.ScheduledActivity,
   total_user_limit: 300,
   enabled: true
 
+config :pleroma, :oauth2,
+  token_expires_in: 600,
+  issue_new_refresh_token: true
+
 # 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 1350ace43eb0086d323e5eccdeb33944813e3dd4..d3ba41b6a1964e2f68b009300c3e831a821a7aa0 100644 (file)
@@ -1,6 +1,6 @@
 # Differences in Mastodon API responses from vanilla Mastodon
 
-A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma <version>)" present in `version` field in response from `/api/v1/instance` 
+A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma <version>)" present in `version` field in response from `/api/v1/instance`
 
 ## Flake IDs
 
@@ -80,3 +80,10 @@ Additional parameters can be added to the JSON body/Form data:
 - `hide_favorites` - if true, user's favorites timeline will be hidden
 - `show_role` - if true, user's role (e.g admin, moderator) will be exposed to anyone in the API
 - `default_scope` - the scope returned under `privacy` key in Source subentity
+
+## Authentication
+
+*Pleroma supports refreshing tokens.
+
+`POST /oauth/token`
+Post here request with grant_type=refresh_token to obtain new access token. Returns an access token.
index 7e31e6fb785ddd6d9cc56469486758cab85d228e..d999952e1d3600c23dcb58ac1675f26184443209 100644 (file)
@@ -37,7 +37,7 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
 
 An example for Sendgrid adapter:
 
-```exs
+```elixir
 config :pleroma, Pleroma.Emails.Mailer,
   adapter: Swoosh.Adapters.Sendgrid,
   api_key: "YOUR_API_KEY"
@@ -45,7 +45,7 @@ config :pleroma, Pleroma.Emails.Mailer,
 
 An example for SMTP adapter:
 
-```exs
+```elixir
 config :pleroma, Pleroma.Emails.Mailer,
   adapter: Swoosh.Adapters.SMTP,
   relay: "smtp.gmail.com",
@@ -104,12 +104,13 @@ config :pleroma, Pleroma.Emails.Mailer,
 * `max_report_comment_size`: The maximum size of the report comment (Default: `1000`)
 * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
 * `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``.
+* `repo_batch_size`: Repo batch size. The number of loaded rows from the database to the memory for processing chunks. E.g. deleting user statuses.
 
 ## :logger
 * `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack
 
 An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
-```
+```elixir
 config :logger,
   backends: [{ExSyslogger, :ex_syslogger}]
 
@@ -118,7 +119,7 @@ config :logger, :ex_syslogger,
 ```
 
 Another example, keeping console output and adding the pid to syslog output:
-```
+```elixir
 config :logger,
   backends: [:console, {ExSyslogger, :ex_syslogger}]
 
@@ -130,7 +131,7 @@ config :logger, :ex_syslogger,
 See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_syslogger’s documentation](https://hexdocs.pm/ex_syslogger/)
 
 An example of logging info to local syslog, but warn to a Slack channel:
-```
+```elixir
 config :logger,
   backends: [ {ExSyslogger, :ex_syslogger}, Quack.Logger ],
   level: :info
@@ -156,14 +157,30 @@ Frontends can access these settings at `/api/pleroma/frontend_configurations`
 
 To add your own configuration for PleromaFE, use it like this:
 
-`config :pleroma, :frontend_configurations, pleroma_fe: %{redirectRootNoLogin: "/main/all", ...}`
+```elixir
+config :pleroma, :frontend_configurations,
+  pleroma_fe: %{
+    theme: "pleroma-dark",
+    # ... see /priv/static/static/config.json for the available keys.
+},
+  masto_fe: %{
+    showInstanceSpecificPanel: true
+  }
+```
 
-These settings need to be complete, they will override the defaults. See `priv/static/static/config.json` for the available keys.
+These settings **need to be complete**, they will override the defaults.
+
+NOTE: for versions < 1.0, you need to set [`:fe`](#fe) to false, as shown a few lines below. 
 
 ## :fe
 __THIS IS DEPRECATED__
 
-If you are using this method, please change it to the `frontend_configurations` method. Please set this option to false in your config like this: `config :pleroma, :fe, false`.
+If you are using this method, please change it to the [`frontend_configurations`](#frontend_configurations) method.
+Please **set this option to false** in your config like this: 
+
+```elixir
+config :pleroma, :fe, false
+```
 
 This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:instance`` is set to false.
 
@@ -274,7 +291,7 @@ their ActivityPub ID.
 
 An example:
 
-```exs
+```elixir
 config :pleroma, :mrf_user_allowlist,
   "example.org": ["https://example.org/users/admin"]
 ```
@@ -303,7 +320,7 @@ the source code is here: https://github.com/koto-bank/kocaptcha. The default end
 
 Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter. Example:
 
-```exs
+```elixir
 config :pleroma, :admin_token, "somerandomtoken"
 ```
 
@@ -387,7 +404,7 @@ Configuration for the `auto_linker` library:
 
 Example:
 
-```exs
+```elixir
 config :auto_linker,
   opts: [
     scheme: true,
@@ -428,15 +445,36 @@ Pleroma account will be created with the same name as the LDAP user name.
 * `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"
 
+## BBS / SSH access
+
+To enable simple command line interface accessible over ssh, add a setting like this to your configuration file:
+
+```exs
+app_dir = File.cwd!
+priv_dir = Path.join([app_dir, "priv/ssh_keys"])
+
+config :esshd,
+  enabled: true,
+  priv_dir: priv_dir,
+  handler: "Pleroma.BBS.Handler",
+  port: 10_022,
+  password_authenticator: "Pleroma.BBS.Authenticator"
+```
+
+Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT`
+
 ## :auth
 
+* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
+* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
+
 Authentication / authorization settings.
 
 * `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.
 * `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
 * `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable.
 
-# OAuth consumer mode
+## OAuth consumer mode
 
 OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
 Implementation is based on Ueberauth; see the list of [available strategies](https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies).
@@ -460,7 +498,7 @@ Note: make sure that `"SameSite=Lax"` is set in `extra_cookie_attrs` when you ha
 Once the app is configured on external OAuth provider side, add app's credentials and strategy-specific settings (if any — e.g. see Microsoft below) to `config/prod.secret.exs`,
 per strategy's documentation (e.g. [ueberauth_twitter](https://github.com/ueberauth/ueberauth_twitter)). Example config basing on environment variables:
 
-```
+```elixir
 # Twitter
 config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
   consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
@@ -489,6 +527,13 @@ config :ueberauth, Ueberauth,
   ]
 ```
 
+## OAuth 2.0 provider - :oauth2
+
+Configure OAuth 2 provider capabilities:
+
+* `token_expires_in` - The lifetime in seconds of the access token.
+* `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token.
+
 ## :emoji
 * `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]`
 * `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]`
diff --git a/installation/download-mastofe-build.sh b/installation/download-mastofe-build.sh
new file mode 100755 (executable)
index 0000000..7e29386
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+project_id="74"
+project_branch="rebase/glitch-soc"
+static_dir="instance/static"
+# For bundling:
+# project_branch="pleroma"
+# static_dir="priv/static"
+
+if [[ ! -d "${static_dir}" ]]
+then
+       echo "Error: ${static_dir} directory is missing, are you sure you are running this script at the root of pleroma’s repository?"
+       exit 1
+fi
+
+last_modified="$(curl -s -I 'https://git.pleroma.social/api/v4/projects/'${project_id}'/jobs/artifacts/'${project_branch}'/download?job=build' | grep '^Last-Modified:' | cut -d: -f2-)"
+
+echo "branch:${project_branch}"
+echo "Last-Modified:${last_modified}"
+
+artifact="mastofe.zip"
+
+if [[ -e mastofe.timestamp ]] && [[ "${last_modified}" != "" ]]
+then
+       if [[ "$(cat mastofe.timestamp)" == "${last_modified}" ]]
+       then
+               echo "MastoFE is up-to-date, exiting…"
+               exit 0
+       fi
+fi
+
+curl -c - "https://git.pleroma.social/api/v4/projects/${project_id}/jobs/artifacts/${project_branch}/download?job=build" -o "${artifact}" || exit
+
+# TODO: Update the emoji as well
+rm -fr "${static_dir}/sw.js" "${static_dir}/packs" || exit
+unzip -q "${artifact}" || exit
+
+cp public/assets/sw.js "${static_dir}/sw.js" || exit
+cp -r public/packs "${static_dir}/packs" || exit
+
+echo "${last_modified}" > mastofe.timestamp
+rm -fr public
+rm -i "${artifact}"
diff --git a/lib/mix/tasks/benchmark.ex b/lib/mix/tasks/benchmark.ex
new file mode 100644 (file)
index 0000000..0fbb4db
--- /dev/null
@@ -0,0 +1,25 @@
+defmodule Mix.Tasks.Pleroma.Benchmark do
+  use Mix.Task
+  alias Mix.Tasks.Pleroma.Common
+
+  def run(["search"]) do
+    Common.start_pleroma()
+
+    Benchee.run(%{
+      "search" => fn ->
+        Pleroma.Web.MastodonAPI.MastodonAPIController.status_search(nil, "cofe")
+      end
+    })
+  end
+
+  def run(["tag"]) do
+    Common.start_pleroma()
+
+    Benchee.run(%{
+      "tag" => fn ->
+        %{"type" => "Create", "tag" => "cofe"}
+        |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
+      end
+    })
+  end
+end
index b396ff0deebc5cfc8c353c785b666b509980cefd..6a83a8c0d9669e1ea45eac1bfc3e33538a37e849 100644 (file)
@@ -126,7 +126,7 @@ defmodule Mix.Tasks.Pleroma.User do
 
     proceed? = assume_yes? or Mix.shell().yes?("Continue?")
 
-    unless not proceed? do
+    if proceed? do
       Common.start_pleroma()
 
       params = %{
@@ -163,7 +163,7 @@ defmodule Mix.Tasks.Pleroma.User do
     Common.start_pleroma()
 
     with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
-      User.delete(user)
+      User.perform(:delete, user)
       Mix.shell().info("User #{nickname} deleted.")
     else
       _ ->
@@ -380,7 +380,7 @@ defmodule Mix.Tasks.Pleroma.User do
     Common.start_pleroma()
 
     with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
-      User.delete_user_activities(user)
+      {:ok, _} = User.delete_user_activities(user)
       Mix.shell().info("User #{nickname} statuses deleted.")
     else
       _ ->
index 4a2ded51819f7dde23a89905609622735262fbb5..73e63bb144b6e83ef5cce904ade31ca6e452a0a5 100644 (file)
@@ -14,6 +14,8 @@ defmodule Pleroma.Activity do
   import Ecto.Query
 
   @type t :: %__MODULE__{}
+  @type actor :: String.t()
+
   @primary_key {:id, Pleroma.FlakeId, autogenerate: true}
 
   # https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
@@ -260,4 +262,9 @@ defmodule Pleroma.Activity do
     |> where([s], s.actor == ^actor)
     |> Repo.all()
   end
+
+  @spec query_by_actor(actor()) :: Ecto.Query.t()
+  def query_by_actor(actor) do
+    from(a in Activity, where: a.actor == ^actor)
+  end
 end
diff --git a/lib/pleroma/bbs/authenticator.ex b/lib/pleroma/bbs/authenticator.ex
new file mode 100644 (file)
index 0000000..a2c1537
--- /dev/null
@@ -0,0 +1,16 @@
+defmodule Pleroma.BBS.Authenticator do
+  use Sshd.PasswordAuthenticator
+  alias Comeonin.Pbkdf2
+  alias Pleroma.User
+
+  def authenticate(username, password) do
+    username = to_string(username)
+    password = to_string(password)
+
+    with %User{} = user <- User.get_by_nickname(username) do
+      Pbkdf2.checkpw(password, user.password_hash)
+    else
+      _e -> false
+    end
+  end
+end
diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex
new file mode 100644 (file)
index 0000000..106fe5d
--- /dev/null
@@ -0,0 +1,147 @@
+defmodule Pleroma.BBS.Handler do
+  use Sshd.ShellHandler
+  alias Pleroma.Activity
+  alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.CommonAPI
+
+  def on_shell(username, _pubkey, _ip, _port) do
+    :ok = IO.puts("Welcome to #{Pleroma.Config.get([:instance, :name])}!")
+    user = Pleroma.User.get_cached_by_nickname(to_string(username))
+    Logger.debug("#{inspect(user)}")
+    loop(run_state(user: user))
+  end
+
+  def on_connect(username, ip, port, method) do
+    Logger.debug(fn ->
+      """
+      Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{
+        inspect(port)
+      } using #{inspect(method)}
+      """
+    end)
+  end
+
+  def on_disconnect(username, ip, port) do
+    Logger.debug(fn ->
+      "Disconnecting SSH shell for #{username} from #{inspect(ip)}:#{inspect(port)}"
+    end)
+  end
+
+  defp loop(state) do
+    self_pid = self()
+    counter = state.counter
+    prefix = state.prefix
+    user = state.user
+
+    input = spawn(fn -> io_get(self_pid, prefix, counter, user.nickname) end)
+    wait_input(state, input)
+  end
+
+  def puts_activity(activity) do
+    status = Pleroma.Web.MastodonAPI.StatusView.render("status.json", %{activity: activity})
+    IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})")
+    IO.puts(HtmlSanitizeEx.strip_tags(status.content))
+    IO.puts("")
+  end
+
+  def handle_command(state, "help") do
+    IO.puts("Available commands:")
+    IO.puts("help - This help")
+    IO.puts("home - Show the home timeline")
+    IO.puts("p <text> - Post the given text")
+    IO.puts("r <id> <text> - Reply to the post with the given id")
+    IO.puts("quit - Quit")
+
+    state
+  end
+
+  def handle_command(%{user: user} = state, "r " <> text) do
+    text = String.trim(text)
+    [activity_id, rest] = String.split(text, " ", parts: 2)
+
+    with %Activity{} <- Activity.get_by_id(activity_id),
+         {:ok, _activity} <-
+           CommonAPI.post(user, %{"status" => rest, "in_reply_to_status_id" => activity_id}) do
+      IO.puts("Replied!")
+    else
+      _e -> IO.puts("Could not reply...")
+    end
+
+    state
+  end
+
+  def handle_command(%{user: user} = state, "p " <> text) do
+    text = String.trim(text)
+
+    with {:ok, _activity} <- CommonAPI.post(user, %{"status" => text}) do
+      IO.puts("Posted!")
+    else
+      _e -> IO.puts("Could not post...")
+    end
+
+    state
+  end
+
+  def handle_command(state, "home") do
+    user = state.user
+
+    params =
+      %{}
+      |> Map.put("type", ["Create"])
+      |> Map.put("blocking_user", user)
+      |> Map.put("muting_user", user)
+      |> Map.put("user", user)
+
+    activities =
+      [user.ap_id | user.following]
+      |> ActivityPub.fetch_activities(params)
+      |> ActivityPub.contain_timeline(user)
+
+    Enum.each(activities, fn activity ->
+      puts_activity(activity)
+    end)
+
+    state
+  end
+
+  def handle_command(state, command) do
+    IO.puts("Unknown command '#{command}'")
+    state
+  end
+
+  defp wait_input(state, input) do
+    receive do
+      {:input, ^input, "quit\n"} ->
+        IO.puts("Exiting...")
+
+      {:input, ^input, code} when is_binary(code) ->
+        code = String.trim(code)
+
+        state = handle_command(state, code)
+
+        loop(%{state | counter: state.counter + 1})
+
+      {:error, :interrupted} ->
+        IO.puts("Caught Ctrl+C...")
+        loop(%{state | counter: state.counter + 1})
+
+      {:input, ^input, msg} ->
+        :ok = Logger.warn("received unknown message: #{inspect(msg)}")
+        loop(%{state | counter: state.counter + 1})
+    end
+  end
+
+  defp run_state(opts) do
+    %{prefix: "pleroma", counter: 1, user: opts[:user]}
+  end
+
+  defp io_get(pid, prefix, counter, username) do
+    prompt = prompt(prefix, counter, username)
+    send(pid, {:input, self(), IO.gets(:stdio, prompt)})
+  end
+
+  defp prompt(prefix, counter, username) do
+    prompt = "#{username}@#{prefix}:#{counter}>"
+    prompt <> " "
+  end
+end
index dab8910c199b2dcc23cbe6ed96c1b7ab4313d72e..3d7c36d215a6c5603fb4e8ac32a98f9187424014 100644 (file)
@@ -113,9 +113,7 @@ defmodule Pleroma.Formatter do
 
       html =
         if not strip do
-          "<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{
-            MediaProxy.url(file)
-          }' />"
+          "<img class='emoji' alt='#{emoji}' title='#{emoji}' src='#{MediaProxy.url(file)}' />"
         else
           ""
         end
@@ -130,12 +128,23 @@ defmodule Pleroma.Formatter do
 
   def demojify(text, nil), do: text
 
+  @doc "Outputs a list of the emoji-shortcodes in a text"
   def get_emoji(text) when is_binary(text) do
     Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
   end
 
   def get_emoji(_), do: []
 
+  @doc "Outputs a list of the emoji-Maps in a text"
+  def get_emoji_map(text) when is_binary(text) do
+    get_emoji(text)
+    |> Enum.reduce(%{}, fn {name, file, _group}, acc ->
+      Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
+    end)
+  end
+
+  def get_emoji_map(_), do: []
+
   def html_escape({text, mentions, hashtags}, type) do
     {html_escape(text, type), mentions, hashtags}
   end
index cf6c0ee0a9535f0d57dedacaa72916ee8680f218..d1da746de0c781d6bd08a386641cad36492c9fd4 100644 (file)
@@ -28,12 +28,18 @@ defmodule Pleroma.HTML do
   def filter_tags(html), do: filter_tags(html, nil)
   def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags)
 
-  def get_cached_scrubbed_html_for_activity(content, scrubbers, activity, key \\ "") do
+  def get_cached_scrubbed_html_for_activity(
+        content,
+        scrubbers,
+        activity,
+        key \\ "",
+        callback \\ fn x -> x end
+      ) do
     key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
 
     Cachex.fetch!(:scrubber_cache, key, fn _key ->
       object = Pleroma.Object.normalize(activity)
-      ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false)
+      ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback)
     end)
   end
 
@@ -42,24 +48,27 @@ defmodule Pleroma.HTML do
       content,
       HtmlSanitizeEx.Scrubber.StripTags,
       activity,
-      key
+      key,
+      &HtmlEntities.decode/1
     )
   end
 
   def ensure_scrubbed_html(
         content,
         scrubbers,
-        false = _fake
-      ) do
-    {:commit, filter_tags(content, scrubbers)}
-  end
-
-  def ensure_scrubbed_html(
-        content,
-        scrubbers,
-        true = _fake
+        fake,
+        callback
       ) do
-    {:ignore, filter_tags(content, scrubbers)}
+    content =
+      content
+      |> filter_tags(scrubbers)
+      |> callback.()
+
+    if fake do
+      {:ignore, content}
+    else
+      {:commit, content}
+    end
   end
 
   defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
@@ -142,6 +151,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
     Meta.allow_tag_with_these_attributes("img", [
       "width",
       "height",
+      "class",
       "title",
       "alt"
     ])
@@ -212,6 +222,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
     Meta.allow_tag_with_these_attributes("img", [
       "width",
       "height",
+      "class",
       "title",
       "alt"
     ])
index f701aaaa5cec3b6157a1e08bf5957df52ca9555b..a476f1d49ab2ed02e98b0a1aba677b37f845a541 100644 (file)
@@ -35,7 +35,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
   defp csp_string do
     scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
     static_url = Pleroma.Web.Endpoint.static_url()
-    websocket_url = String.replace(static_url, "http", "ws")
+    websocket_url = Pleroma.Web.Endpoint.websocket_url()
 
     connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
 
index 5888d596aaaf4ae89df271be0166eabb13b75152..9d43732eb43592cbafe41c6f2a08c947836ee44c 100644 (file)
@@ -16,6 +16,16 @@ defmodule Pleroma.Plugs.OAuthPlug do
 
   def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
 
+  def call(%{params: %{"access_token" => access_token}} = conn, _) do
+    with {:ok, user, token_record} <- fetch_user_and_token(access_token) do
+      conn
+      |> assign(:token, token_record)
+      |> assign(:user, user)
+    else
+      _ -> conn
+    end
+  end
+
   def call(conn, _) do
     with {:ok, token_str} <- fetch_token_str(conn),
          {:ok, user, token_record} <- fetch_user_and_token(token_str) do
index aa5d427ae28401cccabfb82ab5734ff86d39aa71..f57e088bc288f321c11c62868ea5b363d77a702b 100644 (file)
@@ -19,4 +19,32 @@ defmodule Pleroma.Repo do
   def init(_, opts) do
     {:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))}
   end
+
+  @doc "find resource based on prepared query"
+  @spec find_resource(Ecto.Query.t()) :: {:ok, struct()} | {:error, :not_found}
+  def find_resource(%Ecto.Query{} = query) do
+    case __MODULE__.one(query) do
+      nil -> {:error, :not_found}
+      resource -> {:ok, resource}
+    end
+  end
+
+  def find_resource(_query), do: {:error, :not_found}
+
+  @doc """
+  Gets association from cache or loads if need
+
+  ## Examples
+
+    iex> Repo.get_assoc(token, :user)
+    %User{}
+
+  """
+  @spec get_assoc(struct(), atom()) :: {:ok, struct()} | {:error, :not_found}
+  def get_assoc(resource, association) do
+    case __MODULE__.preload(resource, association) do
+      %{^association => assoc} when not is_nil(assoc) -> {:ok, assoc}
+      _ -> {:error, :not_found}
+    end
+  end
 end
index c5b1ddc5da0991b15d4ba93a44a693c00b927b74..fd2ce81ad3a99f73f55315a228d4e9e443b7a1fc 100644 (file)
@@ -11,7 +11,6 @@ defmodule Pleroma.User do
   alias Comeonin.Pbkdf2
   alias Pleroma.Activity
   alias Pleroma.Bookmark
-  alias Pleroma.Formatter
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Registration
@@ -423,7 +422,7 @@ defmodule Pleroma.User do
     Enum.map(
       followed_identifiers,
       fn followed_identifier ->
-        with %User{} = followed <- get_or_fetch(followed_identifier),
+        with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
              {:ok, follower} <- maybe_direct_follow(follower, followed),
              {:ok, _} <- ActivityPub.follow(follower, followed) do
           followed
@@ -507,7 +506,15 @@ defmodule Pleroma.User do
 
   def get_cached_by_nickname(nickname) do
     key = "nickname:#{nickname}"
-    Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end)
+
+    Cachex.fetch!(:user_cache, key, fn ->
+      user_result = get_or_fetch_by_nickname(nickname)
+
+      case user_result do
+        {:ok, user} -> {:commit, user}
+        {:error, _error} -> {:ignore, nil}
+      end
+    end)
   end
 
   def get_cached_by_nickname_or_id(nickname_or_id) do
@@ -543,7 +550,7 @@ defmodule Pleroma.User do
 
   def get_or_fetch_by_nickname(nickname) do
     with %User{} = user <- get_by_nickname(nickname) do
-      user
+      {:ok, user}
     else
       _e ->
         with [_nick, _domain] <- String.split(nickname, "@"),
@@ -553,9 +560,9 @@ defmodule Pleroma.User do
             {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
           end
 
-          user
+          {:ok, user}
         else
-          _e -> nil
+          _e -> {:error, "not found " <> nickname}
         end
     end
   end
@@ -902,7 +909,7 @@ defmodule Pleroma.User do
     Enum.map(
       blocked_identifiers,
       fn blocked_identifier ->
-        with %User{} = blocked <- get_or_fetch(blocked_identifier),
+        with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
              {:ok, blocker} <- block(blocker, blocked),
              {:ok, _} <- ActivityPub.block(blocker, blocked) do
           blocked
@@ -1157,7 +1164,12 @@ defmodule Pleroma.User do
     |> update_and_set_cache()
   end
 
-  def delete(%User{} = user) do
+  @spec delete(User.t()) :: :ok
+  def delete(%User{} = user),
+    do: PleromaJobQueue.enqueue(:background, __MODULE__, [:delete, user])
+
+  @spec perform(atom(), User.t()) :: {:ok, User.t()}
+  def perform(:delete, %User{} = user) do
     {:ok, user} = User.deactivate(user)
 
     # Remove all relationships
@@ -1173,22 +1185,23 @@ defmodule Pleroma.User do
   end
 
   def delete_user_activities(%User{ap_id: ap_id} = user) do
-    Activity
-    |> where(actor: ^ap_id)
-    |> Activity.with_preloaded_object()
-    |> Repo.all()
-    |> Enum.each(fn
-      %{data: %{"type" => "Create"}} = activity ->
-        activity |> Object.normalize() |> ActivityPub.delete()
+    stream =
+      ap_id
+      |> Activity.query_by_actor()
+      |> Activity.with_preloaded_object()
+      |> Repo.stream()
 
-      # TODO: Do something with likes, follows, repeats.
-      _ ->
-        "Doing nothing"
-    end)
+    Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
 
     {:ok, user}
   end
 
+  defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
+    Object.normalize(activity) |> ActivityPub.delete()
+  end
+
+  defp delete_activity(_activity), do: "Doing nothing"
+
   def html_filter_policy(%User{info: %{no_rich_text: true}}) do
     Pleroma.HTML.Scrubber.TwitterText
   end
@@ -1202,11 +1215,11 @@ defmodule Pleroma.User do
 
     case ap_try do
       {:ok, user} ->
-        user
+        {:ok, user}
 
       _ ->
         case OStatus.make_user(ap_id) do
-          {:ok, user} -> user
+          {:ok, user} -> {:ok, user}
           _ -> {:error, "Could not fetch by AP id"}
         end
     end
@@ -1216,20 +1229,20 @@ defmodule Pleroma.User do
     user = get_cached_by_ap_id(ap_id)
 
     if !is_nil(user) and !User.needs_update?(user) do
-      user
+      {:ok, user}
     else
       # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
       should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
 
-      user = fetch_by_ap_id(ap_id)
+      resp = fetch_by_ap_id(ap_id)
 
       if should_fetch_initial do
-        with %User{} = user do
+        with {:ok, %User{} = user} = resp do
           {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
         end
       end
 
-      user
+      resp
     end
   end
 
@@ -1271,7 +1284,7 @@ defmodule Pleroma.User do
   end
 
   def get_public_key_for_ap_id(ap_id) do
-    with %User{} = user <- get_or_fetch_by_ap_id(ap_id),
+    with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
          {:ok, public_key} <- public_key_from_info(user.info) do
       {:ok, public_key}
     else
@@ -1323,18 +1336,15 @@ defmodule Pleroma.User do
     end
   end
 
-  def parse_bio(bio, user \\ %User{info: %{source_data: %{}}})
-  def parse_bio(nil, _user), do: ""
-  def parse_bio(bio, _user) when bio == "", do: bio
+  def parse_bio(bio) when is_binary(bio) and bio != "" do
+    bio
+    |> CommonUtils.format_input("text/plain", mentions_format: :full)
+    |> elem(0)
+  end
 
-  def parse_bio(bio, user) do
-    emoji =
-      (user.info.source_data["tag"] || [])
-      |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
-      |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
-        {String.trim(name, ":"), url}
-      end)
+  def parse_bio(_), do: ""
 
+  def parse_bio(bio, user) when is_binary(bio) and bio != "" do
     # TODO: get profile URLs other than user.ap_id
     profile_urls = [user.ap_id]
 
@@ -1344,9 +1354,10 @@ defmodule Pleroma.User do
       rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
     )
     |> elem(0)
-    |> Formatter.emojify(emoji)
   end
 
+  def parse_bio(_, _), do: ""
+
   def tag(user_identifiers, tags) when is_list(user_identifiers) do
     Repo.transaction(fn ->
       for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
index a3658d57ff495cd65a8fe1e9bb45412318d66072..1b81619cef86cc377f11464b1fededd2ff8e71d8 100644 (file)
@@ -41,6 +41,7 @@ defmodule Pleroma.User.Info do
     field(:hide_favorites, :boolean, default: true)
     field(:pinned_activities, {:array, :string}, default: [])
     field(:flavour, :string, default: nil)
+    field(:emoji, {:array, :map}, default: [])
 
     field(:notification_settings, :map,
       default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}
index 86f0a548690c00ff7e482efa536688757f45a1aa..fadc89891a880d0ab10d7b55a168b547152c1292 100644 (file)
@@ -24,7 +24,7 @@ defmodule Pleroma.UserInviteToken do
     timestamps()
   end
 
-  @spec create_invite(map()) :: UserInviteToken.t()
+  @spec create_invite(map()) :: {:ok, UserInviteToken.t()}
   def create_invite(params \\ %{}) do
     %UserInviteToken{}
     |> cast(params, [:max_use, :expires_at])
index 604ffae7b4e50df0d525e94c0f7dbda0711d217c..483a2153fb31db107b200b6a18215c151d50ab6f 100644 (file)
@@ -168,7 +168,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     public = "https://www.w3.org/ns/activitystreams#Public"
 
     if activity.data["type"] in ["Create", "Announce", "Delete"] do
-      object = Object.normalize(activity)
       Pleroma.Web.Streamer.stream("user", activity)
       Pleroma.Web.Streamer.stream("list", activity)
 
@@ -180,6 +179,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
         end
 
         if activity.data["type"] in ["Create"] do
+          object = Object.normalize(activity)
+
           object.data
           |> Map.get("tag", [])
           |> Enum.filter(fn tag -> is_bitstring(tag) end)
index 0b80566bf5be5859ce25ccf54e00a8deb3188c48..c967ab7a9fdc4430a7414598e27eac1ffbbb45d8 100644 (file)
@@ -155,7 +155,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
     with %User{} = recipient <- User.get_cached_by_nickname(nickname),
-         %User{} = actor <- User.get_or_fetch_by_ap_id(params["actor"]),
+         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
          true <- Utils.recipient_in_message(recipient, actor, params),
          params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
       Federator.incoming_ap_doc(params)
index a7a20ca37612686a3b38ba9dfb352ec6672572dc..93808517bde9ad2af6c717de8022b0db9c513a7c 100644 (file)
@@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
 
   def follow(target_instance) do
     with %User{} = local_user <- get_actor(),
-         %User{} = target_user <- User.get_or_fetch_by_ap_id(target_instance),
+         {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
          {:ok, activity} <- ActivityPub.follow(local_user, target_user) do
       Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
       {:ok, activity}
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
 
   def unfollow(target_instance) do
     with %User{} = local_user <- get_actor(),
-         %User{} = target_user <- User.get_or_fetch_by_ap_id(target_instance),
+         {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
          {:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do
       Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
       {:ok, activity}
index b1e859d7cf83d7bd2a5fd0ac28fdd94d8893e122..508f3532f2de46398976c5672e0ffdceff171567 100644 (file)
@@ -126,7 +126,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def fix_implicit_addressing(object, _), do: object
 
   def fix_addressing(object) do
-    %User{} = user = User.get_or_fetch_by_ap_id(object["actor"])
+    {:ok, %User{} = user} = User.get_or_fetch_by_ap_id(object["actor"])
     followers_collection = User.ap_followers(user)
 
     object
@@ -407,7 +407,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       |> fix_addressing
 
     with nil <- Activity.get_create_by_object_ap_id(object["id"]),
-         %User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
+         {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
       object = fix_object(data["object"])
 
       params = %{
@@ -436,7 +436,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
         %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
       ) do
     with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
-         %User{} = follower <- User.get_or_fetch_by_ap_id(follower),
+         {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
          {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
       with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
            {:user_blocked, false} <-
@@ -485,7 +485,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
         %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data
       ) do
     with actor <- Containment.get_actor(data),
-         %User{} = followed <- User.get_or_fetch_by_ap_id(actor),
+         {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
          {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
          {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
          %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
@@ -511,7 +511,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
         %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data
       ) do
     with actor <- Containment.get_actor(data),
-         %User{} = followed <- User.get_or_fetch_by_ap_id(actor),
+         {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
          {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
          {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
          %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
@@ -535,7 +535,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
         %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data
       ) do
     with actor <- Containment.get_actor(data),
-         %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
+         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
          {:ok, object} <- get_obj_helper(object_id),
          {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
       {:ok, activity}
@@ -548,7 +548,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
         %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data
       ) do
     with actor <- Containment.get_actor(data),
-         %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
+         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
          {:ok, object} <- get_obj_helper(object_id),
          public <- Visibility.is_public?(data),
          {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
@@ -603,7 +603,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     object_id = Utils.get_ap_id(object_id)
 
     with actor <- Containment.get_actor(data),
-         %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
+         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
          {:ok, object} <- get_obj_helper(object_id),
          :ok <- Containment.contain_origin(actor.ap_id, object.data),
          {:ok, activity} <- ActivityPub.delete(object, false) do
@@ -622,7 +622,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
         } = data
       ) do
     with actor <- Containment.get_actor(data),
-         %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
+         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
          {:ok, object} <- get_obj_helper(object_id),
          {:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
       {:ok, activity}
@@ -640,7 +640,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
         } = _data
       ) do
     with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
-         %User{} = follower <- User.get_or_fetch_by_ap_id(follower),
+         {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
          {:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
       User.unfollow(follower, followed)
       {:ok, activity}
@@ -659,7 +659,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       ) do
     with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
          %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
-         %User{} = blocker <- User.get_or_fetch_by_ap_id(blocker),
+         {:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
          {:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
       User.unblock(blocker, blocked)
       {:ok, activity}
@@ -673,7 +673,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       ) do
     with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
          %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
-         %User{} = blocker = User.get_or_fetch_by_ap_id(blocker),
+         {:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
          {:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
       User.unfollow(blocker, blocked)
       User.block(blocker, blocked)
@@ -692,7 +692,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
         } = data
       ) do
     with actor <- Containment.get_actor(data),
-         %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
+         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
          {:ok, object} <- get_obj_helper(object_id),
          {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
       {:ok, activity}
@@ -856,10 +856,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> Map.put("tag", tags ++ mentions)
   end
 
+  def add_emoji_tags(%User{info: %{"emoji" => _emoji} = user_info} = object) do
+    user_info = add_emoji_tags(user_info)
+
+    object
+    |> Map.put(:info, user_info)
+  end
+
   # TODO: we should probably send mtime instead of unix epoch time for updated
-  def add_emoji_tags(object) do
+  def add_emoji_tags(%{"emoji" => emoji} = object) do
     tags = object["tag"] || []
-    emoji = object["emoji"] || []
 
     out =
       emoji
@@ -877,6 +883,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> Map.put("tag", tags ++ out)
   end
 
+  def add_emoji_tags(object) do
+    object
+  end
+
   def set_conversation(object) do
     Map.put(object, "conversation", object["context"])
   end
index 5926a3294e44faa8a569acbb06c0989cc06bbdeb..1254fdf6cfdf94352ff1eba73f927f638ef0978f 100644 (file)
@@ -69,6 +69,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do
 
     endpoints = render("endpoints.json", %{user: user})
 
+    user_tags =
+      user
+      |> Transmogrifier.add_emoji_tags()
+      |> Map.get("tag", [])
+
     %{
       "id" => user.ap_id,
       "type" => "Person",
@@ -87,7 +92,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
         "publicKeyPem" => public_key
       },
       "endpoints" => endpoints,
-      "tag" => user.info.source_data["tag"] || []
+      "tag" => (user.info.source_data["tag"] || []) ++ user_tags
     }
     |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
     |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
index b02f595dceaabe01424be5bba6d396f33e2e49ec..d4e0ffa801f9c05ca76e60ff8d239fd6006e34c4 100644 (file)
@@ -42,4 +42,30 @@ defmodule Pleroma.Web.Auth.Authenticator do
     implementation().oauth_consumer_template() ||
       Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
   end
+
+  @doc "Gets user by nickname or email for auth."
+  @spec fetch_user(String.t()) :: User.t() | nil
+  def fetch_user(name) do
+    User.get_by_nickname_or_email(name)
+  end
+
+  # Gets name and password from conn
+  #
+  @spec fetch_credentials(Plug.Conn.t() | map()) ::
+          {:ok, {name :: any, password :: any}} | {:error, :invalid_credentials}
+  def fetch_credentials(%Plug.Conn{params: params} = _),
+    do: fetch_credentials(params)
+
+  def fetch_credentials(params) do
+    case params do
+      %{"authorization" => %{"name" => name, "password" => password}} ->
+        {:ok, {name, password}}
+
+      %{"grant_type" => "password", "username" => name, "password" => password} ->
+        {:ok, {name, password}}
+
+      _ ->
+        {:error, :invalid_credentials}
+    end
+  end
 end
index 363c99597de7a4f30433f395f935408812432f97..177c0563680c1b1a8ecce07eee518cf907e96279 100644 (file)
@@ -7,6 +7,9 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
 
   require Logger
 
+  import Pleroma.Web.Auth.Authenticator,
+    only: [fetch_credentials: 1, fetch_user: 1]
+
   @behaviour Pleroma.Web.Auth.Authenticator
   @base Pleroma.Web.Auth.PleromaAuthenticator
 
@@ -20,30 +23,20 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
   defdelegate oauth_consumer_template, to: @base
 
   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}
+    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
+      {:error, {:ldap_connection_error, _}} ->
+        # When LDAP is unavailable, try default authenticator
+        @base.get_user(conn)
 
-        {:error, {:ldap_connection_error, _}} ->
-          # When LDAP is unavailable, try default authenticator
-          @base.get_user(conn)
+      {:ldap, _} ->
+        @base.get_user(conn)
 
-        error ->
-          error
-      end
-    else
-      # Fall back to default authenticator
-      @base.get_user(conn)
+      error ->
+        error
     end
   end
 
@@ -94,7 +87,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
 
     case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do
       :ok ->
-        case User.get_by_nickname_or_email(name) do
+        case fetch_user(name) do
           %User{} = user ->
             user
 
index d647f1e05d016e52145ee23b367d0ec2a1af0e79..dd79cdcf7f8c993b98e2db42e7d372c05acf949e 100644 (file)
@@ -8,19 +8,14 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
   alias Pleroma.Repo
   alias Pleroma.User
 
+  import Pleroma.Web.Auth.Authenticator,
+    only: [fetch_credentials: 1, fetch_user: 1]
+
   @behaviour Pleroma.Web.Auth.Authenticator
 
   def get_user(%Plug.Conn{} = conn) do
-    {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)},
+    with {:ok, {name, password}} <- fetch_credentials(conn),
+         {_, %User{} = user} <- {:user, fetch_user(name)},
          {_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do
       {:ok, user}
     else
index ecd1831107d26efeba18d9eb483dbe18964b3160..b53869c7582899eebfbad8bc188a4f2bcb47a730 100644 (file)
@@ -151,8 +151,8 @@ defmodule Pleroma.Web.CommonAPI do
            ),
          {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
          context <- make_context(in_reply_to),
-         cw <- data["spoiler_text"],
-         full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
+         cw <- data["spoiler_text"] || "",
+         full_payload <- String.trim(status <> cw),
          length when length in 1..limit <- String.length(full_payload),
          object <-
            make_note_data(
@@ -170,10 +170,7 @@ defmodule Pleroma.Web.CommonAPI do
            Map.put(
              object,
              "emoji",
-             (Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
-             |> Enum.reduce(%{}, fn {name, file, _}, acc ->
-               Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
-             end)
+             Formatter.get_emoji_map(full_payload)
            ) do
       res =
         ActivityPub.create(
index 811a45c794623cf6f56aa76db9e1ddb2612330f8..b099199afd37f88af4ca3437ed17e87e58e9baa9 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   alias Pleroma.Bookmark
   alias Pleroma.Config
   alias Pleroma.Filter
+  alias Pleroma.Formatter
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Object.Fetcher
@@ -86,7 +87,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     user_params =
       %{}
       |> add_if_present(params, "display_name", :name)
-      |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value)} end)
+      |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
       |> add_if_present(params, "avatar", :avatar, fn value ->
         with %Plug.Upload{} <- value,
              {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
@@ -96,6 +97,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         end
       end)
 
+    emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
+
+    user_info_emojis =
+      ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
+      |> Enum.dedup()
+
     info_params =
       [:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role]
       |> Enum.reduce(%{}, fn key, acc ->
@@ -112,6 +119,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
           _ -> :error
         end
       end)
+      |> Map.put(:emoji, user_info_emojis)
 
     info_cng = User.Info.profile_update(user.info, info_params)
 
@@ -704,7 +712,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
-  def favourited_by(conn, %{"id" => id}) do
+  def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
     with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
          %Object{data: %{"likes" => likes}} <- Object.normalize(object) do
       q = from(u in User, where: u.ap_id in ^likes)
@@ -712,13 +720,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
 
       conn
       |> put_view(AccountView)
-      |> render(AccountView, "accounts.json", %{users: users, as: :user})
+      |> render(AccountView, "accounts.json", %{for: user, users: users, as: :user})
     else
       _ -> json(conn, [])
     end
   end
 
-  def reblogged_by(conn, %{"id" => id}) do
+  def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
     with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
          %Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
       q = from(u in User, where: u.ap_id in ^announces)
@@ -726,7 +734,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
 
       conn
       |> put_view(AccountView)
-      |> render("accounts.json", %{users: users, as: :user})
+      |> render("accounts.json", %{for: user, users: users, as: :user})
     else
       _ -> json(conn, [])
     end
@@ -783,7 +791,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       conn
       |> add_link_headers(:followers, followers, user)
       |> put_view(AccountView)
-      |> render("accounts.json", %{users: followers, as: :user})
+      |> render("accounts.json", %{for: for_user, users: followers, as: :user})
     end
   end
 
@@ -800,7 +808,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       conn
       |> add_link_headers(:following, followers, user)
       |> put_view(AccountView)
-      |> render("accounts.json", %{users: followers, as: :user})
+      |> render("accounts.json", %{for: for_user, users: followers, as: :user})
     end
   end
 
@@ -808,7 +816,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     with {:ok, follow_requests} <- User.get_follow_requests(followed) do
       conn
       |> put_view(AccountView)
-      |> render("accounts.json", %{users: follow_requests, as: :user})
+      |> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
     end
   end
 
@@ -1235,7 +1243,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
          {:ok, users} = Pleroma.List.get_following(list) do
       conn
       |> put_view(AccountView)
-      |> render("accounts.json", %{users: users, as: :user})
+      |> render("accounts.json", %{for: user, users: users, as: :user})
     end
   end
 
@@ -1295,8 +1303,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       initial_state =
         %{
           meta: %{
-            streaming_api_base_url:
-              String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
+            streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
             access_token: token,
             locale: "en",
             domain: Pleroma.Web.Endpoint.host(),
@@ -1653,7 +1660,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
               x,
               "id",
               case User.get_or_fetch(x["acct"]) do
-                %{id: id} -> id
+                {:ok, %User{id: id}} -> id
                 _ -> 0
               end
             )
index 3476da484b817cc8a3a27676f9ab03d02e3e532c..bccc2ac967f48c4394c9f3bc47e9bc48daafc8f2 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.OAuth.App do
   use Ecto.Schema
   import Ecto.Changeset
 
+  @type t :: %__MODULE__{}
   schema "apps" do
     field(:client_name, :string)
     field(:redirect_uris, :string)
index 3461f9983df55569fd9cad1b21e8ba6fdcb923a6..ca3901cc4af3f6c3075da29349e87616dc8b35cc 100644 (file)
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
   import Ecto.Changeset
   import Ecto.Query
 
+  @type t :: %__MODULE__{}
   schema "oauth_authorizations" do
     field(:token, :string)
     field(:scopes, {:array, :string}, default: [])
@@ -63,4 +64,11 @@ defmodule Pleroma.Web.OAuth.Authorization do
     )
     |> Repo.delete_all()
   end
+
+  @doc "gets auth for app by token"
+  @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
+  def get_by_token(%App{id: app_id} = _app, token) do
+    from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
+    |> Repo.find_resource()
+  end
 end
index 688eaca11420980cb0e904c1ff1f8bcb0cc152b5..e3c01217d1e9fa24bc4b24d8fc951eaa8fa40c58 100644 (file)
@@ -13,11 +13,15 @@ defmodule Pleroma.Web.OAuth.OAuthController do
   alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.OAuth.Authorization
   alias Pleroma.Web.OAuth.Token
+  alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
+  alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
 
   import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
 
   if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
 
+  @expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
+
   plug(:fetch_session)
   plug(:fetch_flash)
 
@@ -138,25 +142,33 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     Authenticator.handle_error(conn, error)
   end
 
+  @doc "Renew access_token with refresh_token"
+  def token_exchange(
+        conn,
+        %{"grant_type" => "refresh_token", "refresh_token" => token} = params
+      ) do
+    with %App{} = app <- get_app_from_request(conn, params),
+         {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
+         {:ok, token} <- RefreshToken.grant(token) do
+      response_attrs = %{created_at: Token.Utils.format_created_at(token)}
+
+      json(conn, response_token(user, token, response_attrs))
+    else
+      _error ->
+        put_status(conn, 400)
+        |> json(%{error: "Invalid credentials"})
+    end
+  end
+
   def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
     with %App{} = app <- get_app_from_request(conn, params),
-         fixed_token = fix_padding(params["code"]),
-         %Authorization{} = auth <-
-           Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
+         fixed_token = Token.Utils.fix_padding(params["code"]),
+         {:ok, auth} <- Authorization.get_by_token(app, fixed_token),
          %User{} = user <- User.get_cached_by_id(auth.user_id),
-         {:ok, token} <- Token.exchange_token(app, auth),
-         {:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
-      response = %{
-        token_type: "Bearer",
-        access_token: token.token,
-        refresh_token: token.refresh_token,
-        created_at: DateTime.to_unix(inserted_at),
-        expires_in: 60 * 10,
-        scope: Enum.join(token.scopes, " "),
-        me: user.ap_id
-      }
-
-      json(conn, response)
+         {:ok, token} <- Token.exchange_token(app, auth) do
+      response_attrs = %{created_at: Token.Utils.format_created_at(token)}
+
+      json(conn, response_token(user, token, response_attrs))
     else
       _error ->
         put_status(conn, 400)
@@ -177,16 +189,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
          true <- Enum.any?(scopes),
          {:ok, auth} <- Authorization.create_authorization(app, user, scopes),
          {:ok, token} <- Token.exchange_token(app, auth) do
-      response = %{
-        token_type: "Bearer",
-        access_token: token.token,
-        refresh_token: token.refresh_token,
-        expires_in: 60 * 10,
-        scope: Enum.join(token.scopes, " "),
-        me: user.ap_id
-      }
-
-      json(conn, response)
+      json(conn, response_token(user, token))
     else
       {:auth_active, false} ->
         # Per https://github.com/tootsuite/mastodon/blob/
@@ -218,10 +221,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     token_exchange(conn, params)
   end
 
-  def token_revoke(conn, %{"token" => token} = params) do
+  # Bad request
+  def token_exchange(conn, params), do: bad_request(conn, params)
+
+  def token_revoke(conn, %{"token" => _token} = params) do
     with %App{} = app <- get_app_from_request(conn, params),
-         %Token{} = token <- Repo.get_by(Token, token: token, app_id: app.id),
-         {:ok, %Token{}} <- Repo.delete(token) do
+         {:ok, _token} <- RevokeToken.revoke(app, params) do
       json(conn, %{})
     else
       _error ->
@@ -230,6 +235,15 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     end
   end
 
+  def token_revoke(conn, params), do: bad_request(conn, params)
+
+  # Response for bad request
+  defp bad_request(conn, _) do
+    conn
+    |> put_status(500)
+    |> json(%{error: "Bad request"})
+  end
+
   @doc "Prepares OAuth request to provider for Ueberauth"
   def prepare_request(conn, %{"provider" => provider, "authorization" => auth_attrs}) do
     scope =
@@ -278,25 +292,22 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     params = callback_params(params)
 
     with {:ok, registration} <- Authenticator.get_registration(conn) do
-      user = Repo.preload(registration, :user).user
       auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
 
-      if user do
-        create_authorization(
-          conn,
-          %{"authorization" => auth_attrs},
-          user: user
-        )
-      else
-        registration_params =
-          Map.merge(auth_attrs, %{
-            "nickname" => Registration.nickname(registration),
-            "email" => Registration.email(registration)
-          })
+      case Repo.get_assoc(registration, :user) do
+        {:ok, user} ->
+          create_authorization(conn, %{"authorization" => auth_attrs}, user: user)
 
-        conn
-        |> put_session(:registration_id, registration.id)
-        |> registration_details(%{"authorization" => registration_params})
+        _ ->
+          registration_params =
+            Map.merge(auth_attrs, %{
+              "nickname" => Registration.nickname(registration),
+              "email" => Registration.email(registration)
+            })
+
+          conn
+          |> put_session(:registration_id, registration.id)
+          |> registration_details(%{"authorization" => registration_params})
       end
     else
       _ ->
@@ -399,36 +410,30 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     end
   end
 
-  # XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
-  # decoding it.  Investigate sometime.
-  defp fix_padding(token) do
-    token
-    |> URI.decode()
-    |> Base.url_decode64!(padding: false)
-    |> Base.url_encode64(padding: false)
+  defp get_app_from_request(conn, params) do
+    conn
+    |> fetch_client_credentials(params)
+    |> fetch_client
   end
 
-  defp get_app_from_request(conn, params) do
-    # Per RFC 6749, HTTP Basic is preferred to body params
-    {client_id, client_secret} =
-      with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
-           {:ok, decoded} <- Base.decode64(encoded),
-           [id, secret] <-
-             String.split(decoded, ":")
-             |> Enum.map(fn s -> URI.decode_www_form(s) end) do
-        {id, secret}
-      else
-        _ -> {params["client_id"], params["client_secret"]}
-      end
+  defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
+    Repo.get_by(App, client_id: id, client_secret: secret)
+  end
 
-    if client_id && client_secret do
-      Repo.get_by(
-        App,
-        client_id: client_id,
-        client_secret: client_secret
-      )
+  defp fetch_client({_id, _secret}), do: nil
+
+  defp fetch_client_credentials(conn, params) do
+    # Per RFC 6749, HTTP Basic is preferred to body params
+    with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
+         {:ok, decoded} <- Base.decode64(encoded),
+         [id, secret] <-
+           Enum.map(
+             String.split(decoded, ":"),
+             fn s -> URI.decode_www_form(s) end
+           ) do
+      {id, secret}
     else
-      nil
+      _ -> {params["client_id"], params["client_secret"]}
     end
   end
 
@@ -441,4 +446,16 @@ defmodule Pleroma.Web.OAuth.OAuthController do
 
   defp put_session_registration_id(conn, registration_id),
     do: put_session(conn, :registration_id, registration_id)
+
+  defp response_token(%User{} = user, token, opts \\ %{}) do
+    %{
+      token_type: "Bearer",
+      access_token: token.token,
+      refresh_token: token.refresh_token,
+      expires_in: @expires_in,
+      scope: Enum.join(token.scopes, " "),
+      me: user.ap_id
+    }
+    |> Map.merge(opts)
+  end
 end
index 399140003dbc73aa6f84b8480543c8f40eb7da58..4e5d1d1180a28c6c67758389919c4f9bd9fd2c0c 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.OAuth.Token do
   use Ecto.Schema
 
   import Ecto.Query
+  import Ecto.Changeset
 
   alias Pleroma.Repo
   alias Pleroma.User
@@ -13,6 +14,9 @@ defmodule Pleroma.Web.OAuth.Token do
   alias Pleroma.Web.OAuth.Authorization
   alias Pleroma.Web.OAuth.Token
 
+  @expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
+  @type t :: %__MODULE__{}
+
   schema "oauth_tokens" do
     field(:token, :string)
     field(:refresh_token, :string)
@@ -24,28 +28,67 @@ defmodule Pleroma.Web.OAuth.Token do
     timestamps()
   end
 
+  @doc "Gets token for app by access token"
+  @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
+  def get_by_token(%App{id: app_id} = _app, token) do
+    from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
+    |> Repo.find_resource()
+  end
+
+  @doc "Gets token for app by refresh token"
+  @spec get_by_refresh_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
+  def get_by_refresh_token(%App{id: app_id} = _app, token) do
+    from(t in __MODULE__,
+      where: t.app_id == ^app_id and t.refresh_token == ^token,
+      preload: [:user]
+    )
+    |> Repo.find_resource()
+  end
+
   def exchange_token(app, auth) do
     with {:ok, auth} <- Authorization.use_token(auth),
          true <- auth.app_id == app.id do
-      create_token(app, User.get_cached_by_id(auth.user_id), auth.scopes)
+      create_token(
+        app,
+        User.get_cached_by_id(auth.user_id),
+        %{scopes: auth.scopes}
+      )
     end
   end
 
-  def create_token(%App{} = app, %User{} = user, scopes \\ nil) do
-    scopes = scopes || app.scopes
-    token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
-    refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
-
-    token = %Token{
-      token: token,
-      refresh_token: refresh_token,
-      scopes: scopes,
-      user_id: user.id,
-      app_id: app.id,
-      valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
-    }
-
-    Repo.insert(token)
+  defp put_token(changeset) do
+    changeset
+    |> change(%{token: Token.Utils.generate_token()})
+    |> validate_required([:token])
+    |> unique_constraint(:token)
+  end
+
+  defp put_refresh_token(changeset, attrs) do
+    refresh_token = Map.get(attrs, :refresh_token, Token.Utils.generate_token())
+
+    changeset
+    |> change(%{refresh_token: refresh_token})
+    |> validate_required([:refresh_token])
+    |> unique_constraint(:refresh_token)
+  end
+
+  defp put_valid_until(changeset, attrs) do
+    expires_in =
+      Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), @expires_in))
+
+    changeset
+    |> change(%{valid_until: expires_in})
+    |> validate_required([:valid_until])
+  end
+
+  def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do
+    %__MODULE__{user_id: user.id, app_id: app.id}
+    |> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes])
+    |> validate_required([:scopes, :user_id, :app_id])
+    |> put_valid_until(attrs)
+    |> put_token
+    |> put_refresh_token(attrs)
+    |> Repo.insert()
   end
 
   def delete_user_tokens(%User{id: user_id}) do
@@ -73,4 +116,10 @@ defmodule Pleroma.Web.OAuth.Token do
     |> Repo.all()
     |> Repo.preload(:app)
   end
+
+  def is_expired?(%__MODULE__{valid_until: valid_until}) do
+    NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0
+  end
+
+  def is_expired?(_), do: false
 end
diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
new file mode 100644 (file)
index 0000000..7df0be1
--- /dev/null
@@ -0,0 +1,54 @@
+defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
+  @moduledoc """
+  Functions for dealing with refresh token strategy.
+  """
+
+  alias Pleroma.Config
+  alias Pleroma.Repo
+  alias Pleroma.Web.OAuth.Token
+  alias Pleroma.Web.OAuth.Token.Strategy.Revoke
+
+  @doc """
+  Will grant access token by refresh token.
+  """
+  @spec grant(Token.t()) :: {:ok, Token.t()} | {:error, any()}
+  def grant(token) do
+    access_token = Repo.preload(token, [:user, :app])
+
+    result =
+      Repo.transaction(fn ->
+        token_params = %{
+          app: access_token.app,
+          user: access_token.user,
+          scopes: access_token.scopes
+        }
+
+        access_token
+        |> revoke_access_token()
+        |> create_access_token(token_params)
+      end)
+
+    case result do
+      {:ok, {:error, reason}} -> {:error, reason}
+      {:ok, {:ok, token}} -> {:ok, token}
+      {:error, reason} -> {:error, reason}
+    end
+  end
+
+  defp revoke_access_token(token) do
+    Revoke.revoke(token)
+  end
+
+  defp create_access_token({:error, error}, _), do: {:error, error}
+
+  defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do
+    Token.create_token(app, user, add_refresh_token(token_params, token.refresh_token))
+  end
+
+  defp add_refresh_token(params, token) do
+    case Config.get([:oauth2, :issue_new_refresh_token], false) do
+      true -> Map.put(params, :refresh_token, token)
+      false -> params
+    end
+  end
+end
diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/oauth/token/strategy/revoke.ex
new file mode 100644 (file)
index 0000000..dea63ca
--- /dev/null
@@ -0,0 +1,22 @@
+defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
+  @moduledoc """
+  Functions for dealing with revocation.
+  """
+
+  alias Pleroma.Repo
+  alias Pleroma.Web.OAuth.App
+  alias Pleroma.Web.OAuth.Token
+
+  @doc "Finds and revokes access token for app and by token"
+  @spec revoke(App.t(), map()) :: {:ok, Token.t()} | {:error, :not_found | Ecto.Changeset.t()}
+  def revoke(%App{} = app, %{"token" => token} = _attrs) do
+    with {:ok, token} <- Token.get_by_token(app, token),
+         do: revoke(token)
+  end
+
+  @doc "Revokes access token"
+  @spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
+  def revoke(%Token{} = token) do
+    Repo.delete(token)
+  end
+end
diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/oauth/token/utils.ex
new file mode 100644 (file)
index 0000000..a81560a
--- /dev/null
@@ -0,0 +1,30 @@
+defmodule Pleroma.Web.OAuth.Token.Utils do
+  @moduledoc """
+  Auxiliary functions for dealing with tokens.
+  """
+
+  @doc "convert token inserted_at to unix timestamp"
+  def format_created_at(%{inserted_at: inserted_at} = _token) do
+    inserted_at
+    |> DateTime.from_naive!("Etc/UTC")
+    |> DateTime.to_unix()
+  end
+
+  @doc false
+  @spec generate_token(keyword()) :: binary()
+  def generate_token(opts \\ []) do
+    opts
+    |> Keyword.get(:size, 32)
+    |> :crypto.strong_rand_bytes()
+    |> Base.url_encode64(padding: false)
+  end
+
+  # XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
+  # decoding it.  Investigate sometime.
+  def fix_padding(token) do
+    token
+    |> URI.decode()
+    |> Base.url_decode64!(padding: false)
+    |> Base.url_encode64(padding: false)
+  end
+end
index 2233480c5ed9c2903f78ee246e5dbf5280e215d4..35d3ff07cbb4946b411eac8f040efb454931dd76 100644 (file)
@@ -21,8 +21,10 @@ defmodule Pleroma.Web.Push.Impl do
   @doc "Performs sending notifications for user subscriptions"
   @spec perform(Notification.t()) :: list(any) | :error
   def perform(
-        %{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} =
-          notif
+        %{
+          activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
+          user_id: user_id
+        } = notif
       )
       when activity_type in @types do
     actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
@@ -30,13 +32,14 @@ defmodule Pleroma.Web.Push.Impl do
     type = Activity.mastodon_notification_type(notif.activity)
     gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
     avatar_url = User.avatar_url(actor)
+    object = Object.normalize(activity)
 
     for subscription <- fetch_subsriptions(user_id),
         get_in(subscription.data, ["alerts", type]) do
       %{
         title: format_title(notif),
         access_token: subscription.token.token,
-        body: format_body(notif, actor),
+        body: format_body(notif, actor, object),
         notification_id: notif.id,
         notification_type: type,
         icon: avatar_url,
@@ -95,25 +98,25 @@ defmodule Pleroma.Web.Push.Impl do
   end
 
   def format_body(
-        %{activity: %{data: %{"type" => "Create", "object" => %{"content" => content}}}},
-        actor
+        %{activity: %{data: %{"type" => "Create"}}},
+        actor,
+        %{data: %{"content" => content}}
       ) do
     "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
   end
 
   def format_body(
-        %{activity: %{data: %{"type" => "Announce", "object" => activity_id}}},
-        actor
+        %{activity: %{data: %{"type" => "Announce"}}},
+        actor,
+        %{data: %{"content" => content}}
       ) do
-    %Activity{data: %{"object" => %{"id" => object_id}}} = Activity.get_by_ap_id(activity_id)
-    %Object{data: %{"content" => content}} = Object.get_by_ap_id(object_id)
-
     "@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
   end
 
   def format_body(
         %{activity: %{data: %{"type" => type}}},
-        actor
+        actor,
+        _object
       )
       when type in ["Follow", "Like"] do
     case type do
index 1122e6c5d26eef2e385ac184861cbd8bd81e673e..c03f8ab3a9e4ab7b4c3a920c8c3a45d94119a638 100644 (file)
@@ -352,7 +352,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
   def delete_account(%{assigns: %{user: user}} = conn, params) do
     case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
       {:ok, user} ->
-        Task.start(fn -> User.delete(user) end)
+        User.delete(user)
         json(conn, %{status: "success"})
 
       {:error, msg} ->
index adeac6f3c71489aa8f746fd954b3e852de10cbb6..3a777464784078dda93c10e388a8c41d11463e02 100644 (file)
@@ -293,7 +293,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
   end
 
   def get_external_profile(for_user, uri) do
-    with %User{} = user <- User.get_or_fetch(uri) do
+    with {:ok, %User{} = user} <- User.get_or_fetch(uri) do
       {:ok, UserView.render("show.json", %{user: user, for: for_user})}
     else
       _e ->
index 79ed9dad206334eb6774ba761205a20bf984759c..ef7b6fe659bd0bcee72cd9f93972cd505b37392f 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
 
   alias Ecto.Changeset
   alias Pleroma.Activity
+  alias Pleroma.Formatter
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Repo
@@ -653,7 +654,22 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
 
   defp parse_profile_bio(user, params) do
     if bio = params["description"] do
-      Map.put(params, "bio", User.parse_bio(bio, user))
+      emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")
+
+      emojis =
+        ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
+        |> Enum.dedup()
+
+      user_info =
+        user.info
+        |> Map.put(
+          "emoji",
+          emojis
+        )
+
+      params
+      |> Map.put("bio", User.parse_bio(bio, user))
+      |> Map.put("info", user_info)
     else
       params
     end
index ea015b8f05549b19a4a5721af4b1093349a654a0..f0a4ddbd3be1964eeb2116682bb20e87da4387d5 100644 (file)
@@ -67,6 +67,13 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
         {String.trim(name, ":"), url}
       end)
 
+    emoji = Enum.dedup(emoji ++ user.info.emoji)
+
+    description_html =
+      (user.bio || "")
+      |> HTML.filter_tags(User.html_filter_policy(for_user))
+      |> Formatter.emojify(emoji)
+
     # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
     # For example: [{"name": "Pronoun", "value": "she/her"}, …]
     fields =
@@ -78,7 +85,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
       %{
         "created_at" => user.inserted_at |> Utils.format_naive_asctime(),
         "description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
-        "description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)),
+        "description_html" => description_html,
         "favourites_count" => 0,
         "followers_count" => user_info[:follower_count],
         "following" => following,
diff --git a/mix.exs b/mix.exs
index efaa06a1c473b5d4ed792ee27e4ff27fccee42b3..47f7c290390c1814b1faad2b9fd8a80bbbd778d3 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -41,7 +41,7 @@ defmodule Pleroma.Mixfile do
   def application do
     [
       mod: {Pleroma.Application, []},
-      extra_applications: [:logger, :runtime_tools, :comeonin, :quack],
+      extra_applications: [:logger, :runtime_tools, :comeonin, :esshd, :quack],
       included_applications: [:ex_syslogger]
     ]
   end
@@ -102,7 +102,7 @@ defmodule Pleroma.Mixfile do
       {:ueberauth, "~> 0.4"},
       {:auto_linker,
        git: "https://git.pleroma.social/pleroma/auto_linker.git",
-       ref: "90613b4bae875a3610c275b7056b61ffdd53210d"},
+       ref: "c00c4e75b35367fa42c95ffd9b8c455bf9995829"},
       {:pleroma_job_queue, "~> 0.2.0"},
       {:telemetry, "~> 0.3"},
       {:prometheus_ex, "~> 3.0"},
@@ -111,7 +111,9 @@ defmodule Pleroma.Mixfile do
       {:prometheus_ecto, "~> 1.4"},
       {:prometheus_process_collector, "~> 1.4"},
       {:recon, github: "ferd/recon", tag: "2.4.0"},
-      {:quack, "~> 0.1.1"}
+      {:quack, "~> 0.1.1"},
+      {:benchee, "~> 1.0"},
+      {:esshd, "~> 0.1.0"}
     ] ++ oauth_deps
   end
 
index 979d599b465fb35b5d7c90d7a41549d6d848c4e8..df4d31c2fd24e1c6798e8ad57b77828f2ece39bd 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -1,8 +1,9 @@
 %{
   "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"},
-  "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "90613b4bae875a3610c275b7056b61ffdd53210d", [ref: "90613b4bae875a3610c275b7056b61ffdd53210d"]},
+  "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "c00c4e75b35367fa42c95ffd9b8c455bf9995829", [ref: "c00c4e75b35367fa42c95ffd9b8c455bf9995829"]},
   "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
   "bbcode": {:hex, :bbcode, "0.1.0", "400e618b640b635261611d7fb7f79d104917fc5b084aae371ab6b08477cb035b", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
+  "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"},
   "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
   "cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
   "calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
   "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
   "db_connection": {:hex, :db_connection, "2.0.5", "ddb2ba6761a08b2bb9ca0e7d260e8f4dd39067426d835c24491a321b7f92a4da", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
   "decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"},
+  "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
   "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
   "ecto": {:hex, :ecto, "3.0.7", "44dda84ac6b17bbbdeb8ac5dfef08b7da253b37a453c34ab1a98de7f7e5fec7f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
   "ecto_sql": {:hex, :ecto_sql, "3.0.5", "7e44172b4f7aca4469f38d7f6a3da394dbf43a1bcf0ca975e958cb957becd74e", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
+  "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
   "eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
   "ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
   "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
index ebe69696e6ff68f797b2bffecd1504d600d05367..134b7c6f769285e08d0dd724e0b87e003a0b2042 100644 (file)
@@ -18,7 +18,7 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
     |> Enum.each(fn %{id: user_id, bookmarks: bookmarks} ->
       Enum.each(bookmarks, fn ap_id ->
         activity = Activity.get_create_by_object_ap_id(ap_id)
-        {:ok, _} = Bookmark.create(user_id, activity.id)
+       unless is_nil(activity), do: {:ok, _} = Bookmark.create(user_id, activity.id)
       end)
     end)
 
diff --git a/priv/repo/migrations/20190501125843_add_fts_index_to_objects.exs b/priv/repo/migrations/20190501125843_add_fts_index_to_objects.exs
new file mode 100644 (file)
index 0000000..9b27469
--- /dev/null
@@ -0,0 +1,8 @@
+defmodule Pleroma.Repo.Migrations.AddFTSIndexToObjects do
+  use Ecto.Migration
+
+  def change do
+    drop_if_exists index(:activities, ["(to_tsvector('english', data->'object'->>'content'))"], using: :gin, name: :activities_fts)
+    create index(:objects, ["(to_tsvector('english', data->>'content'))"], using: :gin, name: :objects_fts)
+  end
+end
diff --git a/priv/repo/migrations/20190501133552_add_refresh_token_index_to_token.exs b/priv/repo/migrations/20190501133552_add_refresh_token_index_to_token.exs
new file mode 100644 (file)
index 0000000..449f2a3
--- /dev/null
@@ -0,0 +1,7 @@
+defmodule Pleroma.Repo.Migrations.AddRefreshTokenIndexToToken do
+  use Ecto.Migration
+
+  def change do
+    create(unique_index(:oauth_tokens, [:refresh_token]))
+  end
+end
diff --git a/test/bbs/handler_test.exs b/test/bbs/handler_test.exs
new file mode 100644 (file)
index 0000000..7d5d68d
--- /dev/null
@@ -0,0 +1,83 @@
+defmodule Pleroma.BBS.HandlerTest do
+  use Pleroma.DataCase
+  alias Pleroma.Activity
+  alias Pleroma.BBS.Handler
+  alias Pleroma.Object
+  alias Pleroma.Repo
+  alias Pleroma.User
+  alias Pleroma.Web.CommonAPI
+
+  import ExUnit.CaptureIO
+  import Pleroma.Factory
+  import Ecto.Query
+
+  test "getting the home timeline" do
+    user = insert(:user)
+    followed = insert(:user)
+
+    {:ok, user} = User.follow(user, followed)
+
+    {:ok, _first} = CommonAPI.post(user, %{"status" => "hey"})
+    {:ok, _second} = CommonAPI.post(followed, %{"status" => "hello"})
+
+    output =
+      capture_io(fn ->
+        Handler.handle_command(%{user: user}, "home")
+      end)
+
+    assert output =~ user.nickname
+    assert output =~ followed.nickname
+
+    assert output =~ "hey"
+    assert output =~ "hello"
+  end
+
+  test "posting" do
+    user = insert(:user)
+
+    output =
+      capture_io(fn ->
+        Handler.handle_command(%{user: user}, "p this is a test post")
+      end)
+
+    assert output =~ "Posted"
+
+    activity =
+      Repo.one(
+        from(a in Activity,
+          where: fragment("?->>'type' = ?", a.data, "Create")
+        )
+      )
+
+    assert activity.actor == user.ap_id
+    object = Object.normalize(activity)
+    assert object.data["content"] == "this is a test post"
+  end
+
+  test "replying" do
+    user = insert(:user)
+    another_user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(another_user, %{"status" => "this is a test post"})
+
+    output =
+      capture_io(fn ->
+        Handler.handle_command(%{user: user}, "r #{activity.id} this is a reply")
+      end)
+
+    assert output =~ "Replied"
+
+    reply =
+      Repo.one(
+        from(a in Activity,
+          where: fragment("?->>'type' = ?", a.data, "Create"),
+          where: a.actor == ^user.ap_id
+        )
+      )
+
+    assert reply.actor == user.ap_id
+    object = Object.normalize(reply)
+    assert object.data["content"] == "this is a reply"
+    assert object.data["inReplyTo"] == activity.data["object"]
+  end
+end
index 97eb2f58352946c83d6e710e375e77df1da0be10..06f4f6e50d57241d349b17c2805ab7d8860646c1 100644 (file)
@@ -147,7 +147,7 @@ defmodule Pleroma.FormatterTest do
     end
 
     test "gives a replacement for user links when the user is using Osada" do
-      mike = User.get_or_fetch("mike@osada.macgirvin.com")
+      {:ok, mike} = User.get_or_fetch("mike@osada.macgirvin.com")
 
       text = "@mike@osada.macgirvin.com test"
 
@@ -248,7 +248,7 @@ defmodule Pleroma.FormatterTest do
     text = "I love :firefox:"
 
     expected_result =
-      "I love <img height=\"32px\" width=\"32px\" alt=\"firefox\" title=\"firefox\" src=\"/emoji/Firefox.gif\" />"
+      "I love <img class=\"emoji\" alt=\"firefox\" title=\"firefox\" src=\"/emoji/Firefox.gif\" />"
 
     assert Formatter.emojify(text) == expected_result
   end
@@ -263,7 +263,7 @@ defmodule Pleroma.FormatterTest do
     }
 
     expected_result =
-      "I love <img height=\"32px\" width=\"32px\" alt=\"\" title=\"\" src=\"https://placehold.it/1x1\" />"
+      "I love <img class=\"emoji\" alt=\"\" title=\"\" src=\"https://placehold.it/1x1\" />"
 
     assert Formatter.emojify(text, custom_emoji) == expected_result
   end
index a4331478e163f159e8f41f242373bbe6186f1938..0a02039a6ee9cf16db304c7debc31fe9b4fc2048 100644 (file)
@@ -7,15 +7,15 @@ defmodule Pleroma.MediaProxyTest do
   import Pleroma.Web.MediaProxy
   alias Pleroma.Web.MediaProxy.MediaProxyController
 
+  setup do
+    enabled = Pleroma.Config.get([:media_proxy, :enabled])
+    on_exit(fn -> Pleroma.Config.put([:media_proxy, :enabled], enabled) end)
+    :ok
+  end
+
   describe "when enabled" do
     setup do
-      enabled = Pleroma.Config.get([:media_proxy, :enabled])
-
-      unless enabled do
-        Pleroma.Config.put([:media_proxy, :enabled], true)
-        on_exit(fn -> Pleroma.Config.put([:media_proxy, :enabled], enabled) end)
-      end
-
+      Pleroma.Config.put([:media_proxy, :enabled], true)
       :ok
     end
 
index 17fdba916ffa872daf7aeffe141a78a3f07c1592..5a2ed11cc660a857590546e44fe4388a35431f20 100644 (file)
@@ -38,6 +38,26 @@ defmodule Pleroma.Plugs.OAuthPlugTest do
     assert conn.assigns[:user] == opts[:user]
   end
 
+  test "with valid token(downcase) in url parameters, it assings the user", opts do
+    conn =
+      :get
+      |> build_conn("/?access_token=#{opts[:token]}")
+      |> put_req_header("content-type", "application/json")
+      |> fetch_query_params()
+      |> OAuthPlug.call(%{})
+
+    assert conn.assigns[:user] == opts[:user]
+  end
+
+  test "with valid token(downcase) in body parameters, it assigns the user", opts do
+    conn =
+      :post
+      |> build_conn("/api/v1/statuses", access_token: opts[:token], status: "test")
+      |> OAuthPlug.call(%{})
+
+    assert conn.assigns[:user] == opts[:user]
+  end
+
   test "with invalid token, it not assigns the user", %{conn: conn} do
     conn =
       conn
diff --git a/test/repo_test.exs b/test/repo_test.exs
new file mode 100644 (file)
index 0000000..5382289
--- /dev/null
@@ -0,0 +1,44 @@
+defmodule Pleroma.RepoTest do
+  use Pleroma.DataCase
+  import Pleroma.Factory
+
+  describe "find_resource/1" do
+    test "returns user" do
+      user = insert(:user)
+      query = from(t in Pleroma.User, where: t.id == ^user.id)
+      assert Repo.find_resource(query) == {:ok, user}
+    end
+
+    test "returns not_found" do
+      query = from(t in Pleroma.User, where: t.id == ^"9gBuXNpD2NyDmmxxdw")
+      assert Repo.find_resource(query) == {:error, :not_found}
+    end
+  end
+
+  describe "get_assoc/2" do
+    test "get assoc from preloaded data" do
+      user = %Pleroma.User{name: "Agent Smith"}
+      token = %Pleroma.Web.OAuth.Token{insert(:oauth_token) | user: user}
+      assert Repo.get_assoc(token, :user) == {:ok, user}
+    end
+
+    test "get one-to-one assoc from repo" do
+      user = insert(:user, name: "Jimi Hendrix")
+      token = refresh_record(insert(:oauth_token, user: user))
+
+      assert Repo.get_assoc(token, :user) == {:ok, user}
+    end
+
+    test "get one-to-many assoc from repo" do
+      user = insert(:user)
+      notification = refresh_record(insert(:notification, user: user))
+
+      assert Repo.get_assoc(user, :notifications) == {:ok, [notification]}
+    end
+
+    test "return error if has not assoc " do
+      token = insert(:oauth_token, user: nil)
+      assert Repo.get_assoc(token, :user) == {:error, :not_found}
+    end
+  end
+end
index 7be47e5fb505d814263eb666afc537e61fb15587..adc77a26416ddd67ecf6c92c3725289ee9fec3c7 100644 (file)
@@ -362,7 +362,7 @@ defmodule Pleroma.UserTest do
   describe "get_or_fetch/1" do
     test "gets an existing user by nickname" do
       user = insert(:user)
-      fetched_user = User.get_or_fetch(user.nickname)
+      {:ok, fetched_user} = User.get_or_fetch(user.nickname)
 
       assert user == fetched_user
     end
@@ -379,7 +379,7 @@ defmodule Pleroma.UserTest do
           info: %{}
         )
 
-      fetched_user = User.get_or_fetch(ap_id)
+      {:ok, fetched_user} = User.get_or_fetch(ap_id)
       freshed_user = refresh_record(user)
       assert freshed_user == fetched_user
     end
@@ -388,14 +388,14 @@ defmodule Pleroma.UserTest do
   describe "fetching a user from nickname or trying to build one" do
     test "gets an existing user" do
       user = insert(:user)
-      fetched_user = User.get_or_fetch_by_nickname(user.nickname)
+      {:ok, fetched_user} = User.get_or_fetch_by_nickname(user.nickname)
 
       assert user == fetched_user
     end
 
     test "gets an existing user, case insensitive" do
       user = insert(:user, nickname: "nick")
-      fetched_user = User.get_or_fetch_by_nickname("NICK")
+      {:ok, fetched_user} = User.get_or_fetch_by_nickname("NICK")
 
       assert user == fetched_user
     end
@@ -403,7 +403,7 @@ defmodule Pleroma.UserTest do
     test "gets an existing user by fully qualified nickname" do
       user = insert(:user)
 
-      fetched_user =
+      {:ok, fetched_user} =
         User.get_or_fetch_by_nickname(user.nickname <> "@" <> Pleroma.Web.Endpoint.host())
 
       assert user == fetched_user
@@ -413,24 +413,24 @@ defmodule Pleroma.UserTest do
       user = insert(:user, nickname: "nick")
       casing_altered_fqn = String.upcase(user.nickname <> "@" <> Pleroma.Web.Endpoint.host())
 
-      fetched_user = User.get_or_fetch_by_nickname(casing_altered_fqn)
+      {:ok, fetched_user} = User.get_or_fetch_by_nickname(casing_altered_fqn)
 
       assert user == fetched_user
     end
 
     test "fetches an external user via ostatus if no user exists" do
-      fetched_user = User.get_or_fetch_by_nickname("shp@social.heldscal.la")
+      {:ok, fetched_user} = User.get_or_fetch_by_nickname("shp@social.heldscal.la")
       assert fetched_user.nickname == "shp@social.heldscal.la"
     end
 
     test "returns nil if no user could be fetched" do
-      fetched_user = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la")
-      assert fetched_user == nil
+      {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la")
+      assert fetched_user == "not found nonexistant@social.heldscal.la"
     end
 
     test "returns nil for nonexistant local user" do
-      fetched_user = User.get_or_fetch_by_nickname("nonexistant")
-      assert fetched_user == nil
+      {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant")
+      assert fetched_user == "not found nonexistant"
     end
 
     test "updates an existing user, if stale" do
@@ -448,7 +448,7 @@ defmodule Pleroma.UserTest do
 
       assert orig_user.last_refreshed_at == a_week_ago
 
-      user = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
+      {:ok, user} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
       assert user.info.source_data["endpoints"]
 
       refute user.last_refreshed_at == orig_user.last_refreshed_at
@@ -829,10 +829,12 @@ defmodule Pleroma.UserTest do
     user = insert(:user)
 
     {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
-    {:ok, _} = User.delete_user_activities(user)
 
-    # TODO: Remove favorites, repeats, delete activities.
-    refute Activity.get_by_id(activity.id)
+    Ecto.Adapters.SQL.Sandbox.unboxed_run(Repo, fn ->
+      {:ok, _} = User.delete_user_activities(user)
+      # TODO: Remove favorites, repeats, delete activities.
+      refute Activity.get_by_id(activity.id)
+    end)
   end
 
   test ".delete deactivates a user, all follow relationships and all create activities" do
@@ -1103,7 +1105,7 @@ defmodule Pleroma.UserTest do
       expected_text =
         "A.k.a. <span class='h-card'><a data-user='#{remote_user.id}' class='u-url mention' href='#{
           remote_user.ap_id
-        }'>" <> "@<span>nick@domain.com</span></a></span>"
+        }'>@<span>nick@domain.com</span></a></span>"
 
       assert expected_text == User.parse_bio(bio, user)
     end
index 78429c7c686246baf2215eae1cee7952c3e339ad..c24b50f8c833a4f1513edb8841b51cb7153fdc25 100644 (file)
@@ -219,7 +219,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       Pleroma.Config.put([:user, :deny_follow_blocked], true)
 
       user = insert(:user)
-      target = User.get_or_fetch("http://mastodon.example.org/users/admin")
+      {:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin")
 
       {:ok, user} = User.block(user, target)
 
diff --git a/test/web/auth/authenticator_test.exs b/test/web/auth/authenticator_test.exs
new file mode 100644 (file)
index 0000000..fea5c82
--- /dev/null
@@ -0,0 +1,42 @@
+# 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.AuthenticatorTest do
+  use Pleroma.Web.ConnCase
+
+  alias Pleroma.Web.Auth.Authenticator
+  import Pleroma.Factory
+
+  describe "fetch_user/1" do
+    test "returns user by name" do
+      user = insert(:user)
+      assert Authenticator.fetch_user(user.nickname) == user
+    end
+
+    test "returns user by email" do
+      user = insert(:user)
+      assert Authenticator.fetch_user(user.email) == user
+    end
+
+    test "returns nil" do
+      assert Authenticator.fetch_user("email") == nil
+    end
+  end
+
+  describe "fetch_credentials/1" do
+    test "returns name and password from authorization params" do
+      params = %{"authorization" => %{"name" => "test", "password" => "test-pass"}}
+      assert Authenticator.fetch_credentials(params) == {:ok, {"test", "test-pass"}}
+    end
+
+    test "returns name and password with grant_type 'password'" do
+      params = %{"grant_type" => "password", "username" => "test", "password" => "test-pass"}
+      assert Authenticator.fetch_credentials(params) == {:ok, {"test", "test-pass"}}
+    end
+
+    test "returns error" do
+      assert Authenticator.fetch_credentials(%{}) == {:error, :invalid_credentials}
+    end
+  end
+end
index c2a12d3c7a9fca1d4114ef148359024e7e9a1461..610aa486e551b2189ea0c9870e4ed2c8725941c7 100644 (file)
@@ -2351,6 +2351,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         end
       end
     end
+
+    test "updates profile emojos", %{conn: conn} do
+      user = insert(:user)
+
+      note = "*sips :blank:*"
+      name = "I am :firefox:"
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{
+          "note" => note,
+          "display_name" => name
+        })
+
+      assert json_response(conn, 200)
+
+      conn =
+        conn
+        |> get("/api/v1/accounts/#{user.id}")
+
+      assert user = json_response(conn, 200)
+
+      assert user["note"] == note
+      assert user["display_name"] == name
+      assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
+    end
   end
 
   test "get instance information", %{conn: conn} do
index 6e96537ecc9759834aa5dc10287ad71902303c55..cb68369838979a66504054b78683d596b94a2e75 100644 (file)
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
   alias Pleroma.Web.OAuth.Authorization
   alias Pleroma.Web.OAuth.Token
 
+  @oauth_config_path [:oauth2, :issue_new_refresh_token]
   @session_opts [
     store: :cookie,
     key: "_test",
@@ -714,4 +715,199 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       refute Map.has_key?(resp, "access_token")
     end
   end
+
+  describe "POST /oauth/token - refresh token" do
+    setup do
+      oauth_token_config = Pleroma.Config.get(@oauth_config_path)
+
+      on_exit(fn ->
+        Pleroma.Config.get(@oauth_config_path, oauth_token_config)
+      end)
+    end
+
+    test "issues a new access token with keep fresh token" do
+      Pleroma.Config.put(@oauth_config_path, true)
+      user = insert(:user)
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
+      {:ok, token} = Token.exchange_token(app, auth)
+
+      response =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "refresh_token",
+          "refresh_token" => token.refresh_token,
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+        |> json_response(200)
+
+      ap_id = user.ap_id
+
+      assert match?(
+               %{
+                 "scope" => "write",
+                 "token_type" => "Bearer",
+                 "expires_in" => 600,
+                 "access_token" => _,
+                 "refresh_token" => _,
+                 "me" => ^ap_id
+               },
+               response
+             )
+
+      refute Repo.get_by(Token, token: token.token)
+      new_token = Repo.get_by(Token, token: response["access_token"])
+      assert new_token.refresh_token == token.refresh_token
+      assert new_token.scopes == auth.scopes
+      assert new_token.user_id == user.id
+      assert new_token.app_id == app.id
+    end
+
+    test "issues a new access token with new fresh token" do
+      Pleroma.Config.put(@oauth_config_path, false)
+      user = insert(:user)
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
+      {:ok, token} = Token.exchange_token(app, auth)
+
+      response =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "refresh_token",
+          "refresh_token" => token.refresh_token,
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+        |> json_response(200)
+
+      ap_id = user.ap_id
+
+      assert match?(
+               %{
+                 "scope" => "write",
+                 "token_type" => "Bearer",
+                 "expires_in" => 600,
+                 "access_token" => _,
+                 "refresh_token" => _,
+                 "me" => ^ap_id
+               },
+               response
+             )
+
+      refute Repo.get_by(Token, token: token.token)
+      new_token = Repo.get_by(Token, token: response["access_token"])
+      refute new_token.refresh_token == token.refresh_token
+      assert new_token.scopes == auth.scopes
+      assert new_token.user_id == user.id
+      assert new_token.app_id == app.id
+    end
+
+    test "returns 400 if we try use access token" do
+      user = insert(:user)
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
+      {:ok, token} = Token.exchange_token(app, auth)
+
+      response =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "refresh_token",
+          "refresh_token" => token.token,
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+        |> json_response(400)
+
+      assert %{"error" => "Invalid credentials"} == response
+    end
+
+    test "returns 400 if refresh_token invalid" do
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      response =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "refresh_token",
+          "refresh_token" => "token.refresh_token",
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+        |> json_response(400)
+
+      assert %{"error" => "Invalid credentials"} == response
+    end
+
+    test "issues a new token if token expired" do
+      user = insert(:user)
+      app = insert(:oauth_app, scopes: ["read", "write"])
+
+      {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
+      {:ok, token} = Token.exchange_token(app, auth)
+
+      change =
+        Ecto.Changeset.change(
+          token,
+          %{valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -86_400 * 30)}
+        )
+
+      {:ok, access_token} = Repo.update(change)
+
+      response =
+        build_conn()
+        |> post("/oauth/token", %{
+          "grant_type" => "refresh_token",
+          "refresh_token" => access_token.refresh_token,
+          "client_id" => app.client_id,
+          "client_secret" => app.client_secret
+        })
+        |> json_response(200)
+
+      ap_id = user.ap_id
+
+      assert match?(
+               %{
+                 "scope" => "write",
+                 "token_type" => "Bearer",
+                 "expires_in" => 600,
+                 "access_token" => _,
+                 "refresh_token" => _,
+                 "me" => ^ap_id
+               },
+               response
+             )
+
+      refute Repo.get_by(Token, token: token.token)
+      token = Repo.get_by(Token, token: response["access_token"])
+      assert token
+      assert token.scopes == auth.scopes
+      assert token.user_id == user.id
+      assert token.app_id == app.id
+    end
+  end
+
+  describe "POST /oauth/token - bad request" do
+    test "returns 500" do
+      response =
+        build_conn()
+        |> post("/oauth/token", %{})
+        |> json_response(500)
+
+      assert %{"error" => "Bad request"} == response
+    end
+  end
+
+  describe "POST /oauth/revoke - bad request" do
+    test "returns 500" do
+      response =
+        build_conn()
+        |> post("/oauth/revoke", %{})
+        |> json_response(500)
+
+      assert %{"error" => "Bad request"} == response
+    end
+  end
 end
index 49b2a92030b59791ec337f58d5b56cd152bfe535..1e948086a135f49568ab681aa1cecfb7b4cfb466 100644 (file)
@@ -5,6 +5,8 @@
 defmodule Pleroma.Web.Push.ImplTest do
   use Pleroma.DataCase
 
+  alias Pleroma.Object
+  alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Push.Impl
   alias Pleroma.Web.Push.Subscription
 
@@ -52,16 +54,12 @@ defmodule Pleroma.Web.Push.ImplTest do
       data: %{alerts: %{"follow" => true, "mention" => false}}
     )
 
+    {:ok, activity} = CommonAPI.post(user, %{"status" => "<Lorem ipsum dolor sit amet."})
+
     notif =
       insert(:notification,
         user: user,
-        activity: %Pleroma.Activity{
-          data: %{
-            "type" => "Create",
-            "actor" => user.ap_id,
-            "object" => %{"content" => "<Lorem ipsum dolor sit amet."}
-          }
-        }
+        activity: activity
       )
 
     assert Impl.perform(notif) == [:ok, :ok]
@@ -100,48 +98,65 @@ defmodule Pleroma.Web.Push.ImplTest do
   end
 
   test "renders body for create activity" do
+    user = insert(:user, nickname: "Bob")
+
+    {:ok, activity} =
+      CommonAPI.post(user, %{
+        "status" =>
+          "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
+      })
+
+    object = Object.normalize(activity)
+
     assert Impl.format_body(
              %{
-               activity: %{
-                 data: %{
-                   "type" => "Create",
-                   "object" => %{
-                     "content" =>
-                       "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
-                   }
-                 }
-               }
+               activity: activity
              },
-             %{nickname: "Bob"}
+             user,
+             object
            ) ==
              "@Bob: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..."
   end
 
   test "renders body for follow activity" do
-    assert Impl.format_body(%{activity: %{data: %{"type" => "Follow"}}}, %{nickname: "Bob"}) ==
+    user = insert(:user, nickname: "Bob")
+    other_user = insert(:user)
+    {:ok, _, _, activity} = CommonAPI.follow(user, other_user)
+    object = Object.normalize(activity)
+
+    assert Impl.format_body(%{activity: activity}, user, object) ==
              "@Bob has followed you"
   end
 
   test "renders body for announce activity" do
     user = insert(:user)
 
-    note =
-      insert(:note, %{
-        data: %{
-          "content" =>
-            "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
-        }
+    {:ok, activity} =
+      CommonAPI.post(user, %{
+        "status" =>
+          "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
       })
 
-    note_activity = insert(:note_activity, %{note: note})
-    announce_activity = insert(:announce_activity, %{user: user, note_activity: note_activity})
+    {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user)
+    object = Object.normalize(activity)
 
-    assert Impl.format_body(%{activity: announce_activity}, user) ==
+    assert Impl.format_body(%{activity: announce_activity}, user, object) ==
              "@#{user.nickname} repeated: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..."
   end
 
   test "renders body for like activity" do
-    assert Impl.format_body(%{activity: %{data: %{"type" => "Like"}}}, %{nickname: "Bob"}) ==
+    user = insert(:user, nickname: "Bob")
+
+    {:ok, activity} =
+      CommonAPI.post(user, %{
+        "status" =>
+          "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
+      })
+
+    {:ok, activity, _} = CommonAPI.favorite(activity.id, user)
+    object = Object.normalize(activity)
+
+    assert Impl.format_body(%{activity: activity}, user, object) ==
              "@Bob has favorited your post"
   end
 end
index 43ad71a166404c335a89ac6aee964accecd9c035..90718cfb4351c5f342f2bec19bc3c874c314635e 100644 (file)
@@ -1611,6 +1611,34 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
 
       assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
     end
+
+    # Broken before the change to class="emoji" and non-<img/> in the DB
+    @tag :skip
+    test "it formats emojos", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/account/update_profile.json", %{
+          "bio" => "I love our :moominmamma:​"
+        })
+
+      assert response = json_response(conn, 200)
+
+      assert %{
+               "description" => "I love our :moominmamma:",
+               "description_html" =>
+                 ~s{I love our <img class="emoji" alt="moominmamma" title="moominmamma" src="} <>
+                   _
+             } = response
+
+      conn =
+        conn
+        |> get("/api/users/show.json?user_id=#{user.nickname}")
+
+      assert response == json_response(conn, 200)
+    end
   end
 
   defp valid_user(_context) do
index d84ab74208fd5910fb344a17df13d0ee9af03d83..1aa533b48720a6bf705190fa9597cd216178b3ab 100644 (file)
@@ -100,7 +100,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
     expected = ":firefox: meow"
 
     expected_html =
-      "<img height=\"32px\" width=\"32px\" alt=\"firefox\" title=\"firefox\" src=\"http://localhost:4001/emoji/Firefox.gif\" /> meow"
+      "<img class=\"emoji\" alt=\"firefox\" title=\"firefox\" src=\"http://localhost:4001/emoji/Firefox.gif\" /> meow"
 
     assert result["summary"] == expected
     assert result["summary_html"] == expected_html
@@ -371,4 +371,14 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
     assert length(result["attachments"]) == 1
     assert result["summary"] == "Friday Night"
   end
+
+  test "special characters are not escaped in text field for status created" do
+    text = "<3 is on the way"
+
+    {:ok, activity} = CommonAPI.post(insert(:user), %{"status" => text})
+
+    result = ActivityView.render("activity.json", activity: activity)
+
+    assert result["text"] == text
+  end
 end
index c99dbddeb355630cb4819ec23b26f0130fa0452f..74526673c2959034b72b84618bbc194be36fd1ad 100644 (file)
@@ -32,7 +32,7 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
 
   test "A user with emoji in username" do
     expected =
-      "<img height=\"32px\" width=\"32px\" alt=\"karjalanpiirakka\" title=\"karjalanpiirakka\" src=\"/file.png\" /> man"
+      "<img class=\"emoji\" alt=\"karjalanpiirakka\" title=\"karjalanpiirakka\" src=\"/file.png\" /> man"
 
     user =
       insert(:user, %{