diff options
27 files changed, 537 insertions, 259 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 46fa1c74c..c28468cd4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: elixir:1.6.4 +image: elixir:1.7.2  services:    - postgres:9.6.2 diff --git a/installation/caddyfile-pleroma.example b/installation/caddyfile-pleroma.example index 2c1efde2d..305f2aa79 100644 --- a/installation/caddyfile-pleroma.example +++ b/installation/caddyfile-pleroma.example @@ -1,4 +1,10 @@ -social.domain.tld  { +# default Caddyfile config for Pleroma +# +# Simple installation instructions: +# 1. Replace 'example.tld' with your instance's domain wherever it appears. +# 2. Copy this section into your Caddyfile and restart Caddy. + +example.tld  {    log /var/log/caddy/pleroma_access.log    errors /var/log/caddy/pleroma_error.log @@ -9,7 +15,7 @@ social.domain.tld  {      transparent    } -  tls user@domain.tld { +  tls {      # Remove the rest of the lines in here, if you want to support older devices      key_type p256      ciphers ECDHE-ECDSA-WITH-CHACHA20-POLY1305 ECDHE-RSA-WITH-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 @@ -22,15 +28,15 @@ social.domain.tld  {      Referrer-Policy "same-origin"      Strict-Transport-Security "max-age=31536000; includeSubDomains;"      Expect-CT "enforce, max-age=2592000" -    Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://social.domain.tld; upgrade-insecure-requests;" +    Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://{host}; upgrade-insecure-requests;"    }    # If you do not want remote frontends to be able to access your Pleroma backend server, remove these lines.    # If you want to allow all origins access, remove the origin lines.    # To use this directive, you need the http.cors plugin for Caddy.    cors / { -    origin https://halcyon.domain.tld -    origin https://pinafore.domain.tld +    origin https://halcyon.example.tld +    origin https://pinafore.example.tld      methods POST,PUT,DELETE,GET,PATCH,OPTIONS      allowed_headers Authorization,Content-Type,Idempotency-Key      exposed_headers Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id diff --git a/installation/pleroma-apache.conf b/installation/pleroma-apache.conf index 992c0c900..fb777983e 100644 --- a/installation/pleroma-apache.conf +++ b/installation/pleroma-apache.conf @@ -1,24 +1,31 @@ -#Example configuration for when Apache httpd and Pleroma are on the same host. -#Needed modules: headers proxy proxy_http proxy_wstunnel rewrite ssl -#This assumes a Debian style Apache config. Put this in /etc/apache2/sites-available -#Install your TLS certificate, possibly using Let's Encrypt. -#Replace 'pleroma.example.com' with your instance's domain wherever it appears - -ServerName pleroma.example.com +# default Apache site config for Pleroma +# +# needed modules: define headers proxy proxy_http proxy_wstunnel rewrite ssl +# +# Simple installation instructions: +# 1. Install your TLS certificate, possibly using Let's Encrypt. +# 2. Replace 'example.tld' with your instance's domain wherever it appears. +# 3. This assumes a Debian style Apache config. Copy this file to +#    /etc/apache2/sites-available/ and then add a symlink to it in +#    /etc/apache2/sites-enabled/ by running 'a2ensite pleroma-apache.conf', then restart Apache. + +Define servername example.tld + +ServerName ${servername}  ServerTokens Prod  ErrorLog ${APACHE_LOG_DIR}/error.log  CustomLog ${APACHE_LOG_DIR}/access.log combined  <VirtualHost *:80> -    Redirect permanent / https://pleroma.example.com +    Redirect permanent / https://${servername}  </VirtualHost>  <VirtualHost *:443>      SSLEngine on -    SSLCertificateFile      /etc/letsencrypt/live/pleroma.example.com/cert.pem -    SSLCertificateKeyFile   /etc/letsencrypt/live/pleroma.example.com/privkey.pem -    SSLCertificateChainFile /etc/letsencrypt/live/pleroma.example.com/fullchain.pem +    SSLCertificateFile      /etc/letsencrypt/live/${servername}/cert.pem +    SSLCertificateKeyFile   /etc/letsencrypt/live/${servername}/privkey.pem +    SSLCertificateChainFile /etc/letsencrypt/live/${servername}/fullchain.pem      # Mozilla modern configuration, tweak to your needs      SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1 @@ -31,7 +38,7 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined      Header always set X-Frame-Options "DENY"      Header always set X-Content-Type-Options "nosniff"      Header always set Referrer-Policy same-origin -    Header always set Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://pleroma.example.tld; upgrade-insecure-requests;" +    Header always set Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://${servername}; upgrade-insecure-requests;"      # Uncomment this only after you get HTTPS working.      # Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" @@ -45,7 +52,7 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined      ProxyPass / http://localhost:4000/      ProxyPassReverse / http://localhost:4000/ -    RequestHeader set Host "pleroma.example.com" +    RequestHeader set Host ${servername}      ProxyPreserveHost On  </VirtualHost> @@ -53,4 +60,4 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined  SSLUseStapling          on  SSLStaplingResponderTimeout 5  SSLStaplingReturnResponderErrors off -SSLStaplingCache        shmcb:/var/run/ocsp(128000)
\ No newline at end of file +SSLStaplingCache        shmcb:/var/run/ocsp(128000) diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx index f86239672..65a3cdb4c 100644 --- a/installation/pleroma.nginx +++ b/installation/pleroma.nginx @@ -10,8 +10,8 @@ proxy_cache_path /tmp/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cac                   inactive=720m use_temp_path=off;  server { -    listen         80;      server_name    example.tld; +    listen         80;      return         301 https://$server_name$request_uri;      # Uncomment this if you need to use the 'webroot' method with certbot. Make sure @@ -46,7 +46,7 @@ server {      ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;      ssl_stapling on;      ssl_stapling_verify on; -     +      server_name example.tld;      gzip_vary on; @@ -76,8 +76,8 @@ server {          add_header X-Content-Type-Options "nosniff" always;          add_header Referrer-Policy "same-origin" always;          add_header X-Download-Options "noopen" always; -        add_header Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action *; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://example.tld; upgrade-insecure-requests;" always; -         +        add_header Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action *; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://$server_name; upgrade-insecure-requests;" always; +          # Uncomment this only after you get HTTPS working.          # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index f30fcd1e4..eedad7675 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -10,9 +10,9 @@ defmodule Pleroma.Application do      # Define workers and child supervisors to be supervised      children =        [ -        worker(Pleroma.Config, [Application.get_all_env(:pleroma)]),          # Start the Ecto repository          supervisor(Pleroma.Repo, []), +        worker(Pleroma.Emoji, []),          # Start the endpoint when the application starts          supervisor(Pleroma.Web.Endpoint, []),          # Start your own worker by calling: Pleroma.Worker.start_link(arg1, arg2, arg3) @@ -57,8 +57,8 @@ defmodule Pleroma.Application do            id: :cachex_idem          ),          worker(Pleroma.Web.Federator, []), -        worker(Pleroma.Gopher.Server, []), -        worker(Pleroma.Stats, []) +        worker(Pleroma.Stats, []), +        worker(Pleroma.Gopher.Server, [])        ] ++          if Mix.env() == :test,            do: [], diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex index 510d8d498..fc5338591 100644 --- a/lib/pleroma/config.ex +++ b/lib/pleroma/config.ex @@ -1,15 +1,26 @@  defmodule Pleroma.Config do -  use Agent +  def get([key]), do: get(key) -  def start_link(initial) do -    Agent.start_link(fn -> initial end, name: __MODULE__) +  def get([parent_key | keys]) do +    Application.get_env(:pleroma, parent_key) +    |> get_in(keys)    end -  def get(path) do -    Agent.get(__MODULE__, Kernel, :get_in, [path]) +  def get(key) do +    Application.get_env(:pleroma, key)    end -  def put(path, value) do -    Agent.update(__MODULE__, Kernel, :put_in, [path, value]) +  def put([key], value), do: put(key, value) + +  def put([parent_key | keys], value) do +    parent = +      Application.get_env(:pleroma, parent_key) +      |> put_in(keys, value) + +    Application.put_env(:pleroma, parent_key, parent) +  end + +  def put(key, value) do +    Application.put_env(:pleroma, key, value)    end  end diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex new file mode 100644 index 000000000..0a5e1d5ce --- /dev/null +++ b/lib/pleroma/emoji.ex @@ -0,0 +1,194 @@ +defmodule Pleroma.Emoji do +  @moduledoc """ +  The emojis are loaded from: + +    * the built-in Finmojis (if enabled in configuration), +    * the files: `config/emoji.txt` and `config/custom_emoji.txt` +    * glob paths + +  This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime. +  """ +  use GenServer +  @ets __MODULE__.Ets +  @ets_options [:set, :protected, :named_table, {:read_concurrency, true}] + +  @doc false +  def start_link() do +    GenServer.start_link(__MODULE__, [], name: __MODULE__) +  end + +  @doc "Reloads the emojis from disk." +  @spec reload() :: :ok +  def reload() do +    GenServer.call(__MODULE__, :reload) +  end + +  @doc "Returns the path of the emoji `name`." +  @spec get(String.t()) :: String.t() | nil +  def get(name) do +    case :ets.lookup(@ets, name) do +      [{_, path}] -> path +      _ -> nil +    end +  end + +  @doc "Returns all the emojos!!" +  @spec get_all() :: [{String.t(), String.t()}, ...] +  def get_all() do +    :ets.tab2list(@ets) +  end + +  @doc false +  def init(_) do +    @ets = :ets.new(@ets, @ets_options) +    GenServer.cast(self(), :reload) +    {:ok, nil} +  end + +  @doc false +  def handle_cast(:reload, state) do +    load() +    {:noreply, state} +  end + +  @doc false +  def handle_call(:reload, _from, state) do +    load() +    {:reply, :ok, state} +  end + +  @doc false +  def terminate(_, _) do +    :ok +  end + +  @doc false +  def code_change(_old_vsn, state, _extra) do +    load() +    {:ok, state} +  end + +  defp load() do +    emojis = +      (load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++ +         load_from_file("config/emoji.txt") ++ +         load_from_file("config/custom_emoji.txt") ++ +         load_from_globs( +           Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, []) +         )) +      |> Enum.reject(fn value -> value == nil end) + +    true = :ets.insert(@ets, emojis) +    :ok +  end + +  @finmoji [ +    "a_trusted_friend", +    "alandislands", +    "association", +    "auroraborealis", +    "baby_in_a_box", +    "bear", +    "black_gold", +    "christmasparty", +    "crosscountryskiing", +    "cupofcoffee", +    "education", +    "fashionista_finns", +    "finnishlove", +    "flag", +    "forest", +    "four_seasons_of_bbq", +    "girlpower", +    "handshake", +    "happiness", +    "headbanger", +    "icebreaker", +    "iceman", +    "joulutorttu", +    "kaamos", +    "kalsarikannit_f", +    "kalsarikannit_m", +    "karjalanpiirakka", +    "kicksled", +    "kokko", +    "lavatanssit", +    "losthopes_f", +    "losthopes_m", +    "mattinykanen", +    "meanwhileinfinland", +    "moominmamma", +    "nordicfamily", +    "out_of_office", +    "peacemaker", +    "perkele", +    "pesapallo", +    "polarbear", +    "pusa_hispida_saimensis", +    "reindeer", +    "sami", +    "sauna_f", +    "sauna_m", +    "sauna_whisk", +    "sisu", +    "stuck", +    "suomimainittu", +    "superfood", +    "swan", +    "the_cap", +    "the_conductor", +    "the_king", +    "the_voice", +    "theoriginalsanta", +    "tomoffinland", +    "torillatavataan", +    "unbreakable", +    "waiting", +    "white_nights", +    "woollysocks" +  ] +  defp load_finmoji(true) do +    Enum.map(@finmoji, fn finmoji -> +      {finmoji, "/finmoji/128px/#{finmoji}-128.png"} +    end) +  end + +  defp load_finmoji(_), do: [] + +  defp load_from_file(file) do +    if File.exists?(file) do +      load_from_file_stream(File.stream!(file)) +    else +      [] +    end +  end + +  defp load_from_file_stream(stream) do +    stream +    |> Stream.map(&String.strip/1) +    |> Stream.map(fn line -> +      case String.split(line, ~r/,\s*/) do +        [name, file] -> {name, file} +        _ -> nil +      end +    end) +    |> Enum.to_list() +  end + +  defp load_from_globs(globs) do +    static_path = Path.join(:code.priv_dir(:pleroma), "static") + +    paths = +      Enum.map(globs, fn glob -> +        Path.join(static_path, glob) +        |> Path.wildcard() +      end) +      |> Enum.concat() + +    Enum.map(paths, fn path -> +      shortcode = Path.basename(path, Path.extname(path)) +      external_path = Path.join("/", Path.relative_to(path, static_path)) +      {shortcode, external_path} +    end) +  end +end diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index ecc102b62..dd971df9b 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -2,6 +2,7 @@ defmodule Pleroma.Formatter do    alias Pleroma.User    alias Pleroma.Web.MediaProxy    alias Pleroma.HTML +  alias Pleroma.Emoji    @tag_regex ~r/\#\w+/u    def parse_tags(text, data \\ %{}) do @@ -28,125 +29,12 @@ defmodule Pleroma.Formatter do      |> Enum.filter(fn {_match, user} -> user end)    end -  @finmoji [ -    "a_trusted_friend", -    "alandislands", -    "association", -    "auroraborealis", -    "baby_in_a_box", -    "bear", -    "black_gold", -    "christmasparty", -    "crosscountryskiing", -    "cupofcoffee", -    "education", -    "fashionista_finns", -    "finnishlove", -    "flag", -    "forest", -    "four_seasons_of_bbq", -    "girlpower", -    "handshake", -    "happiness", -    "headbanger", -    "icebreaker", -    "iceman", -    "joulutorttu", -    "kaamos", -    "kalsarikannit_f", -    "kalsarikannit_m", -    "karjalanpiirakka", -    "kicksled", -    "kokko", -    "lavatanssit", -    "losthopes_f", -    "losthopes_m", -    "mattinykanen", -    "meanwhileinfinland", -    "moominmamma", -    "nordicfamily", -    "out_of_office", -    "peacemaker", -    "perkele", -    "pesapallo", -    "polarbear", -    "pusa_hispida_saimensis", -    "reindeer", -    "sami", -    "sauna_f", -    "sauna_m", -    "sauna_whisk", -    "sisu", -    "stuck", -    "suomimainittu", -    "superfood", -    "swan", -    "the_cap", -    "the_conductor", -    "the_king", -    "the_voice", -    "theoriginalsanta", -    "tomoffinland", -    "torillatavataan", -    "unbreakable", -    "waiting", -    "white_nights", -    "woollysocks" -  ] -    @instance Application.get_env(:pleroma, :instance) -  @finmoji_with_filenames (if Keyword.get(@instance, :finmoji_enabled) do -                             Enum.map(@finmoji, fn finmoji -> -                               {finmoji, "/finmoji/128px/#{finmoji}-128.png"} -                             end) -                           else -                             [] -                           end) - -  @emoji_from_file (with {:ok, default} <- File.read("config/emoji.txt") do -                      custom = -                        with {:ok, custom} <- File.read("config/custom_emoji.txt") do -                          custom -                        else -                          _e -> "" -                        end - -                      (default <> "\n" <> custom) -                      |> String.trim() -                      |> String.split(~r/\n+/) -                      |> Enum.map(fn line -> -                        [name, file] = String.split(line, ~r/,\s*/) -                        {name, file} -                      end) -                    else -                      _ -> [] -                    end) - -  @emoji_from_globs ( -                      static_path = Path.join(:code.priv_dir(:pleroma), "static") - -                      globs = -                        Application.get_env(:pleroma, :emoji, []) -                        |> Keyword.get(:shortcode_globs, []) - -                      paths = -                        Enum.map(globs, fn glob -> -                          Path.join(static_path, glob) -                          |> Path.wildcard() -                        end) -                        |> Enum.concat() - -                      Enum.map(paths, fn path -> -                        shortcode = Path.basename(path, Path.extname(path)) -                        external_path = Path.join("/", Path.relative_to(path, static_path)) -                        {shortcode, external_path} -                      end) -                    ) - -  @emoji @finmoji_with_filenames ++ @emoji_from_globs ++ @emoji_from_file +  def emojify(text) do +    emojify(text, Emoji.get_all()) +  end -  def emojify(text, emoji \\ @emoji)    def emojify(text, nil), do: text    def emojify(text, emoji) do @@ -166,15 +54,11 @@ defmodule Pleroma.Formatter do    end    def get_emoji(text) when is_binary(text) do -    Enum.filter(@emoji, fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end) +    Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)    end    def get_emoji(_), do: [] -  def get_custom_emoji() do -    @emoji -  end -    @link_regex ~r/[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+/ui    @uri_schemes Application.get_env(:pleroma, :uri_schemes, []) diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex new file mode 100644 index 000000000..4108d90af --- /dev/null +++ b/lib/pleroma/plugs/federating_plug.ex @@ -0,0 +1,18 @@ +defmodule Pleroma.Web.FederatingPlug do +  import Plug.Conn + +  def init(options) do +    options +  end + +  def call(conn, opts) do +    if Keyword.get(Application.get_env(:pleroma, :instance), :federating) do +      conn +    else +      conn +      |> put_status(404) +      |> Phoenix.Controller.render(Pleroma.Web.ErrorView, "404.json") +      |> halt() +    end +  end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 531e98237..3570a75cb 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -6,16 +6,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    alias Pleroma.Web.ActivityPub.Relay    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.Federator -  alias Pleroma.Config    require Logger    action_fallback(:errors) +  plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])    plug(:relay_active? when action in [:relay])    def relay_active?(conn, _) do -    if Config.get([:instance, :allow_relay]) do +    if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do        conn      else        conn diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 9ea2507a1..01c2c89c3 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.Federator do    alias Pleroma.Web.ActivityPub.Relay    alias Pleroma.Web.ActivityPub.Transmogrifier    alias Pleroma.Web.ActivityPub.Utils -  alias Pleroma.Config    require Logger    @websub Application.get_env(:pleroma, :websub) @@ -72,7 +71,7 @@ defmodule Pleroma.Web.Federator do          Logger.info(fn -> "Sending #{activity.data["id"]} out via Salmon" end)          Pleroma.Web.Salmon.publish(actor, activity) -        if Config.get([:instance, :allow_relay]) do +        if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do            Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)            Relay.publish(activity)          end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index f6cf081fd..5cb007740 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -158,7 +158,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    defp mastodonized_emoji do -    Pleroma.Formatter.get_custom_emoji() +    Pleroma.Emoji.get_all()      |> Enum.map(fn {shortcode, relative_url} ->        url = to_string(URI.merge(Web.base_url(), relative_url)) @@ -985,9 +985,29 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end +  def login(conn, %{"code" => code}) do +    with {:ok, app} <- get_or_make_app(), +         %Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id), +         {:ok, token} <- Token.exchange_token(app, auth) do +      conn +      |> put_session(:oauth_token, token.token) +      |> redirect(to: "/web/getting-started") +    end +  end +    def login(conn, _) do -    conn -    |> render(MastodonView, "login.html", %{error: false}) +    with {:ok, app} <- get_or_make_app() do +      path = +        o_auth_path(conn, :authorize, +          response_type: "code", +          client_id: app.client_id, +          redirect_uri: ".", +          scope: app.scopes +        ) + +      conn +      |> redirect(to: path) +    end    end    defp get_or_make_app() do @@ -1006,22 +1026,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end -  def login_post(conn, %{"authorization" => %{"name" => name, "password" => password}}) do -    with %User{} = user <- User.get_by_nickname_or_email(name), -         true <- Pbkdf2.checkpw(password, user.password_hash), -         {:ok, app} <- get_or_make_app(), -         {:ok, auth} <- Authorization.create_authorization(app, user), -         {:ok, token} <- Token.exchange_token(app, auth) do -      conn -      |> put_session(:oauth_token, token.token) -      |> redirect(to: "/web/getting-started") -    else -      _e -> -        conn -        |> render(MastodonView, "login.html", %{error: "Wrong username or password"}) -    end -  end -    def logout(conn, _) do      conn      |> clear_session diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 5446179cb..d58f08881 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -6,6 +6,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do    alias Pleroma.{User, Repo}    alias Pleroma.Web.ActivityPub.MRF +  plug(Pleroma.Web.FederatingPlug) +    def schemas(conn, _params) do      response = %{        links: [ diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 5441ee0a8..35c158fbb 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -33,25 +33,35 @@ defmodule Pleroma.Web.OAuth.OAuthController do           true <- Pbkdf2.checkpw(password, user.password_hash),           %App{} = app <- Repo.get_by(App, client_id: client_id),           {:ok, auth} <- Authorization.create_authorization(app, user) do -      if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do -        render(conn, "results.html", %{ -          auth: auth -        }) -      else -        connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?" -        url = "#{redirect_uri}#{connector}" -        url_params = %{:code => auth.token} - -        url_params = -          if params["state"] do -            Map.put(url_params, :state, params["state"]) -          else -            url_params -          end - -        url = "#{url}#{Plug.Conn.Query.encode(url_params)}" - -        redirect(conn, external: url) +      # Special case: Local MastodonFE. +      redirect_uri = +        if redirect_uri == "." do +          mastodon_api_url(conn, :login) +        else +          redirect_uri +        end + +      cond do +        redirect_uri == "urn:ietf:wg:oauth:2.0:oob" -> +          render(conn, "results.html", %{ +            auth: auth +          }) + +        true -> +          connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?" +          url = "#{redirect_uri}#{connector}" +          url_params = %{:code => auth.token} + +          url_params = +            if params["state"] do +              Map.put(url_params, :state, params["state"]) +            else +              url_params +            end + +          url = "#{url}#{Plug.Conn.Query.encode(url_params)}" + +          redirect(conn, external: url)        end      end    end diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 09d1b1110..2f92935e7 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do    alias Pleroma.Web.ActivityPub.ActivityPubController    alias Pleroma.Web.ActivityPub.ActivityPub +  plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])    action_fallback(:errors)    def feed_redirect(conn, %{"nickname" => nickname}) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 7b7affe5e..b461def82 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -3,11 +3,6 @@ defmodule Pleroma.Web.Router do    alias Pleroma.{Repo, User, Web.Router} -  @instance Application.get_env(:pleroma, :instance) -  @federating Keyword.get(@instance, :federating) -  @public Keyword.get(@instance, :public) -  @registrations_open Keyword.get(@instance, :registrations_open) -    pipeline :api do      plug(:accepts, ["json"])      plug(:fetch_session) @@ -242,11 +237,7 @@ defmodule Pleroma.Web.Router do    end    scope "/api", Pleroma.Web do -    if @public do -      pipe_through(:api) -    else -      pipe_through(:authenticated_api) -    end +    pipe_through(:api)      get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline) @@ -330,12 +321,10 @@ defmodule Pleroma.Web.Router do      get("/users/:nickname/feed", OStatus.OStatusController, :feed)      get("/users/:nickname", OStatus.OStatusController, :feed_redirect) -    if @federating do -      post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming) -      post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request) -      get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation) -      post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming) -    end +    post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming) +    post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request) +    get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation) +    post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)    end    pipeline :activitypub do @@ -352,29 +341,27 @@ defmodule Pleroma.Web.Router do      get("/users/:nickname/outbox", ActivityPubController, :outbox)    end -  if @federating do -    scope "/relay", Pleroma.Web.ActivityPub do -      pipe_through(:ap_relay) -      get("/", ActivityPubController, :relay) -    end +  scope "/relay", Pleroma.Web.ActivityPub do +    pipe_through(:ap_relay) +    get("/", ActivityPubController, :relay) +  end -    scope "/", Pleroma.Web.ActivityPub do -      pipe_through(:activitypub) -      post("/users/:nickname/inbox", ActivityPubController, :inbox) -      post("/inbox", ActivityPubController, :inbox) -    end +  scope "/", Pleroma.Web.ActivityPub do +    pipe_through(:activitypub) +    post("/users/:nickname/inbox", ActivityPubController, :inbox) +    post("/inbox", ActivityPubController, :inbox) +  end -    scope "/.well-known", Pleroma.Web do -      pipe_through(:well_known) +  scope "/.well-known", Pleroma.Web do +    pipe_through(:well_known) -      get("/host-meta", WebFinger.WebFingerController, :host_meta) -      get("/webfinger", WebFinger.WebFingerController, :webfinger) -      get("/nodeinfo", Nodeinfo.NodeinfoController, :schemas) -    end +    get("/host-meta", WebFinger.WebFingerController, :host_meta) +    get("/webfinger", WebFinger.WebFingerController, :webfinger) +    get("/nodeinfo", Nodeinfo.NodeinfoController, :schemas) +  end -    scope "/nodeinfo", Pleroma.Web do -      get("/:version", Nodeinfo.NodeinfoController, :nodeinfo) -    end +  scope "/nodeinfo", Pleroma.Web do +    get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)    end    scope "/", Pleroma.Web.MastodonAPI do diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex deleted file mode 100644 index 34cd7ed89..000000000 --- a/lib/pleroma/web/templates/mastodon_api/mastodon/login.html.eex +++ /dev/null @@ -1,11 +0,0 @@ -<h2>Login to Mastodon Frontend</h2> -<%= if @error do %> -  <h2><%= @error %></h2> -<% end %> -<%= form_for @conn, mastodon_api_path(@conn, :login), [as: "authorization"], fn f -> %> -<%= text_input f, :name, placeholder: "Username or email" %> -<br> -<%= password_input f, :password, placeholder: "Password" %> -<br> -<%= submit "Log in" %> -<% end %> diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 01cd17121..e84438e97 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    alias Pleroma.Web.WebFinger    alias Pleroma.Web.CommonAPI    alias Comeonin.Pbkdf2 -  alias Pleroma.Formatter +  alias Pleroma.{Formatter, Emoji}    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.{Repo, PasswordResetToken, User} @@ -212,7 +212,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    end    def emoji(conn, _params) do -    json(conn, Enum.into(Formatter.get_custom_emoji(), %{})) +    json(conn, Enum.into(Emoji.get_all(), %{}))    end    def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 7153a2bd6..83d725f13 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do    require Logger +  plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])    action_fallback(:errors)    def verify_credentials(%{assigns: %{user: user}} = conn, _params) do @@ -518,6 +519,18 @@ defmodule Pleroma.Web.TwitterAPI.Controller do      json_reply(conn, 403, json)    end +  def only_if_public_instance(conn = %{conn: %{assigns: %{user: _user}}}, _), do: conn + +  def only_if_public_instance(conn, _) do +    if Keyword.get(Application.get_env(:pleroma, :instance), :public) do +      conn +    else +      conn +      |> forbidden_json_reply("Invalid credentials.") +      |> halt() +    end +  end +    defp error_json(conn, error_message) do      %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()    end diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex index 50d816256..002353166 100644 --- a/lib/pleroma/web/web_finger/web_finger_controller.ex +++ b/lib/pleroma/web/web_finger/web_finger_controller.ex @@ -3,6 +3,8 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do    alias Pleroma.Web.WebFinger +  plug(Pleroma.Web.FederatingPlug) +    def host_meta(conn, _params) do      xml = WebFinger.host_meta() diff --git a/lib/pleroma/web/websub/websub_controller.ex b/lib/pleroma/web/websub/websub_controller.ex index 590dd74a1..c1934ba92 100644 --- a/lib/pleroma/web/websub/websub_controller.ex +++ b/lib/pleroma/web/websub/websub_controller.ex @@ -5,6 +5,15 @@ defmodule Pleroma.Web.Websub.WebsubController do    alias Pleroma.Web.Websub.WebsubClientSubscription    require Logger +  plug( +    Pleroma.Web.FederatingPlug +    when action in [ +           :websub_subscription_request, +           :websub_subscription_confirmation, +           :websub_incoming +         ] +  ) +    def websub_subscription_request(conn, %{"nickname" => nickname} = params) do      user = User.get_cached_by_nickname(nickname) diff --git a/test/config_test.exs b/test/config_test.exs index 6d0f0a2d4..32d5cc90c 100644 --- a/test/config_test.exs +++ b/test/config_test.exs @@ -1,10 +1,39 @@  defmodule Pleroma.ConfigTest do -  use Pleroma.DataCase -  alias Pleroma.Config +  use ExUnit.Case -  test "get returns the item at the path if there is one" do -    Config.put([:instance, :name], "Plemora") -    assert Config.get([:instance, :name]) == "Plemora" -    assert Config.get([:unknown]) == nil +  test "get/1 with an atom" do +    assert Pleroma.Config.get(:instance) == Application.get_env(:pleroma, :instance) +    assert Pleroma.Config.get(:azertyuiop) == nil +  end + +  test "get/1 with a list of keys" do +    assert Pleroma.Config.get([:instance, :public]) == +             Keyword.get(Application.get_env(:pleroma, :instance), :public) + +    assert Pleroma.Config.get([Pleroma.Web.Endpoint, :render_errors, :view]) == +             get_in( +               Application.get_env( +                 :pleroma, +                 Pleroma.Web.Endpoint +               ), +               [:render_errors, :view] +             ) + +    assert Pleroma.Config.get([:azerty, :uiop]) == nil +  end + +  test "put/2 with a key" do +    Pleroma.Config.put(:config_test, true) + +    assert Pleroma.Config.get(:config_test) == true +  end + +  test "put/2 with a list of keys" do +    Pleroma.Config.put([:instance, :config_test], true) +    Pleroma.Config.put([:instance, :config_nested_test], []) +    Pleroma.Config.put([:instance, :config_nested_test, :x], true) + +    assert Pleroma.Config.get([:instance, :config_test]) == true +    assert Pleroma.Config.get([:instance, :config_nested_test, :x]) == true    end  end diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 5b46bbe76..1c24b348c 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -4,12 +4,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do    alias Pleroma.Web.ActivityPub.{UserView, ObjectView}    alias Pleroma.{Repo, User}    alias Pleroma.Activity -  alias Pleroma.Config    describe "/relay" do      test "with the relay active, it returns the relay user", %{conn: conn} do -      Config.put([:instance, :allow_relay], true) -        res =          conn          |> get(activity_pub_path(conn, :relay)) @@ -19,12 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      end      test "with the relay disabled, it returns 404", %{conn: conn} do -      Config.put([:instance, :allow_relay], false) +      Pleroma.Config.put([:instance, :allow_relay], false)        res =          conn          |> get(activity_pub_path(conn, :relay))          |> json_response(404) + +      Pleroma.Config.put([:instance, :allow_relay], true)      end    end diff --git a/test/web/federator_test.exs b/test/web/federator_test.exs index 966702935..c709d1181 100644 --- a/test/web/federator_test.exs +++ b/test/web/federator_test.exs @@ -1,7 +1,6 @@  defmodule Pleroma.Web.FederatorTest do    alias Pleroma.Web.Federator    alias Pleroma.Web.CommonAPI -  alias Pleroma.Config    use Pleroma.DataCase    import Pleroma.Factory    import Mock @@ -40,8 +39,6 @@ defmodule Pleroma.Web.FederatorTest do        activity: activity,        relay_mock: relay_mock      } do -      Config.put([:instance, :allow_relay], true) -        with_mocks([relay_mock]) do          Federator.handle(:publish, activity)        end @@ -53,13 +50,15 @@ defmodule Pleroma.Web.FederatorTest do        activity: activity,        relay_mock: relay_mock      } do -      Config.put([:instance, :allow_relay], false) +      Pleroma.Config.put([:instance, :allow_relay], false)        with_mocks([relay_mock]) do          Federator.handle(:publish, activity)        end        refute_received :relay_publish + +      Pleroma.Config.put([:instance, :allow_relay], true)      end    end  end diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index d48f40e47..a6376453c 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -14,4 +14,36 @@ defmodule Pleroma.Web.NodeInfoTest do      assert user.ap_id in result["metadata"]["staffAccounts"]    end + +  test "returns 404 when federation is disabled" do +    instance = +      Application.get_env(:pleroma, :instance) +      |> Keyword.put(:federating, false) + +    Application.put_env(:pleroma, :instance, instance) + +    conn +    |> get("/.well-known/nodeinfo") +    |> json_response(404) + +    conn +    |> get("/nodeinfo/2.0.json") +    |> json_response(404) + +    instance = +      Application.get_env(:pleroma, :instance) +      |> Keyword.put(:federating, true) + +    Application.put_env(:pleroma, :instance, instance) +  end + +  test "returns 200 when federation is enabled" do +    conn +    |> get("/.well-known/nodeinfo") +    |> json_response(200) + +    conn +    |> get("/nodeinfo/2.0.json") +    |> json_response(200) +  end  end diff --git a/test/web/plugs/federating_plug_test.exs b/test/web/plugs/federating_plug_test.exs new file mode 100644 index 000000000..1455a1c46 --- /dev/null +++ b/test/web/plugs/federating_plug_test.exs @@ -0,0 +1,33 @@ +defmodule Pleroma.Web.FederatingPlugTest do +  use Pleroma.Web.ConnCase + +  test "returns and halt the conn when federating is disabled" do +    instance = +      Application.get_env(:pleroma, :instance) +      |> Keyword.put(:federating, false) + +    Application.put_env(:pleroma, :instance, instance) + +    conn = +      build_conn() +      |> Pleroma.Web.FederatingPlug.call(%{}) + +    assert conn.status == 404 +    assert conn.halted + +    instance = +      Application.get_env(:pleroma, :instance) +      |> Keyword.put(:federating, true) + +    Application.put_env(:pleroma, :instance, instance) +  end + +  test "does nothing when federating is enabled" do +    conn = +      build_conn() +      |> Pleroma.Web.FederatingPlug.call(%{}) + +    refute conn.status +    refute conn.halted +  end +end diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index 87bcdaf71..b64f416e3 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -100,6 +100,56 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        assert length(response) == 10      end + +    test "returns 403 to unauthenticated request when the instance is not public" do +      instance = +        Application.get_env(:pleroma, :instance) +        |> Keyword.put(:public, false) + +      Application.put_env(:pleroma, :instance, instance) + +      conn +      |> get("/api/statuses/public_timeline.json") +      |> json_response(403) + +      instance = +        Application.get_env(:pleroma, :instance) +        |> Keyword.put(:public, true) + +      Application.put_env(:pleroma, :instance, instance) +    end + +    test "returns 200 to unauthenticated request when the instance is public" do +      conn +      |> get("/api/statuses/public_timeline.json") +      |> json_response(200) +    end +  end + +  describe "GET /statuses/public_and_external_timeline.json" do +    test "returns 403 to unauthenticated request when the instance is not public" do +      instance = +        Application.get_env(:pleroma, :instance) +        |> Keyword.put(:public, false) + +      Application.put_env(:pleroma, :instance, instance) + +      conn +      |> get("/api/statuses/public_and_external_timeline.json") +      |> json_response(403) + +      instance = +        Application.get_env(:pleroma, :instance) +        |> Keyword.put(:public, true) + +      Application.put_env(:pleroma, :instance, instance) +    end + +    test "returns 200 to unauthenticated request when the instance is public" do +      conn +      |> get("/api/statuses/public_and_external_timeline.json") +      |> json_response(200) +    end    end    describe "GET /statuses/show/:id.json" do  | 
