From 2a298d70f9938d1b6d5af04d8b8863fdd3299f46 Mon Sep 17 00:00:00 2001 From: Roger Braun Date: Wed, 6 Sep 2017 19:06:25 +0200 Subject: [PATCH] Add very basic oauth and mastodon api support. --- lib/pleroma/app.ex | 29 ++++++++++++ lib/pleroma/plugs/oauth_plug.ex | 22 ++++++++++ lib/pleroma/web/mastodon_api/mastodon_api.ex | 0 .../mastodon_api/mastodon_api_controller.ex | 32 ++++++++++++++ lib/pleroma/web/oauth/authorization.ex | 30 +++++++++++++ lib/pleroma/web/oauth/oauth_controller.ex | 44 +++++++++++++++++++ lib/pleroma/web/oauth/oauth_view.ex | 4 ++ lib/pleroma/web/oauth/token.ex | 31 +++++++++++++ lib/pleroma/web/router.ex | 18 ++++++++ lib/pleroma/web/templates/layout/app.html.eex | 11 +++++ .../templates/o_auth/o_auth/results.html.eex | 2 + .../web/templates/o_auth/o_auth/show.html.eex | 14 ++++++ lib/pleroma/web/views/layout_view.ex | 3 ++ .../20170906120646_add_mastodon_apps.exs | 16 +++++++ ...906143140_create_o_auth_authorizations.exs | 15 +++++++ .../20170906152508_create_o_auth_token.exs | 15 +++++++ 16 files changed, 286 insertions(+) create mode 100644 lib/pleroma/app.ex create mode 100644 lib/pleroma/plugs/oauth_plug.ex create mode 100644 lib/pleroma/web/mastodon_api/mastodon_api.ex create mode 100644 lib/pleroma/web/mastodon_api/mastodon_api_controller.ex create mode 100644 lib/pleroma/web/oauth/authorization.ex create mode 100644 lib/pleroma/web/oauth/oauth_controller.ex create mode 100644 lib/pleroma/web/oauth/oauth_view.ex create mode 100644 lib/pleroma/web/oauth/token.ex create mode 100644 lib/pleroma/web/templates/layout/app.html.eex create mode 100644 lib/pleroma/web/templates/o_auth/o_auth/results.html.eex create mode 100644 lib/pleroma/web/templates/o_auth/o_auth/show.html.eex create mode 100644 lib/pleroma/web/views/layout_view.ex create mode 100644 priv/repo/migrations/20170906120646_add_mastodon_apps.exs create mode 100644 priv/repo/migrations/20170906143140_create_o_auth_authorizations.exs create mode 100644 priv/repo/migrations/20170906152508_create_o_auth_token.exs diff --git a/lib/pleroma/app.ex b/lib/pleroma/app.ex new file mode 100644 index 000000000..d467595ea --- /dev/null +++ b/lib/pleroma/app.ex @@ -0,0 +1,29 @@ +defmodule Pleroma.App do + use Ecto.Schema + import Ecto.{Changeset} + + schema "apps" do + field :client_name, :string + field :redirect_uris, :string + field :scopes, :string + field :website, :string + field :client_id, :string + field :client_secret, :string + + timestamps() + end + + def register_changeset(struct, params \\ %{}) do + changeset = struct + |> cast(params, [:client_name, :redirect_uris, :scopes, :website]) + |> validate_required([:client_name, :redirect_uris, :scopes]) + + if changeset.valid? do + changeset + |> put_change(:client_id, :crypto.strong_rand_bytes(32) |> Base.url_encode64) + |> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64) + else + changeset + end + end +end diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex new file mode 100644 index 000000000..fc2a907a2 --- /dev/null +++ b/lib/pleroma/plugs/oauth_plug.ex @@ -0,0 +1,22 @@ +defmodule Pleroma.Plugs.OAuthPlug do + import Plug.Conn + alias Pleroma.User + alias Pleroma.Repo + alias Pleroma.Web.OAuth.Token + + def init(options) do + options + end + + def call(%{assigns: %{user: %User{}}} = conn, _), do: conn + def call(conn, opts) do + with ["Bearer " <> header] <- get_req_header(conn, "authorization"), + %Token{user_id: user_id} <- Repo.get_by(Token, token: header), + %User{} = user <- Repo.get(User, user_id) do + conn + |> assign(:user, user) + else + _ -> conn + end + end +end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex new file mode 100644 index 000000000..e69de29bb diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex new file mode 100644 index 000000000..89e37d6ab --- /dev/null +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -0,0 +1,32 @@ +defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do + use Pleroma.Web, :controller + alias Pleroma.{Repo, App} + + def create_app(conn, params) do + with cs <- App.register_changeset(%App{}, params) |> IO.inspect, + {:ok, app} <- Repo.insert(cs) |> IO.inspect do + res = %{ + id: app.id, + client_id: app.client_id, + client_secret: app.client_secret + } + + json(conn, res) + end + end + + def verify_credentials(%{assigns: %{user: user}} = conn, params) do + account = %{ + id: user.id, + username: user.nickname, + acct: user.nickname, + display_name: user.name, + locked: false, + created_at: user.inserted_at, + note: user.bio, + url: "" + } + + json(conn, account) + end +end diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex new file mode 100644 index 000000000..9423c9632 --- /dev/null +++ b/lib/pleroma/web/oauth/authorization.ex @@ -0,0 +1,30 @@ +defmodule Pleroma.Web.OAuth.Authorization do + use Ecto.Schema + + alias Pleroma.{App, User, Repo} + alias Pleroma.Web.OAuth.Authorization + + schema "oauth_authorizations" do + field :token, :string + field :valid_until, :naive_datetime + field :used, :boolean, default: false + belongs_to :user, Pleroma.User + belongs_to :app, Pleroma.App + + timestamps() + end + + def create_authorization(%App{} = app, %User{} = user) do + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64 + + authorization = %Authorization{ + token: token, + used: false, + user_id: user.id, + app_id: app.id, + valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 60 * 10) + } + + Repo.insert(authorization) + end +end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex new file mode 100644 index 000000000..f0e091ac2 --- /dev/null +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -0,0 +1,44 @@ +defmodule Pleroma.Web.OAuth.OAuthController do + use Pleroma.Web, :controller + + alias Pleroma.Web.OAuth.{Authorization, Token} + alias Pleroma.{Repo, User, App} + alias Comeonin.Pbkdf2 + + def authorize(conn, params) do + render conn, "show.html", %{ + response_type: params["response_type"], + client_id: params["client_id"], + scope: params["scope"], + redirect_uri: params["redirect_uri"] + } + end + + def create_authorization(conn, %{"authorization" => %{"name" => name, "password" => password, "client_id" => client_id}} = params) do + with %User{} = user <- User.get_cached_by_nickname(name), + true <- Pbkdf2.checkpw(password, user.password_hash), + %App{} = app <- Pleroma.Repo.get_by(Pleroma.App, client_id: client_id), + {:ok, auth} <- Authorization.create_authorization(app, user) do + render conn, "results.html", %{ + auth: auth + } + end + end + + # TODO CRITICAL + # - Check validity of auth token + def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do + with %App{} = app <- Repo.get_by(App, client_id: params["client_id"], client_secret: params["client_secret"]), + %Authorization{} = auth <- Repo.get_by(Authorization, token: params["code"], app_id: app.id), + {:ok, token} <- Token.create_token(app, Repo.get(User, auth.user_id)) do + response = %{ + token_type: "Bearer", + access_token: token.token, + refresh_token: token.refresh_token, + expires_in: 60 * 10, + scope: "read write follow" + } + json(conn, response) + end + end +end diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/oauth/oauth_view.ex new file mode 100644 index 000000000..b3923fcf5 --- /dev/null +++ b/lib/pleroma/web/oauth/oauth_view.ex @@ -0,0 +1,4 @@ +defmodule Pleroma.Web.OAuth.OAuthView do + use Pleroma.Web, :view + import Phoenix.HTML.Form +end diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex new file mode 100644 index 000000000..49e72428c --- /dev/null +++ b/lib/pleroma/web/oauth/token.ex @@ -0,0 +1,31 @@ +defmodule Pleroma.Web.OAuth.Token do + use Ecto.Schema + + alias Pleroma.{App, User, Repo} + alias Pleroma.Web.OAuth.Token + + schema "oauth_tokens" do + field :token, :string + field :refresh_token, :string + field :valid_until, :naive_datetime + belongs_to :user, Pleroma.User + belongs_to :app, Pleroma.App + + timestamps() + end + + def create_token(%App{} = app, %User{} = user) do + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64 + refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64 + + token = %Token{ + token: token, + refresh_token: refresh_token, + user_id: user.id, + app_id: app.id, + valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 60 * 10) + } + + Repo.insert(token) + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c20ec3e80..6081016d6 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -16,6 +16,7 @@ def user_fetcher(username) do pipeline :authenticated_api do plug :accepts, ["json"] plug :fetch_session + plug Pleroma.Plugs.OAuthPlug plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1} end @@ -31,10 +32,27 @@ def user_fetcher(username) do plug :accepts, ["json"] end + pipeline :oauth do + plug :accepts, ["html", "json"] + end + + scope "/oauth", Pleroma.Web.OAuth do + get "/authorize", OAuthController, :authorize + post "/authorize", OAuthController, :create_authorization + post "/token", OAuthController, :token_exchange + end + scope "/api/v1", Pleroma.Web do pipe_through :masto_config # TODO: Move this get "/instance", TwitterAPI.UtilController, :masto_instance + post "/apps", MastodonAPI.MastodonAPIController, :create_app + end + + scope "/api/v1", Pleroma.Web.MastodonAPI do + pipe_through :authenticated_api + + get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials end scope "/api", Pleroma.Web do diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex new file mode 100644 index 000000000..6cc3b7ac5 --- /dev/null +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -0,0 +1,11 @@ + + + + + Pleroma + + +

Welcome to Pleroma

+ <%= render @view_module, @view_template, assigns %> + + diff --git a/lib/pleroma/web/templates/o_auth/o_auth/results.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/results.html.eex new file mode 100644 index 000000000..8443d906b --- /dev/null +++ b/lib/pleroma/web/templates/o_auth/o_auth/results.html.eex @@ -0,0 +1,2 @@ +

Successfully authorized

+

Token code is <%= @auth.token %>

diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex new file mode 100644 index 000000000..ce295ed05 --- /dev/null +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -0,0 +1,14 @@ +

OAuth Authorization

+<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> +<%= label f, :name, "Name" %> +<%= text_input f, :name %> +
+<%= label f, :password, "Password" %> +<%= password_input f, :password %> +
+<%= hidden_input f, :client_id, value: @client_id %> +<%= hidden_input f, :response_type, value: @response_type %> +<%= hidden_input f, :redirect_uri, value: @redirect_uri %> +<%= hidden_input f, :scope, value: @scope %> +<%= submit "Authorize" %> +<% end %> diff --git a/lib/pleroma/web/views/layout_view.ex b/lib/pleroma/web/views/layout_view.ex new file mode 100644 index 000000000..d4d4c3bd3 --- /dev/null +++ b/lib/pleroma/web/views/layout_view.ex @@ -0,0 +1,3 @@ +defmodule Pleroma.Web.LayoutView do + use Pleroma.Web, :view +end diff --git a/priv/repo/migrations/20170906120646_add_mastodon_apps.exs b/priv/repo/migrations/20170906120646_add_mastodon_apps.exs new file mode 100644 index 000000000..d3dd317dd --- /dev/null +++ b/priv/repo/migrations/20170906120646_add_mastodon_apps.exs @@ -0,0 +1,16 @@ +defmodule Pleroma.Repo.Migrations.AddMastodonApps do + use Ecto.Migration + + def change do + create table(:apps) do + add :client_name, :string + add :redirect_uris, :string + add :scopes, :string + add :website, :string + add :client_id, :string + add :client_secret, :string + + timestamps() + end + end +end diff --git a/priv/repo/migrations/20170906143140_create_o_auth_authorizations.exs b/priv/repo/migrations/20170906143140_create_o_auth_authorizations.exs new file mode 100644 index 000000000..b4332870e --- /dev/null +++ b/priv/repo/migrations/20170906143140_create_o_auth_authorizations.exs @@ -0,0 +1,15 @@ +defmodule Pleroma.Repo.Migrations.CreateOAuthAuthorizations do + use Ecto.Migration + + def change do + create table(:oauth_authorizations) do + add :app_id, references(:apps) + add :user_id, references(:users) + add :token, :string + add :valid_until, :naive_datetime + add :used, :boolean, default: false + + timestamps() + end + end +end diff --git a/priv/repo/migrations/20170906152508_create_o_auth_token.exs b/priv/repo/migrations/20170906152508_create_o_auth_token.exs new file mode 100644 index 000000000..7f8550f33 --- /dev/null +++ b/priv/repo/migrations/20170906152508_create_o_auth_token.exs @@ -0,0 +1,15 @@ +defmodule Pleroma.Repo.Migrations.CreateOAuthToken do + use Ecto.Migration + + def change do + create table(:oauth_tokens) do + add :app_id, references(:apps) + add :user_id, references(:users) + add :token, :string + add :refresh_token, :string + add :valid_until, :naive_datetime + + timestamps() + end + end +end