summaryrefslogtreecommitdiff
path: root/lib/pleroma/web/o_auth/token
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/web/o_auth/token')
-rw-r--r--lib/pleroma/web/o_auth/token/query.ex49
-rw-r--r--lib/pleroma/web/o_auth/token/strategy/refresh_token.ex58
-rw-r--r--lib/pleroma/web/o_auth/token/strategy/revoke.ex26
-rw-r--r--lib/pleroma/web/o_auth/token/utils.ex72
4 files changed, 205 insertions, 0 deletions
diff --git a/lib/pleroma/web/o_auth/token/query.ex b/lib/pleroma/web/o_auth/token/query.ex
new file mode 100644
index 000000000..fd6d9b112
--- /dev/null
+++ b/lib/pleroma/web/o_auth/token/query.ex
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.OAuth.Token.Query do
+ @moduledoc """
+ Contains queries for OAuth Token.
+ """
+
+ import Ecto.Query, only: [from: 2]
+
+ @type query :: Ecto.Queryable.t() | Token.t()
+
+ alias Pleroma.Web.OAuth.Token
+
+ @spec get_by_refresh_token(query, String.t()) :: query
+ def get_by_refresh_token(query \\ Token, refresh_token) do
+ from(q in query, where: q.refresh_token == ^refresh_token)
+ end
+
+ @spec get_by_token(query, String.t()) :: query
+ def get_by_token(query \\ Token, token) do
+ from(q in query, where: q.token == ^token)
+ end
+
+ @spec get_by_app(query, String.t()) :: query
+ def get_by_app(query \\ Token, app_id) do
+ from(q in query, where: q.app_id == ^app_id)
+ end
+
+ @spec get_by_id(query, String.t()) :: query
+ def get_by_id(query \\ Token, id) do
+ from(q in query, where: q.id == ^id)
+ end
+
+ @spec get_by_user(query, String.t()) :: query
+ def get_by_user(query \\ Token, user_id) do
+ from(q in query, where: q.user_id == ^user_id)
+ end
+
+ @spec preload(query, any) :: query
+ def preload(query \\ Token, assoc_preload \\ [])
+
+ def preload(query, assoc_preload) when is_list(assoc_preload) do
+ from(q in query, preload: ^assoc_preload)
+ end
+
+ def preload(query, _assoc_preload), do: query
+end
diff --git a/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex
new file mode 100644
index 000000000..625b0fde2
--- /dev/null
+++ b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex
@@ -0,0 +1,58 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
+ @moduledoc """
+ Functions for dealing with refresh token strategy.
+ """
+
+ alias Pleroma.Config
+ alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.OAuth.Token.Strategy.Revoke
+
+ @doc """
+ Will grant access token by refresh token.
+ """
+ @spec grant(Token.t()) :: {:ok, Token.t()} | {:error, any()}
+ def grant(token) do
+ access_token = Repo.preload(token, [:user, :app])
+
+ result =
+ Repo.transaction(fn ->
+ token_params = %{
+ app: access_token.app,
+ user: access_token.user,
+ scopes: access_token.scopes
+ }
+
+ access_token
+ |> revoke_access_token()
+ |> create_access_token(token_params)
+ end)
+
+ case result do
+ {:ok, {:error, reason}} -> {:error, reason}
+ {:ok, {:ok, token}} -> {:ok, token}
+ {:error, reason} -> {:error, reason}
+ end
+ end
+
+ defp revoke_access_token(token) do
+ Revoke.revoke(token)
+ end
+
+ defp create_access_token({:error, error}, _), do: {:error, error}
+
+ defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do
+ Token.create(app, user, add_refresh_token(token_params, token.refresh_token))
+ end
+
+ defp add_refresh_token(params, token) do
+ case Config.get([:oauth2, :issue_new_refresh_token], false) do
+ true -> Map.put(params, :refresh_token, token)
+ false -> params
+ end
+ end
+end
diff --git a/lib/pleroma/web/o_auth/token/strategy/revoke.ex b/lib/pleroma/web/o_auth/token/strategy/revoke.ex
new file mode 100644
index 000000000..069c1ee21
--- /dev/null
+++ b/lib/pleroma/web/o_auth/token/strategy/revoke.ex
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
+ @moduledoc """
+ Functions for dealing with revocation.
+ """
+
+ alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Token
+
+ @doc "Finds and revokes access token for app and by token"
+ @spec revoke(App.t(), map()) :: {:ok, Token.t()} | {:error, :not_found | Ecto.Changeset.t()}
+ def revoke(%App{} = app, %{"token" => token} = _attrs) do
+ with {:ok, token} <- Token.get_by_token(app, token),
+ do: revoke(token)
+ end
+
+ @doc "Revokes access token"
+ @spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
+ def revoke(%Token{} = token) do
+ Repo.delete(token)
+ end
+end
diff --git a/lib/pleroma/web/o_auth/token/utils.ex b/lib/pleroma/web/o_auth/token/utils.ex
new file mode 100644
index 000000000..43aeab6b0
--- /dev/null
+++ b/lib/pleroma/web/o_auth/token/utils.ex
@@ -0,0 +1,72 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.OAuth.Token.Utils do
+ @moduledoc """
+ Auxiliary functions for dealing with tokens.
+ """
+
+ alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.App
+
+ @doc "Fetch app by client credentials from request"
+ @spec fetch_app(Plug.Conn.t()) :: {:ok, App.t()} | {:error, :not_found}
+ def fetch_app(conn) do
+ res =
+ conn
+ |> fetch_client_credentials()
+ |> fetch_client
+
+ case res do
+ %App{} = app -> {:ok, app}
+ _ -> {:error, :not_found}
+ end
+ end
+
+ defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
+ Repo.get_by(App, client_id: id, client_secret: secret)
+ end
+
+ defp fetch_client({_id, _secret}), do: nil
+
+ defp fetch_client_credentials(conn) do
+ # Per RFC 6749, HTTP Basic is preferred to body params
+ with ["Basic " <> encoded] <- Plug.Conn.get_req_header(conn, "authorization"),
+ {:ok, decoded} <- Base.decode64(encoded),
+ [id, secret] <-
+ Enum.map(
+ String.split(decoded, ":"),
+ fn s -> URI.decode_www_form(s) end
+ ) do
+ {id, secret}
+ else
+ _ -> {conn.params["client_id"], conn.params["client_secret"]}
+ end
+ end
+
+ @doc "convert token inserted_at to unix timestamp"
+ def format_created_at(%{inserted_at: inserted_at} = _token) do
+ inserted_at
+ |> DateTime.from_naive!("Etc/UTC")
+ |> DateTime.to_unix()
+ end
+
+ @doc false
+ @spec generate_token(keyword()) :: binary()
+ def generate_token(opts \\ []) do
+ opts
+ |> Keyword.get(:size, 32)
+ |> :crypto.strong_rand_bytes()
+ |> Base.url_encode64(padding: false)
+ end
+
+ # XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
+ # decoding it. Investigate sometime.
+ def fix_padding(token) do
+ token
+ |> URI.decode()
+ |> Base.url_decode64!(padding: false)
+ |> Base.url_encode64(padding: false)
+ end
+end