Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
12 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/web/oauth.ex b/lib/pleroma/web/oauth.ex
index d2835a0ba..f3bac33c8 100644
--- a/lib/pleroma/web/oauth.ex
+++ b/lib/pleroma/web/oauth.ex
@@ -1,20 +1,28 @@
# 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 do
+ @authenticator Application.get_env(
+ :pleroma,
+ Pleroma.Web.AuthenticatorAdapter,
+ Pleroma.Web.Authenticator
+ )
+
+ def authenticator, do: @authenticator
+
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/authenticator.ex b/lib/pleroma/web/oauth/authenticator.ex
new file mode 100644
index 000000000..86bbc41f0
--- /dev/null
+++ b/lib/pleroma/web/oauth/authenticator.ex
@@ -0,0 +1,22 @@
+defmodule Pleroma.Web.Authenticator do
+ alias Pleroma.User
+ alias Comeonin.Pbkdf2
+
+ @behaviour Pleroma.Web.AuthenticatorAdapter
+
+ def get_user(%Plug.Conn{} = conn) do
+ %{"authorization" => %{"name" => name, "password" => password}} = conn.params
+
+ with {_, %User{} = user} <- {:user, User.get_by_nickname_or_email(name)},
+ {_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do
+ {:ok, user}
+ else
+ error ->
+ {:error, error}
+ end
+ end
+
+ def handle_error(%Plug.Conn{} = _conn, error) do
+ error
+ end
+end
diff --git a/lib/pleroma/web/oauth/authenticator_adapter.ex b/lib/pleroma/web/oauth/authenticator_adapter.ex
new file mode 100644
index 000000000..282963b1c
--- /dev/null
+++ b/lib/pleroma/web/oauth/authenticator_adapter.ex
@@ -0,0 +1,7 @@
+defmodule Pleroma.Web.AuthenticatorAdapter do
+ alias Pleroma.User
+
+ @callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
+
+ @callback handle_error(Plug.Conn.t(), any()) :: any()
+end
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 7c1a3adbd..abe6fd2f2 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -1,218 +1,219 @@
# 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.OAuthController do
use Pleroma.Web, :controller
+ alias Pleroma.Web.OAuth
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.App
alias Pleroma.Repo
alias Pleroma.User
alias Comeonin.Pbkdf2
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
plug(:fetch_session)
plug(:fetch_flash)
action_fallback(Pleroma.Web.OAuth.FallbackController)
def 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
- render(conn, "show.html", %{
+ template = Application.get_env(:pleroma, :auth_template, "show.html")
+
+ render(conn, template, %{
response_type: params["response_type"],
client_id: params["client_id"],
available_scopes: available_scopes,
scopes: scopes,
redirect_uri: params["redirect_uri"],
- state: params["state"]
+ state: params["state"],
+ params: params
})
end
def create_authorization(conn, %{
"authorization" =>
%{
- "name" => name,
- "password" => password,
"client_id" => client_id,
"redirect_uri" => redirect_uri
} = auth_params
}) do
- with %User{} = user <- User.get_by_nickname_or_email(name),
- true <- Pbkdf2.checkpw(password, user.password_hash),
+ with {_, {:ok, %User{} = user}} <- {:get_user, OAuth.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_params, []),
{:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
# Note: `scope` param is intentionally not optional in this context
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
- # Special case: Local MastodonFE.
redirect_uri =
if redirect_uri == "." do
+ # Special case: Local MastodonFE
mastodon_api_url(conn, :login)
else
redirect_uri
end
cond do
redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
render(conn, "results.html", %{
auth: auth
})
true ->
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
url = "#{redirect_uri}#{connector}"
url_params = %{:code => auth.token}
url_params =
if auth_params["state"] do
Map.put(url_params, :state, auth_params["state"])
else
url_params
end
url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
redirect(conn, external: url)
end
else
{scopes_issue, _} when scopes_issue in [:unsupported_scopes, :missing_scopes] ->
conn
|> put_flash(:error, "Permissions not specified.")
|> put_status(:unauthorized)
|> authorize(auth_params)
{:auth_active, false} ->
conn
|> put_flash(:error, "Account confirmation pending.")
|> put_status(:forbidden)
|> authorize(auth_params)
error ->
- error
+ OAuth.authenticator().handle_error(conn, error)
end
end
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
with %App{} = app <- get_app_from_request(conn, params),
fixed_token = fix_padding(params["code"]),
%Authorization{} = auth <-
Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
{:ok, token} <- Token.exchange_token(app, auth),
{:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
response = %{
token_type: "Bearer",
access_token: token.token,
refresh_token: token.refresh_token,
created_at: DateTime.to_unix(inserted_at),
expires_in: 60 * 10,
scope: Enum.join(token.scopes)
}
json(conn, response)
else
_error ->
put_status(conn, 400)
|> json(%{error: "Invalid credentials"})
end
end
def token_exchange(
conn,
%{"grant_type" => "password", "username" => name, "password" => password} = params
) do
with %App{} = app <- get_app_from_request(conn, params),
%User{} = user <- User.get_by_nickname_or_email(name),
true <- Pbkdf2.checkpw(password, user.password_hash),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
scopes <- oauth_scopes(params, app.scopes),
[] <- scopes -- app.scopes,
true <- Enum.any?(scopes),
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:ok, token} <- Token.exchange_token(app, auth) do
response = %{
token_type: "Bearer",
access_token: token.token,
refresh_token: token.refresh_token,
expires_in: 60 * 10,
scope: Enum.join(token.scopes, " ")
}
json(conn, response)
else
{:auth_active, false} ->
conn
|> put_status(:forbidden)
|> json(%{error: "Account confirmation pending"})
_error ->
put_status(conn, 400)
|> json(%{error: "Invalid credentials"})
end
end
def token_exchange(
conn,
%{"grant_type" => "password", "name" => name, "password" => _password} = params
) do
params =
params
|> Map.delete("name")
|> Map.put("username", name)
token_exchange(conn, params)
end
def token_revoke(conn, %{"token" => token} = params) do
with %App{} = app <- get_app_from_request(conn, params),
%Token{} = token <- Repo.get_by(Token, token: token, app_id: app.id),
{:ok, %Token{}} <- Repo.delete(token) do
json(conn, %{})
else
_error ->
# RFC 7009: invalid tokens [in the request] do not cause an error response
json(conn, %{})
end
end
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
# decoding it. Investigate sometime.
defp fix_padding(token) do
token
|> URI.decode()
|> Base.url_decode64!(padding: false)
|> Base.url_encode64(padding: false)
end
defp get_app_from_request(conn, params) do
# Per RFC 6749, HTTP Basic is preferred to body params
{client_id, client_secret} =
with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
{:ok, decoded} <- Base.decode64(encoded),
[id, secret] <-
String.split(decoded, ":")
|> Enum.map(fn s -> URI.decode_www_form(s) end) do
{id, secret}
else
_ -> {params["client_id"], params["client_secret"]}
end
if client_id && client_secret do
Repo.get_by(
App,
client_id: client_id,
client_secret: client_secret
)
else
nil
end
end
end
diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex
index 853aa2a87..4f618743d 100644
--- a/lib/pleroma/web/web.ex
+++ b/lib/pleroma/web/web.ex
@@ -1,101 +1,103 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web do
@moduledoc """
A module that keeps using definitions for controllers,
views and so on.
This can be used in your application as:
use Pleroma.Web, :controller
use Pleroma.Web, :view
The definitions below will be executed for every view,
controller, etc, so keep them short and clean, focused
on imports, uses and aliases.
Do NOT define functions inside the quoted expressions
below.
"""
def controller do
quote do
use Phoenix.Controller, namespace: Pleroma.Web
import Plug.Conn
import Pleroma.Web.Gettext
import Pleroma.Web.Router.Helpers
+
+ plug(:put_layout, Application.get_env(:pleroma, :app_template, "app.html"))
end
end
def view do
quote do
use Phoenix.View,
root: "lib/pleroma/web/templates",
namespace: Pleroma.Web
# Import convenience functions from controllers
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
import Pleroma.Web.ErrorHelpers
import Pleroma.Web.Gettext
import Pleroma.Web.Router.Helpers
require Logger
@doc "Same as `render/3` but wrapped in a rescue block"
def safe_render(view, template, assigns \\ %{}) do
Phoenix.View.render(view, template, assigns)
rescue
error ->
Logger.error(
"#{__MODULE__} failed to render #{inspect({view, template})}: #{inspect(error)}"
)
Logger.error(inspect(__STACKTRACE__))
nil
end
@doc """
Same as `render_many/4` but wrapped in rescue block.
"""
def safe_render_many(collection, view, template, assigns \\ %{}) do
Enum.map(collection, fn resource ->
as = Map.get(assigns, :as) || view.__resource__
assigns = Map.put(assigns, as, resource)
safe_render(view, template, assigns)
end)
|> Enum.filter(& &1)
end
end
end
def router do
quote do
use Phoenix.Router
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
import Plug.Conn
import Phoenix.Controller
end
end
def channel do
quote do
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
use Phoenix.Channel
import Pleroma.Web.Gettext
end
end
@doc """
When used, dispatch to the appropriate controller/view/etc.
"""
defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
def base_url do
Pleroma.Web.Endpoint.url()
end
end

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 2, 9:25 AM (2 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1804184
Default Alt Text
(12 KB)

Event Timeline