diff options
Diffstat (limited to 'lib')
33 files changed, 672 insertions, 304 deletions
| diff --git a/lib/mix/tasks/pleroma/sample_psql.eex b/lib/mix/tasks/pleroma/sample_psql.eex index 66f76752f..c89b34ef2 100644 --- a/lib/mix/tasks/pleroma/sample_psql.eex +++ b/lib/mix/tasks/pleroma/sample_psql.eex @@ -1,9 +1,7 @@ -CREATE USER <%= dbuser %> WITH ENCRYPTED PASSWORD '<%= dbpass %>' CREATEDB; --- in case someone runs this second time accidentally -ALTER USER <%= dbuser %> WITH ENCRYPTED PASSWORD '<%= dbpass %>' CREATEDB; -CREATE DATABASE <%= dbname %>; -ALTER DATABASE <%= dbname %> OWNER TO <%= dbuser %>; -\c <%= dbname %>; +CREATE USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>'; +CREATE DATABASE pleroma_dev OWNER pleroma; +\c pleroma_dev;  --Extensions made by ecto.migrate that need superuser access  CREATE EXTENSION IF NOT EXISTS citext;  CREATE EXTENSION IF NOT EXISTS pg_trgm; +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 26bb17377..1a5c07c8a 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -116,8 +116,8 @@ defmodule Pleroma.Formatter do        subs ++          Enum.map(mentions, fn {match, %User{ap_id: ap_id, info: info}, uuid} ->            ap_id = -            if is_binary(info["source_data"]["url"]) do -              info["source_data"]["url"] +            if is_binary(info.source_data["url"]) do +              info.source_data["url"]              else                ap_id              end diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex new file mode 100644 index 000000000..5e8f2aabd --- /dev/null +++ b/lib/pleroma/http/connection.ex @@ -0,0 +1,27 @@ +defmodule Pleroma.HTTP.Connection do +  @moduledoc """ +  Connection for http-requests. +  """ + +  @hackney_options [pool: :default] +  @adapter Application.get_env(:tesla, :adapter) + +  @doc """ +  Configure a client connection + +  # Returns + +  Tesla.Env.client +  """ +  @spec new(Keyword.t()) :: Tesla.Env.client() +  def new(opts \\ []) do +    Tesla.client([], {@adapter, hackney_options(opts)}) +  end + +  # fetch Hackney options +  # +  defp hackney_options(opts \\ []) do +    options = Keyword.get(opts, :adapter, []) +    @hackney_options ++ options +  end +end diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex index e64266ae7..3c0256575 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http/http.ex @@ -1,14 +1,42 @@  defmodule Pleroma.HTTP do -  require HTTPoison +  @moduledoc """ +  """ + +  alias Pleroma.HTTP.Connection +  alias Pleroma.HTTP.RequestBuilder, as: Builder + +  @doc """ +  Builds and perform http request. + +  # Arguments: +  `method` - :get, :post, :put, :delete +  `url` +  `body` +  `headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]` +  `options` - custom, per-request middleware or adapter options + +  # Returns: +  `{:ok, %Tesla.Env{}}` or `{:error, error}` + +  """    def request(method, url, body \\ "", headers \\ [], options \\ []) do      options =        process_request_options(options)        |> process_sni_options(url) -    HTTPoison.request(method, url, body, headers, options) +    %{} +    |> Builder.method(method) +    |> Builder.headers(headers) +    |> Builder.opts(options) +    |> Builder.url(url) +    |> Builder.add_param(:body, :body, body) +    |> Enum.into([]) +    |> (&Tesla.request(Connection.new(), &1)).()    end +  defp process_sni_options(options, nil), do: options +    defp process_sni_options(options, url) do      uri = URI.parse(url)      host = uri.host |> to_charlist() @@ -22,7 +50,7 @@ defmodule Pleroma.HTTP do    def process_request_options(options) do      config = Application.get_env(:pleroma, :http, [])      proxy = Keyword.get(config, :proxy_url, nil) -    options = options ++ [hackney: [pool: :default]] +    options = options ++ [adapter: [pool: :default]]      case proxy do        nil -> options @@ -30,8 +58,19 @@ defmodule Pleroma.HTTP do      end    end -  def get(url, headers \\ [], options \\ []), do: request(:get, url, "", headers, options) +  @doc """ +  Performs GET request. + +  See `Pleroma.HTTP.request/5` +  """ +  def get(url, headers \\ [], options \\ []), +    do: request(:get, url, "", headers, options) + +  @doc """ +  Performs POST request. +  See `Pleroma.HTTP.request/5` +  """    def post(url, body, headers \\ [], options \\ []),      do: request(:post, url, body, headers, options)  end diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex new file mode 100644 index 000000000..5aee2b8ae --- /dev/null +++ b/lib/pleroma/http/request_builder.ex @@ -0,0 +1,126 @@ +defmodule Pleroma.HTTP.RequestBuilder do +  @moduledoc """ +  Helper functions for building Tesla requests +  """ + +  @doc """ +  Specify the request method when building a request + +  ## Parameters + +  - request (Map) - Collected request options +  - m (atom) - Request method + +  ## Returns + +  Map +  """ +  @spec method(map(), atom) :: map() +  def method(request, m) do +    Map.put_new(request, :method, m) +  end + +  @doc """ +  Specify the request method when building a request + +  ## Parameters + +  - request (Map) - Collected request options +  - u (String) - Request URL + +  ## Returns + +  Map +  """ +  @spec url(map(), String.t()) :: map() +  def url(request, u) do +    Map.put_new(request, :url, u) +  end + +  @doc """ +  Add headers to the request +  """ +  @spec headers(map(), list(tuple)) :: map() +  def headers(request, h) do +    Map.put_new(request, :headers, h) +  end + +  @doc """ +  Add custom, per-request middleware or adapter options to the request +  """ +  @spec opts(map(), Keyword.t()) :: map() +  def opts(request, options) do +    Map.put_new(request, :opts, options) +  end + +  @doc """ +  Add optional parameters to the request + +  ## Parameters + +  - request (Map) - Collected request options +  - definitions (Map) - Map of parameter name to parameter location. +  - options (KeywordList) - The provided optional parameters + +  ## Returns + +  Map +  """ +  @spec add_optional_params(map(), %{optional(atom) => atom}, keyword()) :: map() +  def add_optional_params(request, _, []), do: request + +  def add_optional_params(request, definitions, [{key, value} | tail]) do +    case definitions do +      %{^key => location} -> +        request +        |> add_param(location, key, value) +        |> add_optional_params(definitions, tail) + +      _ -> +        add_optional_params(request, definitions, tail) +    end +  end + +  @doc """ +  Add optional parameters to the request + +  ## Parameters + +  - request (Map) - Collected request options +  - location (atom) - Where to put the parameter +  - key (atom) - The name of the parameter +  - value (any) - The value of the parameter + +  ## Returns + +  Map +  """ +  @spec add_param(map(), atom, atom, any()) :: map() +  def add_param(request, :body, :body, value), do: Map.put(request, :body, value) + +  def add_param(request, :body, key, value) do +    request +    |> Map.put_new_lazy(:body, &Tesla.Multipart.new/0) +    |> Map.update!( +      :body, +      &Tesla.Multipart.add_field(&1, key, Poison.encode!(value), +        headers: [{:"Content-Type", "application/json"}] +      ) +    ) +  end + +  def add_param(request, :file, name, path) do +    request +    |> Map.put_new_lazy(:body, &Tesla.Multipart.new/0) +    |> Map.update!(:body, &Tesla.Multipart.add_file(&1, path, name: name)) +  end + +  def add_param(request, :form, name, value) do +    request +    |> Map.update(:body, %{name => value}, &Map.put(&1, name, value)) +  end + +  def add_param(request, location, key, value) do +    Map.update(request, location, [{key, value}], &(&1 ++ [{key, value}])) +  end +end diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex index 0380ce14d..630f15eec 100644 --- a/lib/pleroma/plugs/oauth_plug.ex +++ b/lib/pleroma/plugs/oauth_plug.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Plugs.OAuthPlug do      with token when not is_nil(token) <- token,           %Token{user_id: user_id} <- Repo.get_by(Token, token: token),           %User{} = user <- Repo.get(User, user_id), -         false <- !!user.info["deactivated"] do +         false <- !!user.info.deactivated do        conn        |> assign(:user, user)      else diff --git a/lib/pleroma/plugs/user_enabled_plug.ex b/lib/pleroma/plugs/user_enabled_plug.ex index 9c3285896..01482f47d 100644 --- a/lib/pleroma/plugs/user_enabled_plug.ex +++ b/lib/pleroma/plugs/user_enabled_plug.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Plugs.UserEnabledPlug do      options    end -  def call(%{assigns: %{user: %User{info: %{"deactivated" => true}}}} = conn, _) do +  def call(%{assigns: %{user: %User{info: %{deactivated: true}}}} = conn, _) do      conn      |> assign(:user, nil)    end diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex index 5312f1499..cf22ce5d0 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/plugs/user_is_admin_plug.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do      options    end -  def call(%{assigns: %{user: %User{info: %{"is_admin" => true}}}} = conn, _) do +  def call(%{assigns: %{user: %User{info: %{is_admin: true}}}} = conn, _) do      conn    end diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex index 35d36d3e4..820cf88f5 100644 --- a/lib/pleroma/uploaders/mdii.ex +++ b/lib/pleroma/uploaders/mdii.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Uploaders.MDII do      extension = String.split(upload.name, ".") |> List.last()      query = "#{cgi}?#{extension}" -    with {:ok, %{status_code: 200, body: body}} <- @httpoison.post(query, file_data) do +    with {:ok, %{status: 200, body: body}} <- @httpoison.post(query, file_data) do        remote_file_name = String.split(body) |> List.first()        public_url = "#{files}/#{remote_file_name}.#{extension}"        {:ok, {:url, public_url}} diff --git a/lib/pleroma/uploaders/swift/keystone.ex b/lib/pleroma/uploaders/swift/keystone.ex index e578b3c61..4aed977b1 100644 --- a/lib/pleroma/uploaders/swift/keystone.ex +++ b/lib/pleroma/uploaders/swift/keystone.ex @@ -25,10 +25,10 @@ defmodule Pleroma.Uploaders.Swift.Keystone do             ["Content-Type": "application/json"],             hackney: [:insecure]           ) do -      {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> +      {:ok, %Tesla.Env{status: 200, body: body}} ->          body["access"]["token"]["id"] -      {:ok, %HTTPoison.Response{status_code: _}} -> +      {:ok, %Tesla.Env{status: _}} ->          ""      end    end diff --git a/lib/pleroma/uploaders/swift/swift.ex b/lib/pleroma/uploaders/swift/swift.ex index 1e865f101..a5b3d2852 100644 --- a/lib/pleroma/uploaders/swift/swift.ex +++ b/lib/pleroma/uploaders/swift/swift.ex @@ -13,10 +13,10 @@ defmodule Pleroma.Uploaders.Swift.Client do      token = Pleroma.Uploaders.Swift.Keystone.get_token()      case put("#{filename}", body, "X-Auth-Token": token, "Content-Type": content_type) do -      {:ok, %HTTPoison.Response{status_code: 201}} -> +      {:ok, %Tesla.Env{status: 201}} ->          {:ok, {:file, filename}} -      {:ok, %HTTPoison.Response{status_code: 401}} -> +      {:ok, %Tesla.Env{status: 401}} ->          {:error, "Unauthorized, Bad Token"}        {:error, _} -> diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 6e1d5559d..74ae5ef0d 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -4,6 +4,8 @@ defmodule Pleroma.User do    import Ecto.{Changeset, Query}    alias Pleroma.{Repo, User, Object, Web, Activity, Notification}    alias Comeonin.Pbkdf2 +  alias Pleroma.Formatter +  alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils    alias Pleroma.Web.{OStatus, Websub, OAuth}    alias Pleroma.Web.ActivityPub.{Utils, ActivityPub} @@ -19,11 +21,11 @@ defmodule Pleroma.User do      field(:ap_id, :string)      field(:avatar, :map)      field(:local, :boolean, default: true) -    field(:info, :map, default: %{})      field(:follower_address, :string)      field(:search_distance, :float, virtual: true)      field(:last_refreshed_at, :naive_datetime)      has_many(:notifications, Notification) +    embeds_one(:info, Pleroma.User.Info)      timestamps()    end @@ -36,13 +38,13 @@ defmodule Pleroma.User do    end    def banner_url(user) do -    case user.info["banner"] do +    case user.info.banner do        %{"url" => [%{"href" => href} | _]} -> href        _ -> "#{Web.base_url()}/images/banner.png"      end    end -  def profile_url(%User{info: %{"source_data" => %{"url" => url}}}), do: url +  def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url    def profile_url(%User{ap_id: ap_id}), do: ap_id    def profile_url(_), do: nil @@ -61,9 +63,7 @@ defmodule Pleroma.User do    end    def info_changeset(struct, params \\ %{}) do -    struct -    |> cast(params, [:info]) -    |> validate_required([:info]) +    raise "NOT VALID ANYMORE"    end    def user_info(%User{} = user) do @@ -71,27 +71,34 @@ defmodule Pleroma.User do      %{        following_count: length(user.following) - oneself, -      note_count: user.info["note_count"] || 0, -      follower_count: user.info["follower_count"] || 0, -      locked: user.info["locked"] || false, -      default_scope: user.info["default_scope"] || "public" +      note_count: user.info.note_count, +      follower_count: user.info.follower_count, +      locked: user.info.locked, +      default_scope: user.info.default_scope      }    end    @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/    def remote_user_creation(params) do +    params = +      params +      |> Map.put(:info, params[:info] || %{}) + +    info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info]) +      changes =        %User{} -      |> cast(params, [:bio, :name, :ap_id, :nickname, :info, :avatar]) +      |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])        |> validate_required([:name, :ap_id])        |> unique_constraint(:nickname)        |> validate_format(:nickname, @email_regex)        |> validate_length(:bio, max: 5000)        |> validate_length(:name, max: 100)        |> put_change(:local, false) +      |> put_embed(:info, info_cng)      if changes.valid? do -      case changes.changes[:info]["source_data"] do +      case info_cng.changes[:source_data] do          %{"followers" => followers} ->            changes            |> put_change(:follower_address, followers) @@ -109,7 +116,7 @@ defmodule Pleroma.User do    def update_changeset(struct, params \\ %{}) do      struct -    |> cast(params, [:bio, :name]) +    |> cast(params, [:bio, :name, :avatar])      |> unique_constraint(:nickname)      |> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)      |> validate_length(:bio, max: 5000) @@ -121,12 +128,17 @@ defmodule Pleroma.User do        params        |> Map.put(:last_refreshed_at, NaiveDateTime.utc_now()) +    info_cng = +      struct.info +      |> User.Info.user_upgrade(params[:info]) +      struct -    |> cast(params, [:bio, :name, :info, :follower_address, :avatar, :last_refreshed_at]) +    |> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at])      |> unique_constraint(:nickname)      |> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)      |> validate_length(:bio, max: 5000)      |> validate_length(:name, max: 100) +    |> put_embed(:info, info_cng)    end    def password_update_changeset(struct, params) do @@ -165,6 +177,7 @@ defmodule Pleroma.User do        |> validate_format(:email, @email_regex)        |> validate_length(:bio, max: 1000)        |> validate_length(:name, min: 1, max: 100) +      |> put_change(:info, %Pleroma.User.Info{})      if changeset.valid? do        hashed = Pbkdf2.hashpwsalt(changeset.changes[:password]) @@ -191,7 +204,7 @@ defmodule Pleroma.User do    def needs_update?(_), do: true -  def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{"locked" => true}}) do +  def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{locked: true}}) do      {:ok, follower}    end @@ -222,7 +235,7 @@ defmodule Pleroma.User do      ap_followers = followed.follower_address      cond do -      following?(follower, followed) or info["deactivated"] -> +      following?(follower, followed) or info.deactivated ->          {:error, "Could not follow user: #{followed.nickname} is already on your list."}        deny_follow_blocked and blocks?(followed, follower) -> @@ -274,7 +287,7 @@ defmodule Pleroma.User do    end    def locked?(%User{} = user) do -    user.info["locked"] || false +    user.info.locked || false    end    def get_by_ap_id(ap_id) do @@ -411,22 +424,23 @@ defmodule Pleroma.User do    end    def increase_note_count(%User{} = user) do -    note_count = (user.info["note_count"] || 0) + 1 -    new_info = Map.put(user.info, "note_count", note_count) +    info_cng = User.Info.add_to_note_count(user.info, 1) -    cs = info_changeset(user, %{info: new_info}) +    cng = +      change(user) +      |> put_embed(:info, info_cng) -    update_and_set_cache(cs) +    update_and_set_cache(cng)    end    def decrease_note_count(%User{} = user) do -    note_count = user.info["note_count"] || 0 -    note_count = if note_count <= 0, do: 0, else: note_count - 1 -    new_info = Map.put(user.info, "note_count", note_count) +    info_cng = User.Info.add_to_note_count(user.info, -1) -    cs = info_changeset(user, %{info: new_info}) +    cng = +      change(user) +      |> put_embed(:info, info_cng) -    update_and_set_cache(cs) +    update_and_set_cache(cng)    end    def update_note_count(%User{} = user) do @@ -439,11 +453,13 @@ defmodule Pleroma.User do      note_count = Repo.one(note_count_query) -    new_info = Map.put(user.info, "note_count", note_count) +    info_cng = User.Info.set_note_count(user.info, note_count) -    cs = info_changeset(user, %{info: new_info}) +    cng = +      change(user) +      |> put_embed(:info, info_cng) -    update_and_set_cache(cs) +    update_and_set_cache(cng)    end    def update_follower_count(%User{} = user) do @@ -457,11 +473,15 @@ defmodule Pleroma.User do      follower_count = Repo.one(follower_count_query) -    new_info = Map.put(user.info, "follower_count", follower_count) +    info_cng = +      user.info +      |> User.Info.set_follower_count(follower_count) -    cs = info_changeset(user, %{info: new_info}) +    cng = +      change(user) +      |> put_embed(:info, info_cng) -    update_and_set_cache(cs) +    update_and_set_cache(cng)    end    def get_users_from_set_query(ap_ids, false) do @@ -545,12 +565,15 @@ defmodule Pleroma.User do        unfollow(blocked, blocker)      end -    blocks = blocker.info["blocks"] || [] -    new_blocks = Enum.uniq([ap_id | blocks]) -    new_info = Map.put(blocker.info, "blocks", new_blocks) +    info_cng = +      blocker.info +      |> User.Info.add_to_block(ap_id) + +    cng = +      change(blocker) +      |> put_embed(:info, info_cng) -    cs = User.info_changeset(blocker, %{info: new_info}) -    update_and_set_cache(cs) +    update_and_set_cache(cng)    end    # helper to handle the block given only an actor's AP id @@ -558,18 +581,21 @@ defmodule Pleroma.User do      block(blocker, User.get_by_ap_id(ap_id))    end -  def unblock(user, %{ap_id: ap_id}) do -    blocks = user.info["blocks"] || [] -    new_blocks = List.delete(blocks, ap_id) -    new_info = Map.put(user.info, "blocks", new_blocks) +  def unblock(blocker, %{ap_id: ap_id}) do +    info_cng = +      blocker.info +      |> User.Info.remove_from_block(ap_id) -    cs = User.info_changeset(user, %{info: new_info}) -    update_and_set_cache(cs) +    cng = +      change(blocker) +      |> put_embed(:info, info_cng) + +    update_and_set_cache(cng)    end    def blocks?(user, %{ap_id: ap_id}) do -    blocks = user.info["blocks"] || [] -    domain_blocks = user.info["domain_blocks"] || [] +    blocks = user.info.blocks +    domain_blocks = user.info.domain_blocks      %{host: host} = URI.parse(ap_id)      Enum.member?(blocks, ap_id) || @@ -579,21 +605,27 @@ defmodule Pleroma.User do    end    def block_domain(user, domain) do -    domain_blocks = user.info["domain_blocks"] || [] -    new_blocks = Enum.uniq([domain | domain_blocks]) -    new_info = Map.put(user.info, "domain_blocks", new_blocks) +    info_cng = +      user.info +      |> User.Info.add_to_domain_block(domain) + +    cng = +      change(user) +      |> put_embed(:info, info_cng) -    cs = User.info_changeset(user, %{info: new_info}) -    update_and_set_cache(cs) +    update_and_set_cache(cng)    end    def unblock_domain(user, domain) do -    blocks = user.info["domain_blocks"] || [] -    new_blocks = List.delete(blocks, domain) -    new_info = Map.put(user.info, "domain_blocks", new_blocks) +    info_cng = +      user.info +      |> User.Info.remove_from_domain_block(domain) -    cs = User.info_changeset(user, %{info: new_info}) -    update_and_set_cache(cs) +    cng = +      change(user) +      |> put_embed(:info, info_cng) + +    update_and_set_cache(cng)    end    def local_user_query() do @@ -613,9 +645,13 @@ defmodule Pleroma.User do    end    def deactivate(%User{} = user, status \\ true) do -    new_info = Map.put(user.info, "deactivated", status) -    cs = User.info_changeset(user, %{info: new_info}) -    update_and_set_cache(cs) +    info_cng = User.Info.set_activation_status(user.info, status) + +    cng = +      change(user) +      |> put_embed(:info, info_cng) + +    update_and_set_cache(cng)    end    def delete(%User{} = user) do @@ -649,7 +685,7 @@ defmodule Pleroma.User do      {:ok, user}    end -  def html_filter_policy(%User{info: %{"no_rich_text" => true}}) do +  def html_filter_policy(%User{info: %{no_rich_text: true}}) do      Pleroma.HTML.Scrubber.TwitterText    end @@ -683,7 +719,7 @@ defmodule Pleroma.User do        user      else        changes = -        %User{} +        %User{info: %User.Info{}}          |> cast(%{}, [:ap_id, :nickname, :local])          |> put_change(:ap_id, relay_uri)          |> put_change(:nickname, nil) @@ -697,7 +733,7 @@ defmodule Pleroma.User do    # AP style    def public_key_from_info(%{ -        "source_data" => %{"publicKey" => %{"publicKeyPem" => public_key_pem}} +        source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}        }) do      key =        :public_key.pem_decode(public_key_pem) @@ -708,7 +744,7 @@ defmodule Pleroma.User do    end    # OStatus Magic Key -  def public_key_from_info(%{"magic_key" => magic_key}) do +  def public_key_from_info(%{magic_key: magic_key}) do      {:ok, Pleroma.Web.Salmon.decode_key(magic_key)}    end @@ -730,11 +766,12 @@ defmodule Pleroma.User do        |> Map.put(:name, blank?(data[:name]) || data[:nickname])      cs = User.remote_user_creation(data) +      Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)    end    def ap_enabled?(%User{local: true}), do: true -  def ap_enabled?(%User{info: info}), do: info["ap_enabled"] +  def ap_enabled?(%User{info: info}), do: info.ap_enabled    def ap_enabled?(_), do: false    def get_or_fetch(uri_or_nickname) do @@ -768,4 +805,18 @@ defmodule Pleroma.User do          :error      end    end + +  def parse_bio(bio, user \\ %User{info: %{source_data: %{}}}) do +    mentions = Formatter.parse_mentions(bio) +    tags = Formatter.parse_tags(bio) + +    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) + +    CommonUtils.format_input(bio, mentions, tags, "text/plain") |> Formatter.emojify(emoji) +  end  end diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex new file mode 100644 index 000000000..49b2f0eda --- /dev/null +++ b/lib/pleroma/user/info.ex @@ -0,0 +1,165 @@ +defmodule Pleroma.User.Info do +  use Ecto.Schema +  import Ecto.Changeset + +  embedded_schema do +    field(:banner, :map, default: %{}) +    field(:background, :map, default: %{}) +    field(:source_data, :map, default: %{}) +    field(:note_count, :integer, default: 0) +    field(:follower_count, :integer, default: 0) +    field(:locked, :boolean, default: false) +    field(:default_scope, :string, default: "public") +    field(:blocks, {:array, :string}, default: []) +    field(:domain_blocks, {:array, :string}, default: []) +    field(:deactivated, :boolean, default: false) +    field(:no_rich_text, :boolean, default: false) +    field(:ap_enabled, :boolean, default: false) +    field(:is_moderator, :boolean, default: false) +    field(:is_admin, :boolean, default: false) +    field(:keys, :string, default: nil) +    field(:settings, :map, default: nil) +    field(:magic_key, :string, default: nil) +    field(:uri, :string, default: nil) +    field(:topic, :string, default: nil) +    field(:hub, :string, default: nil) +    field(:salmon, :string, default: nil) + +    # Found in the wild +    # ap_id -> Where is this used? +    # bio -> Where is this used? +    # avatar -> Where is this used? +    # fqn -> Where is this used? +    # host -> Where is this used? +    # subject _> Where is this used? +  end + +  def set_activation_status(info, deactivated) do +    params = %{deactivated: deactivated} + +    info +    |> cast(params, [:deactivated]) +    |> validate_required([:deactivated]) +  end + +  def add_to_note_count(info, number) do +    set_note_count(info, info.note_count + number) +  end + +  def set_note_count(info, number) do +    params = %{note_count: Enum.max([0, number])} + +    info +    |> cast(params, [:note_count]) +    |> validate_required([:note_count]) +  end + +  def set_follower_count(info, number) do +    params = %{follower_count: Enum.max([0, number])} + +    info +    |> cast(params, [:follower_count]) +    |> validate_required([:follower_count]) +  end + +  def set_blocks(info, blocks) do +    params = %{blocks: blocks} + +    info +    |> cast(params, [:blocks]) +    |> validate_required([:blocks]) +  end + +  def add_to_block(info, blocked) do +    set_blocks(info, Enum.uniq([blocked | info.blocks])) +  end + +  def remove_from_block(info, blocked) do +    set_blocks(info, List.delete(info.blocks, blocked)) +  end + +  def set_domain_blocks(info, domain_blocks) do +    params = %{domain_blocks: domain_blocks} + +    info +    |> cast(params, [:domain_blocks]) +    |> validate_required([:domain_blocks]) +  end + +  def add_to_domain_block(info, domain_blocked) do +    set_domain_blocks(info, Enum.uniq([domain_blocked | info.domain_blocks])) +  end + +  def remove_from_domain_block(info, domain_blocked) do +    set_domain_blocks(info, List.delete(info.domain_blocks, domain_blocked)) +  end + +  def set_keys(info, keys) do +    params = %{keys: keys} + +    info +    |> cast(params, [:keys]) +    |> validate_required([:keys]) +  end + +  def remote_user_creation(info, params) do +    info +    |> cast(params, [ +      :ap_enabled, +      :source_data, +      :banner, +      :locked, +      :magic_key, +      :uri, +      :hub, +      :topic, +      :salmon +    ]) +  end + +  def user_upgrade(info, params) do +    info +    |> cast(params, [ +      :ap_enabled, +      :source_data, +      :banner, +      :locked, +      :magic_key +    ]) +  end + +  def profile_update(info, params) do +    info +    |> cast(params, [ +      :locked, +      :no_rich_text, +      :default_scope, +      :banner, +      :background +    ]) +  end + +  def mastodon_profile_update(info, params) do +    info +    |> cast(params, [ +      :locked, +      :banner +    ]) +  end + +  def set_source_data(info, source_data) do +    params = %{source_data: source_data} + +    info +    |> cast(params, [:source_data]) +    |> validate_required([:source_data]) +  end + +  def admin_api_update(info, params) do +    info +    |> cast(params, [ +      :is_moderator, +      :is_admin +    ]) +  end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 76c15cf21..60253a715 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -42,7 +42,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp check_actor_is_active(actor) do      if not is_nil(actor) do        with user <- User.get_cached_by_ap_id(actor), -           false <- !!user.info["deactivated"] do +           false <- user.info.deactivated do          :ok        else          _e -> :reject @@ -509,8 +509,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    end    defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do -    blocks = info["blocks"] || [] -    domain_blocks = info["domain_blocks"] || [] +    blocks = info.blocks || [] +    domain_blocks = info.domain_blocks || []      from(        activity in query, @@ -676,7 +676,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      remote_inboxes =        (Pleroma.Web.Salmon.remote_users(activity) ++ followers)        |> Enum.filter(fn user -> User.ap_enabled?(user) end) -      |> Enum.map(fn %{info: %{"source_data" => data}} -> +      |> Enum.map(fn %{info: %{source_data: data}} ->          (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]        end)        |> Enum.uniq() @@ -762,7 +762,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      Logger.info("Fetching #{id} via AP")      with true <- String.starts_with?(id, "http"), -         {:ok, %{body: body, status_code: code}} when code in 200..299 <- +         {:ok, %{body: body, status: code}} when code in 200..299 <-             @httpoison.get(               id,               [Accept: "application/activity+json"], diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 86dcf5080..12fc3b181 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -23,7 +23,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    defp check_media_removal(           %{host: actor_host} = _actor_info, -         %{"type" => "Create", "object" => %{"attachement" => child_attachment}} = object +         %{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object         )         when length(child_attachment) > 0 do      object = diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 5864855b0..17b063609 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -447,7 +447,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do        update_data =          new_user_data          |> Map.take([:name, :bio, :avatar]) -        |> Map.put(:info, Map.merge(actor.info, %{"banner" => banner, "locked" => locked})) +        |> Map.put(:info, %{"banner" => banner, "locked" => locked})        actor        |> User.upgrade_changeset(update_data) @@ -850,10 +850,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def upgrade_user_from_ap_id(ap_id, async \\ true) do      with %User{local: false} = user <- User.get_by_ap_id(ap_id),           {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do -      data = -        data -        |> Map.put(:info, Map.merge(user.info, data[:info])) -        already_ap = User.ap_enabled?(user)        {:ok, user} = diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index eb335813d..aaa777602 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do    # the instance itself is not a Person, but instead an Application    def render("user.json", %{user: %{nickname: nil} = user}) do      {:ok, user} = WebFinger.ensure_keys_present(user) -    {:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"]) +    {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys)      public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)      public_key = :public_key.pem_encode([public_key]) @@ -40,7 +40,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do    def render("user.json", %{user: user}) do      {:ok, user} = WebFinger.ensure_keys_present(user) -    {:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"]) +    {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys)      public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)      public_key = :public_key.pem_encode([public_key]) @@ -55,7 +55,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do        "name" => user.name,        "summary" => user.bio,        "url" => user.ap_id, -      "manuallyApprovesFollowers" => user.info["locked"] || false, +      "manuallyApprovesFollowers" => user.info.locked,        "publicKey" => %{          "id" => "#{user.ap_id}#main-key",          "owner" => user.ap_id, @@ -72,7 +72,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do          "type" => "Image",          "url" => User.banner_url(user)        }, -      "tag" => user.info["source_data"]["tag"] || [] +      "tag" => user.info.source_data["tag"] || []      }      |> Map.merge(Utils.make_json_ld_header())    end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index bcdb4ba37..2c67d9cda 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -45,21 +45,29 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      user = User.get_by_nickname(nickname)      info = -      user.info +      %{}        |> Map.put("is_" <> permission_group, true) -    cng = User.info_changeset(user, %{info: info}) +    info_cng = User.Info.admin_api_update(user.info, info) + +    cng = +      Ecto.Changeset.change(user) +      |> Ecto.Changeset.put_embed(:info, info_cng) +      {:ok, user} = User.update_and_set_cache(cng)      conn -    |> json(user.info) +    |> json(info)    end    def right_get(conn, %{"nickname" => nickname}) do      user = User.get_by_nickname(nickname)      conn -    |> json(user.info) +    |> json(%{ +      is_moderator: user.info.is_moderator, +      is_admin: user.info.is_admin +    })    end    def right_add(conn, _) do @@ -84,14 +92,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do        user = User.get_by_nickname(nickname)        info = -        user.info +        %{}          |> Map.put("is_" <> permission_group, false) -      cng = User.info_changeset(user, %{info: info}) +      info_cng = User.Info.admin_api_update(user.info, info) + +      cng = +        Ecto.Changeset.change(user) +        |> Ecto.Changeset.put_embed(:info, info_cng) +        {:ok, user} = User.update_and_set_cache(cng)        conn -      |> json(user.info) +      |> json(info)      end    end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 77e4dbbd7..e3385310f 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.CommonAPI do    def delete(activity_id, user) do      with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),           %Object{} = object <- Object.normalize(object_id), -         true <- user.info["is_moderator"] || user.ap_id == object.data["actor"], +         true <- user.info.is_moderator || user.ap_id == object.data["actor"],           {:ok, delete} <- ActivityPub.delete(object) do        {:ok, delete}      end @@ -135,12 +135,13 @@ defmodule Pleroma.Web.CommonAPI do      end    end +  # Updates the emojis for a user based on their profile    def update(user) do      user =        with emoji <- emoji_from_profile(user), -           source_data <- (user.info["source_data"] || %{}) |> Map.put("tag", emoji), -           new_info <- Map.put(user.info, "source_data", source_data), -           change <- User.info_changeset(user, %{info: new_info}), +           source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji), +           info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data), +           change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),             {:ok, user} <- User.update_and_set_cache(change) do          user        else diff --git a/lib/pleroma/web/federator/retry_queue.ex b/lib/pleroma/web/federator/retry_queue.ex index 06c094f26..13df40c80 100644 --- a/lib/pleroma/web/federator/retry_queue.ex +++ b/lib/pleroma/web/federator/retry_queue.ex @@ -17,7 +17,15 @@ defmodule Pleroma.Web.Federator.RetryQueue do    end    def start_link() do -    GenServer.start_link(__MODULE__, %{delivered: 0, dropped: 0}, name: __MODULE__) +    enabled = Pleroma.Config.get([:retry_queue, :enabled], false) + +    if enabled do +      Logger.info("Starting retry queue") +      GenServer.start_link(__MODULE__, %{delivered: 0, dropped: 0}, name: __MODULE__) +    else +      Logger.info("Retry queue disabled") +      :ignore +    end    end    def enqueue(data, transport, retries \\ 0) do diff --git a/lib/pleroma/web/http_signatures/http_signatures.ex b/lib/pleroma/web/http_signatures/http_signatures.ex index 5e42a871b..0e54debd5 100644 --- a/lib/pleroma/web/http_signatures/http_signatures.ex +++ b/lib/pleroma/web/http_signatures/http_signatures.ex @@ -65,7 +65,7 @@ defmodule Pleroma.Web.HTTPSignatures do    end    def sign(user, headers) do -    with {:ok, %{info: %{"keys" => keys}}} <- Pleroma.Web.WebFinger.ensure_keys_present(user), +    with {:ok, %{info: %{keys: keys}}} <- Pleroma.Web.WebFinger.ensure_keys_present(user),           {:ok, private_key, _} = Pleroma.Web.Salmon.keys_from_pem(keys) do        sigstring = build_signing_string(headers, Map.keys(headers)) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 009be50e7..ea64f163d 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -32,67 +32,55 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end -  def update_credentials(%{assigns: %{user: user}} = conn, params) do -    original_user = user - -    params = -      if bio = params["note"] do -        Map.put(params, "bio", bio) -      else -        params +  defp add_if_present( +         map, +         params, +         params_field, +         map_field, +         value_function \\ fn x -> {:ok, x} end +       ) do +    if Map.has_key?(params, params_field) do +      case value_function.(params[params_field]) do +        {:ok, new_value} -> Map.put(map, map_field, new_value) +        :error -> map        end +    else +      map +    end +  end -    params = -      if name = params["display_name"] do -        Map.put(params, "name", name) -      else -        params -      end +  def update_credentials(%{assigns: %{user: user}} = conn, params) do +    original_user = user -    user = -      if avatar = params["avatar"] do -        with %Plug.Upload{} <- avatar, -             {:ok, object} <- ActivityPub.upload(avatar, type: :avatar), -             change = Ecto.Changeset.change(user, %{avatar: object.data}), -             {:ok, user} = User.update_and_set_cache(change) do -          user +    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, "avatar", :avatar, fn value -> +        with %Plug.Upload{} <- value, +             {:ok, object} <- ActivityPub.upload(value, type: :avatar) do +          {:ok, object.data}          else -          _e -> user +          _ -> :error          end -      else -        user -      end +      end) -    user = -      if banner = params["header"] do -        with %Plug.Upload{} <- banner, -             {:ok, object} <- ActivityPub.upload(banner, type: :banner), -             new_info <- Map.put(user.info, "banner", object.data), -             change <- User.info_changeset(user, %{info: new_info}), -             {:ok, user} <- User.update_and_set_cache(change) do -          user +    info_params = +      %{} +      |> add_if_present(params, "locked", :locked, fn value -> {:ok, value == "true"} end) +      |> add_if_present(params, "header", :banner, fn value -> +        with %Plug.Upload{} <- value, +             {:ok, object} <- ActivityPub.upload(value, type: :banner) do +          {:ok, object.data}          else -          _e -> user +          _ -> :error          end -      else -        user -      end +      end) -    user = -      if locked = params["locked"] do -        with locked <- locked == "true", -             new_info <- Map.put(user.info, "locked", locked), -             change <- User.info_changeset(user, %{info: new_info}), -             {:ok, user} <- User.update_and_set_cache(change) do -          user -        else -          _e -> user -        end -      else -        user -      end +    info_cng = User.Info.mastodon_profile_update(user.info, info_params) -    with changeset <- User.update_changeset(user, params), +    with changeset <- User.update_changeset(user, user_params), +         changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),           {:ok, user} <- User.update_and_set_cache(changeset) do        if original_user != user do          CommonAPI.update(user) @@ -644,7 +632,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    # TODO: Use proper query    def blocks(%{assigns: %{user: user}} = conn, _) do -    with blocked_users <- user.info["blocks"] || [], +    with blocked_users <- user.info.blocks || [],           accounts <- Enum.map(blocked_users, fn ap_id -> User.get_cached_by_ap_id(ap_id) end) do        res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)        json(conn, res) @@ -652,7 +640,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do -    json(conn, info["domain_blocks"] || []) +    json(conn, info.domain_blocks || [])    end    def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do @@ -900,11 +888,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do              max_toot_chars: limit            },            rights: %{ -            delete_others_notice: !!user.info["is_moderator"] +            delete_others_notice: !!user.info.is_moderator            },            compose: %{              me: "#{user.id}", -            default_privacy: user.info["default_scope"] || "public", +            default_privacy: user.info.default_scope,              default_sensitive: false            },            media_attachments: %{ @@ -924,7 +912,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do              ]            },            settings: -            Map.get(user.info, "settings") || +            Map.get(user.info, :settings) ||                %{                  onboarded: true,                  home: %{ @@ -1180,7 +1168,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do        user = user.nickname        url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user) -      with {:ok, %{status_code: 200, body: body}} <- +      with {:ok, %{status: 200, body: body}} <-               @httpoison.get(url, [], timeout: timeout, recv_timeout: timeout),             {:ok, data} <- Jason.decode(body) do          data2 = diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index b68845e16..bcfa8836e 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -14,10 +14,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do      image = User.avatar_url(user) |> MediaProxy.url()      header = User.banner_url(user) |> MediaProxy.url()      user_info = User.user_info(user) -    bot = (user.info["source_data"]["type"] || "Person") in ["Application", "Service"] +    bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]      emojis = -      (user.info["source_data"]["tag"] || []) +      (user.info.source_data["tag"] || [])        |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)        |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->          %{ @@ -29,7 +29,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do        end)      fields = -      (user.info["source_data"]["attachment"] || []) +      (user.info.source_data["attachment"] || [])        |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)        |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end) diff --git a/lib/pleroma/web/media_proxy/controller.ex b/lib/pleroma/web/media_proxy/controller.ex index 81ea5d510..e1b87e026 100644 --- a/lib/pleroma/web/media_proxy/controller.ex +++ b/lib/pleroma/web/media_proxy/controller.ex @@ -5,12 +5,12 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do    @default_proxy_opts [max_body_length: 25 * 1_048_576]    def remote(conn, params = %{"sig" => sig64, "url" => url64}) do -    with config <- Pleroma.Config.get([:media_proxy]), +    with config <- Pleroma.Config.get([:media_proxy], []),           true <- Keyword.get(config, :enabled, false),           {:ok, url} <- MediaProxy.decode_url(sig64, url64),           filename <- Path.basename(URI.parse(url).path),           :ok <- filename_matches(Map.has_key?(params, "filename"), conn.request_path, url) do -      ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_length)) +      ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))      else        false ->          send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index 1d0019d3b..67df354db 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -226,25 +226,21 @@ defmodule Pleroma.Web.OStatus do      old_data = %{        avatar: user.avatar,        bio: user.bio, -      name: user.name, -      info: user.info +      name: user.name      }      with false <- user.local,           avatar <- make_avatar_object(doc),           bio <- string_from_xpath("//author[1]/summary", doc),           name <- string_from_xpath("//author[1]/poco:displayName", doc), -         info <- -           Map.put(user.info, "banner", make_avatar_object(doc, "header") || user.info["banner"]),           new_data <- %{             avatar: avatar || old_data.avatar,             name: name || old_data.name, -           bio: bio || old_data.bio, -           info: info || old_data.info +           bio: bio || old_data.bio           },           false <- new_data == old_data do        change = Ecto.Changeset.change(user, new_data) -      Repo.update(change) +      User.update_and_set_cache(change)      else        _ ->          {:ok, user} @@ -350,13 +346,15 @@ defmodule Pleroma.Web.OStatus do    def fetch_activity_from_atom_url(url) do      with true <- String.starts_with?(url, "http"), -         {:ok, %{body: body, status_code: code}} when code in 200..299 <- +         {:ok, %{body: body, status: code}} when code in 200..299 <-             @httpoison.get(               url,               [Accept: "application/atom+xml"],               follow_redirect: true, -             timeout: 10000, -             recv_timeout: 20000 +             adapter: [ +               timeout: 10000, +               recv_timeout: 20000 +             ]             ) do        Logger.debug("Got document from #{url}, handling...")        handle_incoming(body) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 09265954a..d6a9d5779 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -297,12 +297,6 @@ defmodule Pleroma.Web.Router do      post("/account/update_profile_banner", TwitterAPI.Controller, :update_banner)      post("/qvitter/update_background_image", TwitterAPI.Controller, :update_background) -    post( -      "/account/most_recent_notification", -      TwitterAPI.Controller, -      :update_most_recent_notification -    ) -      get("/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline)      get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline)      get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline) diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 562ec3d9c..97251c05e 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -157,15 +157,17 @@ defmodule Pleroma.Web.Salmon do      |> Enum.filter(fn user -> user && !user.local end)    end -  defp send_to_user(%{info: %{"salmon" => salmon}}, feed, poster) do -    with {:ok, %{status_code: code}} <- +  defp send_to_user(%{info: %{salmon: salmon}}, feed, poster) do +    with {:ok, %{status: code}} <-             poster.(               salmon,               feed,               [{"Content-Type", "application/magic-envelope+xml"}], -             timeout: 10000, -             recv_timeout: 20000, -             hackney: [pool: :default] +             adapter: [ +               timeout: 10000, +               recv_timeout: 20000, +               pool: :default +             ]             ) do        Logger.debug(fn -> "Pushed to #{salmon}, code #{code}" end)      else @@ -185,7 +187,7 @@ defmodule Pleroma.Web.Salmon do    ]    def publish(user, activity, poster \\ &@httpoison.post/4) -  def publish(%{info: %{"keys" => keys}} = user, %{data: %{"type" => type}} = activity, poster) +  def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity, poster)        when type in @supported_activities do      feed = ActivityRepresenter.to_simple_form(activity, user, true) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 306598157..99b8b7063 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -188,7 +188,7 @@ defmodule Pleroma.Web.Streamer do        # Get the current user so we have up-to-date blocks etc.        if socket.assigns[:user] do          user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) -        blocks = user.info["blocks"] || [] +        blocks = user.info.blocks || []          parent = Object.normalize(item.data["object"]) @@ -206,7 +206,7 @@ defmodule Pleroma.Web.Streamer do        # Get the current user so we have up-to-date blocks etc.        if socket.assigns[:user] do          user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) -        blocks = user.info["blocks"] || [] +        blocks = user.info.blocks || []          unless item.actor in blocks do            send(socket.transport_pid, {:text, represent_update(item, user)}) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 6223580e1..c19a4f084 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -132,7 +132,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do      params = %{        nickname: params["nickname"],        name: params["fullname"], -      bio: params["bio"], +      bio: User.parse_bio(params["bio"]),        email: params["email"],        password: params["password"],        password_confirmation: params["confirm"] @@ -148,7 +148,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do      cond do        registrations_open || (!is_nil(token) && !token.used) -> -        changeset = User.register_changeset(%User{}, params) +        changeset = User.register_changeset(%User{info: %{}}, params)          with {:ok, user} <- Repo.insert(changeset) do            !registrations_open && UserInviteToken.mark_as_used(token.token) @@ -279,14 +279,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do    def get_external_profile(for_user, uri) do      with %User{} = user <- User.get_or_fetch(uri) do -      spawn(fn -> -        with url <- user.info["topic"], -             {:ok, %{body: body}} <- -               @httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000) do -          OStatus.handle_incoming(body) -        end -      end) -        {:ok, UserView.render("show.json", %{user: user, for: for_user})}      else        _e -> diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 064730867..961250d92 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -300,9 +300,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do    def update_banner(%{assigns: %{user: user}} = conn, params) do      with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner), -         new_info <- Map.put(user.info, "banner", object.data), -         change <- User.info_changeset(user, %{info: new_info}), -         {:ok, user} <- User.update_and_set_cache(change) do +         new_info <- %{"banner" => object.data}, +         info_cng <- User.Info.profile_update(user.info, new_info), +         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), +         {:ok, user} <- User.update_and_set_cache(changeset) do        CommonAPI.update(user)        %{"url" => [%{"href" => href} | _]} = object.data        response = %{url: href} |> Jason.encode!() @@ -314,9 +315,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do    def update_background(%{assigns: %{user: user}} = conn, params) do      with {:ok, object} <- ActivityPub.upload(params, type: :background), -         new_info <- Map.put(user.info, "background", object.data), -         change <- User.info_changeset(user, %{info: new_info}), -         {:ok, _user} <- User.update_and_set_cache(change) do +         new_info <- %{"background" => object.data}, +         info_cng <- User.Info.profile_update(user.info, new_info), +         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), +         {:ok, _user} <- User.update_and_set_cache(changeset) do        %{"url" => [%{"href" => href} | _]} = object.data        response = %{url: href} |> Jason.encode!() @@ -338,20 +340,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do      end    end -  def update_most_recent_notification(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with id when is_number(id) <- String.to_integer(id), -         info <- user.info, -         mrn <- max(id, user.info["most_recent_notification"] || 0), -         updated_info <- Map.put(info, "most_recent_notification", mrn), -         changeset <- User.info_changeset(user, %{info: updated_info}), -         {:ok, _user} <- User.update_and_set_cache(changeset) do -      conn -      |> json_reply(200, Jason.encode!(mrn)) -    else -      _e -> bad_request_reply(conn, "Can't update.") -    end -  end -    def followers(conn, params) do      with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),           {:ok, followers} <- User.get_followers(user) do @@ -439,67 +427,41 @@ defmodule Pleroma.Web.TwitterAPI.Controller do      json(conn, [])    end -  def update_profile(%{assigns: %{user: user}} = conn, params) do -    params = -      if bio = params["description"] do -        mentions = Formatter.parse_mentions(bio) -        tags = Formatter.parse_tags(bio) - -        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) - -        bio_html = CommonUtils.format_input(bio, mentions, tags, "text/plain") -        Map.put(params, "bio", bio_html |> Formatter.emojify(emoji)) -      else -        params -      end - -    user = -      if locked = params["locked"] do -        with locked <- locked == "true", -             new_info <- Map.put(user.info, "locked", locked), -             change <- User.info_changeset(user, %{info: new_info}), -             {:ok, user} <- User.update_and_set_cache(change) do -          user +  defp build_info_cng(user, params) do +    info_params = +      ["no_rich_text", "locked"] +      |> Enum.reduce(%{}, fn key, res -> +        if value = params[key] do +          Map.put(res, key, value == "true")          else -          _e -> user +          res          end -      else -        user -      end +      end) -    user = -      if no_rich_text = params["no_rich_text"] do -        with no_rich_text <- no_rich_text == "true", -             new_info <- Map.put(user.info, "no_rich_text", no_rich_text), -             change <- User.info_changeset(user, %{info: new_info}), -             {:ok, user} <- User.update_and_set_cache(change) do -          user -        else -          _e -> user -        end +    info_params = +      if value = params["default_scope"] do +        Map.put(info_params, "default_scope", value)        else -        user +        info_params        end -    user = -      if default_scope = params["default_scope"] do -        with new_info <- Map.put(user.info, "default_scope", default_scope), -             change <- User.info_changeset(user, %{info: new_info}), -             {:ok, user} <- User.update_and_set_cache(change) do -          user -        else -          _e -> user -        end -      else -        user -      end +    User.Info.profile_update(user.info, info_params) +  end + +  defp parse_profile_bio(user, params) do +    if bio = params["description"] do +      Map.put(params, "bio", User.parse_bio(bio, user)) +    else +      params +    end +  end + +  def update_profile(%{assigns: %{user: user}} = conn, params) do +    params = parse_profile_bio(user, params) +    info_cng = build_info_cng(user, params)      with changeset <- User.update_changeset(user, params), +         changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),           {:ok, user} <- User.update_and_set_cache(changeset) do        CommonAPI.update(user)        render(conn, UserView, "user.json", %{user: user, for: user}) diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index a100a1127..b78024ed7 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do      user_info = User.get_cached_user_info(user)      emoji = -      (user.info["source_data"]["tag"] || []) +      (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} @@ -40,7 +40,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do      # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.      # For example: [{"name": "Pronoun", "value": "she/her"}, …]      fields = -      (user.info["source_data"]["attachment"] || []) +      (user.info.source_data["attachment"] || [])        |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)        |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end) @@ -66,17 +66,17 @@ defmodule Pleroma.Web.TwitterAPI.UserView do        "profile_image_url_profile_size" => image,        "profile_image_url_original" => image,        "rights" => %{ -        "delete_others_notice" => !!user.info["is_moderator"] +        "delete_others_notice" => !!user.info.is_moderator        },        "screen_name" => user.nickname,        "statuses_count" => user_info[:note_count],        "statusnet_profile_url" => user.ap_id,        "cover_photo" => User.banner_url(user) |> MediaProxy.url(), -      "background_image" => image_url(user.info["background"]) |> MediaProxy.url(), +      "background_image" => image_url(user.info.background) |> MediaProxy.url(),        "is_local" => user.local, -      "locked" => !!user.info["locked"], -      "default_scope" => user.info["default_scope"] || "public", -      "no_rich_text" => user.info["no_rich_text"] || false, +      "locked" => user.info.locked, +      "default_scope" => user.info.default_scope, +      "no_rich_text" => user.info.no_rich_text,        "fields" => fields      } diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 9f554d286..99c65a6bf 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -45,7 +45,7 @@ defmodule Pleroma.Web.WebFinger do    def represent_user(user, "JSON") do      {:ok, user} = ensure_keys_present(user) -    {:ok, _private, public} = Salmon.keys_from_pem(user.info["keys"]) +    {:ok, _private, public} = Salmon.keys_from_pem(user.info.keys)      magic_key = Salmon.encode_key(public)      %{ @@ -83,7 +83,7 @@ defmodule Pleroma.Web.WebFinger do    def represent_user(user, "XML") do      {:ok, user} = ensure_keys_present(user) -    {:ok, _private, public} = Salmon.keys_from_pem(user.info["keys"]) +    {:ok, _private, public} = Salmon.keys_from_pem(user.info.keys)      magic_key = Salmon.encode_key(public)      { @@ -113,16 +113,22 @@ defmodule Pleroma.Web.WebFinger do    # This seems a better fit in Salmon    def ensure_keys_present(user) do -    info = user.info || %{} +    info = user.info -    if info["keys"] do +    if info.keys do        {:ok, user}      else        {:ok, pem} = Salmon.generate_rsa_pem() -      info = Map.put(info, "keys", pem) -      Ecto.Changeset.change(user, info: info) -      |> User.update_and_set_cache() +      info_cng = +        info +        |> Pleroma.User.Info.set_keys(pem) + +      cng = +        Ecto.Changeset.change(user) +        |> Ecto.Changeset.put_embed(:info, info_cng) + +      User.update_and_set_cache(cng)      end    end @@ -214,7 +220,7 @@ defmodule Pleroma.Web.WebFinger do    end    def find_lrdd_template(domain) do -    with {:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- +    with {:ok, %{status: status, body: body}} when status in 200..299 <-             @httpoison.get("http://#{domain}/.well-known/host-meta", [], follow_redirect: true) do        get_template_from_xml(body)      else @@ -253,7 +259,7 @@ defmodule Pleroma.Web.WebFinger do               [Accept: "application/xrd+xml,application/jrd+json"],               follow_redirect: true             ), -         {:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- response do +         {:ok, %{status: status, body: body}} when status in 200..299 <- response do        doc = XML.parse_document(body)        if doc != :error do diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex index 396dcf045..0761b5475 100644 --- a/lib/pleroma/web/websub/websub.ex +++ b/lib/pleroma/web/websub/websub.ex @@ -146,7 +146,7 @@ defmodule Pleroma.Web.Websub do    end    def subscribe(subscriber, subscribed, requester \\ &request_subscription/1) do -    topic = subscribed.info["topic"] +    topic = subscribed.info.topic      # FIXME: Race condition, use transactions      {:ok, subscription} =        with subscription when not is_nil(subscription) <- @@ -158,7 +158,7 @@ defmodule Pleroma.Web.Websub do          _e ->            subscription = %WebsubClientSubscription{              topic: topic, -            hub: subscribed.info["hub"], +            hub: subscribed.info.hub,              subscribers: [subscriber.ap_id],              state: "requested",              secret: :crypto.strong_rand_bytes(8) |> Base.url_encode64(), @@ -173,7 +173,7 @@ defmodule Pleroma.Web.Websub do    def gather_feed_data(topic, getter \\ &@httpoison.get/1) do      with {:ok, response} <- getter.(topic), -         status_code when status_code in 200..299 <- response.status_code, +         status when status in 200..299 <- response.status,           body <- response.body,           doc <- XML.parse_document(body),           uri when not is_nil(uri) <- XML.string_from_xpath("/feed/author[1]/uri", doc), @@ -221,7 +221,7 @@ defmodule Pleroma.Web.Websub do      task = Task.async(websub_checker) -    with {:ok, %{status_code: 202}} <- +    with {:ok, %{status: 202}} <-             poster.(websub.hub, {:form, data}, "Content-type": "application/x-www-form-urlencoded"),           {:ok, websub} <- Task.yield(task, timeout) do        {:ok, websub} @@ -257,7 +257,7 @@ defmodule Pleroma.Web.Websub do      signature = sign(secret || "", xml)      Logger.info(fn -> "Pushing #{topic} to #{callback}" end) -    with {:ok, %{status_code: code}} <- +    with {:ok, %{status: code}} <-             @httpoison.post(               callback,               xml, @@ -265,9 +265,11 @@ defmodule Pleroma.Web.Websub do                 {"Content-Type", "application/atom+xml"},                 {"X-Hub-Signature", "sha1=#{signature}"}               ], -             timeout: 10000, -             recv_timeout: 20000, -             hackney: [pool: :default] +             adapter: [ +               timeout: 10000, +               recv_timeout: 20000, +               pool: :default +             ]             ) do        Logger.info(fn -> "Pushed to #{callback}, code #{code}" end)        {:ok, code} | 
