Add very basic oauth and mastodon api support.
authorRoger Braun <roger@rogerbraun.net>
Wed, 6 Sep 2017 17:06:25 +0000 (19:06 +0200)
committerRoger Braun <roger@rogerbraun.net>
Wed, 6 Sep 2017 17:06:25 +0000 (19:06 +0200)
16 files changed:
lib/pleroma/app.ex [new file with mode: 0644]
lib/pleroma/plugs/oauth_plug.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/mastodon_api.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex [new file with mode: 0644]
lib/pleroma/web/oauth/authorization.ex [new file with mode: 0644]
lib/pleroma/web/oauth/oauth_controller.ex [new file with mode: 0644]
lib/pleroma/web/oauth/oauth_view.ex [new file with mode: 0644]
lib/pleroma/web/oauth/token.ex [new file with mode: 0644]
lib/pleroma/web/router.ex
lib/pleroma/web/templates/layout/app.html.eex [new file with mode: 0644]
lib/pleroma/web/templates/o_auth/o_auth/results.html.eex [new file with mode: 0644]
lib/pleroma/web/templates/o_auth/o_auth/show.html.eex [new file with mode: 0644]
lib/pleroma/web/views/layout_view.ex [new file with mode: 0644]
priv/repo/migrations/20170906120646_add_mastodon_apps.exs [new file with mode: 0644]
priv/repo/migrations/20170906143140_create_o_auth_authorizations.exs [new file with mode: 0644]
priv/repo/migrations/20170906152508_create_o_auth_token.exs [new file with mode: 0644]

diff --git a/lib/pleroma/app.ex b/lib/pleroma/app.ex
new file mode 100644 (file)
index 0000000..d467595
--- /dev/null
@@ -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 (file)
index 0000000..fc2a907
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..89e37d6
--- /dev/null
@@ -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 (file)
index 0000000..9423c96
--- /dev/null
@@ -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 (file)
index 0000000..f0e091a
--- /dev/null
@@ -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 (file)
index 0000000..b3923fc
--- /dev/null
@@ -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 (file)
index 0000000..49e7242
--- /dev/null
@@ -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
index c20ec3e801904e656693a26e70def54e35c52837..6081016d68de1f4da0e7d05d809125b18f4a6852 100644 (file)
@@ -16,6 +16,7 @@ defmodule Pleroma.Web.Router 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 @@ defmodule Pleroma.Web.Router 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 (file)
index 0000000..6cc3b7a
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset=utf-8 />
+    <title>Pleroma</title>
+  </head>
+  <body>
+    <h1>Welcome to Pleroma</h1>
+    <%= render @view_module, @view_template, assigns %>
+  </body>
+</html>
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 (file)
index 0000000..8443d90
--- /dev/null
@@ -0,0 +1,2 @@
+<h1>Successfully authorized</h1>
+<h2>Token code is <%= @auth.token %></h2>
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 (file)
index 0000000..ce295ed
--- /dev/null
@@ -0,0 +1,14 @@
+<h2>OAuth Authorization</h2>
+<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
+<%= label f, :name, "Name" %>
+<%= text_input f, :name %>
+<br>
+<%= label f, :password, "Password" %>
+<%= password_input f, :password %>
+<br>
+<%= 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 (file)
index 0000000..d4d4c3b
--- /dev/null
@@ -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 (file)
index 0000000..d3dd317
--- /dev/null
@@ -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 (file)
index 0000000..b433287
--- /dev/null
@@ -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 (file)
index 0000000..7f8550f
--- /dev/null
@@ -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