diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/mix/tasks/pleroma/emoji.ex | 16 | ||||
| -rw-r--r-- | lib/pleroma/activity.ex | 23 | ||||
| -rw-r--r-- | lib/pleroma/stats.ex | 9 | ||||
| -rw-r--r-- | lib/pleroma/user.ex | 196 | ||||
| -rw-r--r-- | lib/pleroma/user/query.ex | 150 | ||||
| -rw-r--r-- | lib/pleroma/web/admin_api/admin_api_controller.ex | 9 | ||||
| -rw-r--r-- | lib/pleroma/web/admin_api/search.ex | 44 | ||||
| -rw-r--r-- | lib/pleroma/web/controller_helper.ex | 6 | ||||
| -rw-r--r-- | lib/pleroma/web/endpoint.ex | 7 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 3 | ||||
| -rw-r--r-- | lib/pleroma/web/oauth.ex | 14 | ||||
| -rw-r--r-- | lib/pleroma/web/oauth/oauth_controller.ex | 31 | ||||
| -rw-r--r-- | lib/pleroma/web/oauth/scopes.ex | 67 | ||||
| -rw-r--r-- | lib/pleroma/web/twitter_api/views/activity_view.ex | 2 | 
14 files changed, 342 insertions, 235 deletions
| diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index cced73226..5cb54c3ca 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -109,7 +109,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do            ])          ) -        binary_archive = Tesla.get!(src_url).body +        binary_archive = Tesla.get!(client(), src_url).body          archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()          sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright] @@ -137,7 +137,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do            ])          ) -        files = Tesla.get!(files_url).body |> Poison.decode!() +        files = Tesla.get!(client(), files_url).body |> Poison.decode!()          IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name])) @@ -213,7 +213,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do      IO.puts("Downloading the pack and generating SHA256") -    binary_archive = Tesla.get!(src).body +    binary_archive = Tesla.get!(client(), src).body      archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()      IO.puts("SHA256 is #{archive_sha}") @@ -272,7 +272,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do    defp fetch_manifest(from) do      Poison.decode!(        if String.starts_with?(from, "http") do -        Tesla.get!(from).body +        Tesla.get!(client(), from).body        else          File.read!(from)        end @@ -290,4 +290,12 @@ defmodule Mix.Tasks.Pleroma.Emoji do        ]      )    end + +  defp client do +    middleware = [ +      {Tesla.Middleware.FollowRedirects, [max_redirects: 3]} +    ] + +    Tesla.client(middleware) +  end  end diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 2b661edc1..c121e800f 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -287,6 +287,29 @@ defmodule Pleroma.Activity do      |> Repo.all()    end +  def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do +    from( +      a in Activity, +      where: +        fragment( +          "? ->> 'type' = 'Follow'", +          a.data +        ), +      where: +        fragment( +          "? ->> 'state' = 'pending'", +          a.data +        ), +      where: +        fragment( +          "coalesce((?)->'object'->>'id', (?)->>'object') = ?", +          a.data, +          a.data, +          ^ap_id +        ) +    ) +  end +    @spec query_by_actor(actor()) :: Ecto.Query.t()    def query_by_actor(actor) do      from(a in Activity, where: a.actor == ^actor) diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index 2e7d747df..5b242927b 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -34,7 +34,7 @@ defmodule Pleroma.Stats do    def update_stats do      peers =        from( -        u in Pleroma.User, +        u in User,          select: fragment("distinct split_part(?, '@', 2)", u.nickname),          where: u.local != ^true        ) @@ -44,10 +44,13 @@ defmodule Pleroma.Stats do      domain_count = Enum.count(peers)      status_query = -      from(u in User.local_user_query(), select: fragment("sum((?->>'note_count')::int)", u.info)) +      from(u in User.Query.build(%{local: true}), +        select: fragment("sum((?->>'note_count')::int)", u.info) +      )      status_count = Repo.one(status_query) -    user_count = Repo.aggregate(User.active_local_user_query(), :count, :id) + +    user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)      Agent.update(__MODULE__, fn _ ->        {peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 6e5473177..b23a70067 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -255,10 +255,7 @@ defmodule Pleroma.User do      candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])      autofollowed_users = -      from(u in User, -        where: u.local == true, -        where: u.nickname in ^candidates -      ) +      User.Query.build(%{nickname: candidates, local: true})        |> Repo.all()      follow_all(user, autofollowed_users) @@ -577,19 +574,17 @@ defmodule Pleroma.User do      )    end -  def get_followers_query(%User{id: id, follower_address: follower_address}, nil) do -    from( -      u in User, -      where: fragment("? <@ ?", ^[follower_address], u.following), -      where: u.id != ^id -    ) +  @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t() +  def get_followers_query(%User{} = user, nil) do +    User.Query.build(%{followers: user})    end    def get_followers_query(user, page) do      from(u in get_followers_query(user, nil)) -    |> paginate(page, 20) +    |> User.Query.paginate(page, 20)    end +  @spec get_followers_query(User.t()) :: Ecto.Query.t()    def get_followers_query(user), do: get_followers_query(user, nil)    def get_followers(user, page \\ nil) do @@ -604,19 +599,17 @@ defmodule Pleroma.User do      Repo.all(from(u in q, select: u.id))    end -  def get_friends_query(%User{id: id, following: following}, nil) do -    from( -      u in User, -      where: u.follower_address in ^following, -      where: u.id != ^id -    ) +  @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t() +  def get_friends_query(%User{} = user, nil) do +    User.Query.build(%{friends: user})    end    def get_friends_query(user, page) do      from(u in get_friends_query(user, nil)) -    |> paginate(page, 20) +    |> User.Query.paginate(page, 20)    end +  @spec get_friends_query(User.t()) :: Ecto.Query.t()    def get_friends_query(user), do: get_friends_query(user, nil)    def get_friends(user, page \\ nil) do @@ -631,33 +624,10 @@ defmodule Pleroma.User do      Repo.all(from(u in q, select: u.id))    end -  def get_follow_requests_query(%User{} = user) do -    from( -      a in Activity, -      where: -        fragment( -          "? ->> 'type' = 'Follow'", -          a.data -        ), -      where: -        fragment( -          "? ->> 'state' = 'pending'", -          a.data -        ), -      where: -        fragment( -          "coalesce((?)->'object'->>'id', (?)->>'object') = ?", -          a.data, -          a.data, -          ^user.ap_id -        ) -    ) -  end - +  @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}    def get_follow_requests(%User{} = user) do      users = -      user -      |> User.get_follow_requests_query() +      Activity.follow_requests_for_actor(user)        |> join(:inner, [a], u in User, on: a.actor == u.ap_id)        |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))        |> group_by([a, u], u.id) @@ -730,10 +700,7 @@ defmodule Pleroma.User do    def update_follower_count(%User{} = user) do      follower_count_query = -      User -      |> where([u], ^user.follower_address in u.following) -      |> where([u], u.id != ^user.id) -      |> select([u], %{count: count(u.id)}) +      User.Query.build(%{followers: user}) |> select([u], %{count: count(u.id)})      User      |> where(id: ^user.id) @@ -756,38 +723,19 @@ defmodule Pleroma.User do      end    end -  def get_users_from_set_query(ap_ids, false) do -    from( -      u in User, -      where: u.ap_id in ^ap_ids -    ) -  end - -  def get_users_from_set_query(ap_ids, true) do -    query = get_users_from_set_query(ap_ids, false) - -    from( -      u in query, -      where: u.local == true -    ) -  end - +  @spec get_users_from_set([String.t()], boolean()) :: [User.t()]    def get_users_from_set(ap_ids, local_only \\ true) do -    get_users_from_set_query(ap_ids, local_only) +    criteria = %{ap_id: ap_ids} +    criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria + +    User.Query.build(criteria)      |> Repo.all()    end +  @spec get_recipients_from_activity(Activity.t()) :: [User.t()]    def get_recipients_from_activity(%Activity{recipients: to}) do -    query = -      from( -        u in User, -        where: u.ap_id in ^to, -        or_where: fragment("? && ?", u.following, ^to) -      ) - -    query = from(u in query, where: u.local == true) - -    Repo.all(query) +    User.Query.build(%{recipients_from_activity: to, local: true}) +    |> Repo.all()    end    def search(query, resolve \\ false, for_user \\ nil) do @@ -1049,14 +997,23 @@ defmodule Pleroma.User do      end    end -  def muted_users(user), -    do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes)) +  @spec muted_users(User.t()) :: [User.t()] +  def muted_users(user) do +    User.Query.build(%{ap_id: user.info.mutes}) +    |> Repo.all() +  end -  def blocked_users(user), -    do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks)) +  @spec blocked_users(User.t()) :: [User.t()] +  def blocked_users(user) do +    User.Query.build(%{ap_id: user.info.blocks}) +    |> Repo.all() +  end -  def subscribers(user), -    do: Repo.all(from(u in User, where: u.ap_id in ^user.info.subscribers)) +  @spec subscribers(User.t()) :: [User.t()] +  def subscribers(user) do +    User.Query.build(%{ap_id: user.info.subscribers}) +    |> Repo.all() +  end    def block_domain(user, domain) do      info_cng = @@ -1082,69 +1039,6 @@ defmodule Pleroma.User do      update_and_set_cache(cng)    end -  def maybe_local_user_query(query, local) do -    if local, do: local_user_query(query), else: query -  end - -  def local_user_query(query \\ User) do -    from( -      u in query, -      where: u.local == true, -      where: not is_nil(u.nickname) -    ) -  end - -  def maybe_external_user_query(query, external) do -    if external, do: external_user_query(query), else: query -  end - -  def external_user_query(query \\ User) do -    from( -      u in query, -      where: u.local == false, -      where: not is_nil(u.nickname) -    ) -  end - -  def maybe_active_user_query(query, active) do -    if active, do: active_user_query(query), else: query -  end - -  def active_user_query(query \\ User) do -    from( -      u in query, -      where: fragment("not (?->'deactivated' @> 'true')", u.info), -      where: not is_nil(u.nickname) -    ) -  end - -  def maybe_deactivated_user_query(query, deactivated) do -    if deactivated, do: deactivated_user_query(query), else: query -  end - -  def deactivated_user_query(query \\ User) do -    from( -      u in query, -      where: fragment("(?->'deactivated' @> 'true')", u.info), -      where: not is_nil(u.nickname) -    ) -  end - -  def active_local_user_query do -    from( -      u in local_user_query(), -      where: fragment("not (?->'deactivated' @> 'true')", u.info) -    ) -  end - -  def moderator_user_query do -    from( -      u in User, -      where: u.local == true, -      where: fragment("?->'is_moderator' @> 'true'", u.info) -    ) -  end -    def deactivate(%User{} = user, status \\ true) do      info_cng = User.Info.set_activation_status(user.info, status) @@ -1307,7 +1201,7 @@ defmodule Pleroma.User do    def ap_enabled?(_), do: false    @doc "Gets or fetch a user by uri or nickname." -  @spec get_or_fetch(String.t()) :: User.t() +  @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}    def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)    def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname) @@ -1424,22 +1318,12 @@ defmodule Pleroma.User do      }    end +  @spec all_superusers() :: [User.t()]    def all_superusers do -    from( -      u in User, -      where: u.local == true, -      where: fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info) -    ) +    User.Query.build(%{super_users: true, local: true})      |> Repo.all()    end -  defp paginate(query, page, page_size) do -    from(u in query, -      limit: ^page_size, -      offset: ^((page - 1) * page_size) -    ) -  end -    def showing_reblogs?(%User{} = user, %User{} = target) do      target.ap_id not in user.info.muted_reblogs    end diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex new file mode 100644 index 000000000..2dfe5ce92 --- /dev/null +++ b/lib/pleroma/user/query.ex @@ -0,0 +1,150 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.Query do +  @moduledoc """ +  User query builder module. Builds query from new query or another user query. + +    ## Example: +        query = Pleroma.User.Query(%{nickname: "nickname"}) +        another_query = Pleroma.User.Query.build(query, %{email: "email@example.com"}) +        Pleroma.Repo.all(query) +        Pleroma.Repo.all(another_query) + +  Adding new rules: +    - *ilike criteria* +      - add field to @ilike_criteria list +      - pass non empty string +      - e.g. Pleroma.User.Query.build(%{nickname: "nickname"}) +    - *equal criteria* +      - add field to @equal_criteria list +      - pass non empty string +      - e.g. Pleroma.User.Query.build(%{email: "email@example.com"}) +    - *contains criteria* +      - add field to @containns_criteria list +      - pass values list +      - e.g. Pleroma.User.Query.build(%{ap_id: ["http://ap_id1", "http://ap_id2"]}) +  """ +  import Ecto.Query +  import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1] +  alias Pleroma.User + +  @type criteria :: +          %{ +            query: String.t(), +            tags: [String.t()], +            name: String.t(), +            email: String.t(), +            local: boolean(), +            external: boolean(), +            active: boolean(), +            deactivated: boolean(), +            is_admin: boolean(), +            is_moderator: boolean(), +            super_users: boolean(), +            followers: User.t(), +            friends: User.t(), +            recipients_from_activity: [String.t()], +            nickname: [String.t()], +            ap_id: [String.t()] +          } +          | %{} + +  @ilike_criteria [:nickname, :name, :query] +  @equal_criteria [:email] +  @role_criteria [:is_admin, :is_moderator] +  @contains_criteria [:ap_id, :nickname] + +  @spec build(criteria()) :: Query.t() +  def build(query \\ base_query(), criteria) do +    prepare_query(query, criteria) +  end + +  @spec paginate(Ecto.Query.t(), pos_integer(), pos_integer()) :: Ecto.Query.t() +  def paginate(query, page, page_size) do +    from(u in query, +      limit: ^page_size, +      offset: ^((page - 1) * page_size) +    ) +  end + +  defp base_query do +    from(u in User) +  end + +  defp prepare_query(query, criteria) do +    Enum.reduce(criteria, query, &compose_query/2) +  end + +  defp compose_query({key, value}, query) +       when key in @ilike_criteria and not_empty_string(value) do +    # hack for :query key +    key = if key == :query, do: :nickname, else: key +    where(query, [u], ilike(field(u, ^key), ^"%#{value}%")) +  end + +  defp compose_query({key, value}, query) +       when key in @equal_criteria and not_empty_string(value) do +    where(query, [u], ^[{key, value}]) +  end + +  defp compose_query({key, values}, query) when key in @contains_criteria and is_list(values) do +    where(query, [u], field(u, ^key) in ^values) +  end + +  defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0 do +    Enum.reduce(tags, query, &prepare_tag_criteria/2) +  end + +  defp compose_query({key, _}, query) when key in @role_criteria do +    where(query, [u], fragment("(?->? @> 'true')", u.info, ^to_string(key))) +  end + +  defp compose_query({:super_users, _}, query) do +    where( +      query, +      [u], +      fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info) +    ) +  end + +  defp compose_query({:local, _}, query), do: location_query(query, true) + +  defp compose_query({:external, _}, query), do: location_query(query, false) + +  defp compose_query({:active, _}, query) do +    where(query, [u], fragment("not (?->'deactivated' @> 'true')", u.info)) +    |> where([u], not is_nil(u.nickname)) +  end + +  defp compose_query({:deactivated, _}, query) do +    where(query, [u], fragment("?->'deactivated' @> 'true'", u.info)) +    |> where([u], not is_nil(u.nickname)) +  end + +  defp compose_query({:followers, %User{id: id, follower_address: follower_address}}, query) do +    where(query, [u], fragment("? <@ ?", ^[follower_address], u.following)) +    |> where([u], u.id != ^id) +  end + +  defp compose_query({:friends, %User{id: id, following: following}}, query) do +    where(query, [u], u.follower_address in ^following) +    |> where([u], u.id != ^id) +  end + +  defp compose_query({:recipients_from_activity, to}, query) do +    where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to)) +  end + +  defp compose_query(_unsupported_param, query), do: query + +  defp prepare_tag_criteria(tag, query) do +    or_where(query, [u], fragment("? = any(?)", ^tag, u.tags)) +  end + +  defp location_query(query, local) do +    where(query, [u], u.local == ^local) +    |> where([u], not is_nil(u.nickname)) +  end +end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 711f233a6..b553d96a8 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -101,7 +101,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      search_params = %{        query: params["query"],        page: page, -      page_size: page_size +      page_size: page_size, +      tags: params["tags"], +      name: params["name"], +      email: params["email"]      }      with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)), @@ -116,11 +119,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do             )    end -  @filters ~w(local external active deactivated) +  @filters ~w(local external active deactivated is_admin is_moderator) +  @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}    defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{} -  @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}    defp maybe_parse_filters(filters) do      filters      |> String.split(",") diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex index 9a8e41c2a..ed919833e 100644 --- a/lib/pleroma/web/admin_api/search.ex +++ b/lib/pleroma/web/admin_api/search.ex @@ -10,45 +10,23 @@ defmodule Pleroma.Web.AdminAPI.Search do    @page_size 50 -  def user(%{query: term} = params) when is_nil(term) or term == "" do -    query = maybe_filtered_query(params) +  defmacro not_empty_string(string) do +    quote do +      is_binary(unquote(string)) and unquote(string) != "" +    end +  end + +  @spec user(map()) :: {:ok, [User.t()], pos_integer()} +  def user(params \\ %{}) do +    query = User.Query.build(params) |> order_by([u], u.nickname)      paginated_query = -      maybe_filtered_query(params) -      |> paginate(params[:page] || 1, params[:page_size] || @page_size) +      User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size) -    count = query |> Repo.aggregate(:count, :id) +    count = Repo.aggregate(query, :count, :id)      results = Repo.all(paginated_query)      {:ok, results, count}    end - -  def user(%{query: term} = params) when is_binary(term) do -    search_query = from(u in maybe_filtered_query(params), where: ilike(u.nickname, ^"%#{term}%")) - -    count = search_query |> Repo.aggregate(:count, :id) - -    results = -      search_query -      |> paginate(params[:page] || 1, params[:page_size] || @page_size) -      |> Repo.all() - -    {:ok, results, count} -  end - -  defp maybe_filtered_query(params) do -    from(u in User, order_by: u.nickname) -    |> User.maybe_local_user_query(params[:local]) -    |> User.maybe_external_user_query(params[:external]) -    |> User.maybe_active_user_query(params[:active]) -    |> User.maybe_deactivated_user_query(params[:deactivated]) -  end - -  defp paginate(query, page, page_size) do -    from(u in query, -      limit: ^page_size, -      offset: ^((page - 1) * page_size) -    ) -  end  end diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 181483664..55706eeb8 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -10,12 +10,6 @@ defmodule Pleroma.Web.ControllerHelper do    def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil    def truthy_param?(value), do: value not in @falsy_param_values -  def oauth_scopes(params, default) do -    # Note: `scopes` is used by Mastodon — supporting it but sticking to -    # OAuth's standard `scope` wherever we control it -    Pleroma.Web.OAuth.parse_scopes(params["scope"] || params["scopes"], default) -  end -    def json_response(conn, status, json) do      conn      |> put_status(status) diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 7f939991d..9ef30e885 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -29,6 +29,13 @@ defmodule Pleroma.Web.Endpoint do      # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength    ) +  plug(Plug.Static.IndexHtml, at: "/pleroma/admin/") + +  plug(Plug.Static, +    at: "/pleroma/admin/", +    from: {:pleroma, "priv/static/adminfe/"} +  ) +    # Code reloading can be explicitly enabled under the    # :code_reloader configuration of your endpoint.    if code_reloading? do diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 83ad90989..956736780 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -37,6 +37,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    alias Pleroma.Web.MediaProxy    alias Pleroma.Web.OAuth.App    alias Pleroma.Web.OAuth.Authorization +  alias Pleroma.Web.OAuth.Scopes    alias Pleroma.Web.OAuth.Token    alias Pleroma.Web.ControllerHelper @@ -50,7 +51,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    action_fallback(:errors)    def create_app(conn, params) do -    scopes = ControllerHelper.oauth_scopes(params, ["read"]) +    scopes = Scopes.fetch_scopes(params, ["read"])      app_attrs =        params diff --git a/lib/pleroma/web/oauth.ex b/lib/pleroma/web/oauth.ex index d2835a0ba..280cf28c0 100644 --- a/lib/pleroma/web/oauth.ex +++ b/lib/pleroma/web/oauth.ex @@ -3,18 +3,4 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.OAuth do -  def parse_scopes(scopes, _default) when is_list(scopes) do -    Enum.filter(scopes, &(&1 not in [nil, ""])) -  end - -  def parse_scopes(scopes, default) when is_binary(scopes) do -    scopes -    |> String.trim() -    |> String.split(~r/[\s,]+/) -    |> parse_scopes(default) -  end - -  def parse_scopes(_, default) do -    default -  end  end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index e3c01217d..8ee0da667 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -15,8 +15,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do    alias Pleroma.Web.OAuth.Token    alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken    alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken - -  import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2] +  alias Pleroma.Web.OAuth.Scopes    if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth) @@ -57,7 +56,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do    defp do_authorize(conn, params) do      app = Repo.get_by(App, client_id: params["client_id"])      available_scopes = (app && app.scopes) || [] -    scopes = oauth_scopes(params, nil) || available_scopes +    scopes = Scopes.fetch_scopes(params, available_scopes)      # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template      render(conn, Authenticator.auth_template(), %{ @@ -113,7 +112,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do    defp handle_create_authorization_error(           conn, -         {scopes_issue, _}, +         {:error, scopes_issue},           %{"authorization" => _} = params         )         when scopes_issue in [:unsupported_scopes, :missing_scopes] do @@ -184,9 +183,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do           %App{} = app <- get_app_from_request(conn, params),           {:auth_active, true} <- {:auth_active, User.auth_active?(user)},           {:user_active, true} <- {:user_active, !user.info.deactivated}, -         scopes <- oauth_scopes(params, app.scopes), -         [] <- scopes -- app.scopes, -         true <- Enum.any?(scopes), +         {:ok, scopes} <- validate_scopes(app, params),           {:ok, auth} <- Authorization.create_authorization(app, user, scopes),           {:ok, token} <- Token.exchange_token(app, auth) do        json(conn, response_token(user, token)) @@ -247,8 +244,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do    @doc "Prepares OAuth request to provider for Ueberauth"    def prepare_request(conn, %{"provider" => provider, "authorization" => auth_attrs}) do      scope = -      oauth_scopes(auth_attrs, []) -      |> Enum.join(" ") +      auth_attrs +      |> Scopes.fetch_scopes([]) +      |> Scopes.to_string()      state =        auth_attrs @@ -326,7 +324,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do        client_id: auth_attrs["client_id"],        redirect_uri: auth_attrs["redirect_uri"],        state: auth_attrs["state"], -      scopes: oauth_scopes(auth_attrs, []), +      scopes: Scopes.fetch_scopes(auth_attrs, []),        nickname: auth_attrs["nickname"],        email: auth_attrs["email"]      }) @@ -401,10 +399,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do             {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},           %App{} = app <- Repo.get_by(App, client_id: client_id),           true <- redirect_uri in String.split(app.redirect_uris), -         scopes <- oauth_scopes(auth_attrs, []), -         {:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes}, -         # Note: `scope` param is intentionally not optional in this context -         {:missing_scopes, false} <- {:missing_scopes, scopes == []}, +         {:ok, scopes} <- validate_scopes(app, auth_attrs),           {:auth_active, true} <- {:auth_active, User.auth_active?(user)} do        Authorization.create_authorization(app, user, scopes)      end @@ -458,4 +453,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do      }      |> Map.merge(opts)    end + +  @spec validate_scopes(App.t(), map()) :: +          {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} +  defp validate_scopes(app, params) do +    params +    |> Scopes.fetch_scopes(app.scopes) +    |> Scopes.validates(app.scopes) +  end  end diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex new file mode 100644 index 000000000..ad9dfb260 --- /dev/null +++ b/lib/pleroma/web/oauth/scopes.ex @@ -0,0 +1,67 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Scopes do +  @moduledoc """ +  Functions for dealing with scopes. +  """ + +  @doc """ +  Fetch scopes from requiest params. + +  Note: `scopes` is used by Mastodon — supporting it but sticking to +  OAuth's standard `scope` wherever we control it +  """ +  @spec fetch_scopes(map(), list()) :: list() +  def fetch_scopes(params, default) do +    parse_scopes(params["scope"] || params["scopes"], default) +  end + +  def parse_scopes(scopes, _default) when is_list(scopes) do +    Enum.filter(scopes, &(&1 not in [nil, ""])) +  end + +  def parse_scopes(scopes, default) when is_binary(scopes) do +    scopes +    |> to_list +    |> parse_scopes(default) +  end + +  def parse_scopes(_, default) do +    default +  end + +  @doc """ +  Convert scopes string to list +  """ +  @spec to_list(binary()) :: [binary()] +  def to_list(nil), do: [] + +  def to_list(str) do +    str +    |> String.trim() +    |> String.split(~r/[\s,]+/) +  end + +  @doc """ +  Convert scopes list to string +  """ +  @spec to_string(list()) :: binary() +  def to_string(scopes), do: Enum.join(scopes, " ") + +  @doc """ +  Validates scopes. +  """ +  @spec validates(list() | nil, list()) :: +          {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} +  def validates([], _app_scopes), do: {:error, :missing_scopes} +  def validates(nil, _app_scopes), do: {:error, :missing_scopes} + +  def validates(scopes, app_scopes) do +    case scopes -- app_scopes do +      [] -> {:ok, scopes} +      _ -> {:error, :unsupported_scopes} +    end +  end +end diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index c64152da8..d084ad734 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -170,7 +170,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do      created_at = activity.data["published"] |> Utils.date_to_asctime()      announced_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) -    text = "#{user.nickname} retweeted a status." +    text = "#{user.nickname} repeated a status."      retweeted_status = render("activity.json", Map.merge(opts, %{activity: announced_activity})) | 
