aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/web/oauth/oauth_controller.ex
blob: 47962bbf868cff4bc8635bfcb1f5b67427c003b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
defmodule Pleroma.Web.OAuth.OAuthController do
  use Pleroma.Web, :controller

  alias Pleroma.Web.OAuth.{Authorization, Token, App}
  alias Pleroma.{Repo, User}
  alias Comeonin.Pbkdf2

  plug(:fetch_session)
  plug(:fetch_flash)

  action_fallback(Pleroma.Web.OAuth.FallbackController)

  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"],
      state: params["state"]
    })
  end

  def create_authorization(conn, %{
        "authorization" =>
          %{
            "name" => name,
            "password" => password,
            "client_id" => client_id,
            "redirect_uri" => redirect_uri
          } = params
      }) do
    with %User{} = user <- User.get_by_nickname_or_email(name),
         true <- Pbkdf2.checkpw(password, user.password_hash),
         %App{} = app <- Repo.get_by(App, client_id: client_id),
         {:ok, auth} <- Authorization.create_authorization(app, user) do
      if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
        render(conn, "results.html", %{
          auth: auth
        })
      else
        connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
        url = "#{redirect_uri}#{connector}code=#{auth.token}"

        url =
          if params["state"] do
            url <> "&state=#{params["state"]}"
          else
            url
          end

        redirect(conn, external: url)
      end
    end
  end

  # TODO
  # - proper scope handling
  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) 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)
    else
      _error ->
        put_status(conn, 400)
        |> json(%{error: "Invalid credentials"})
    end
  end

  # TODO
  # - investigate a way to verify the user wants to grant read/write/follow once scope handling is done
  def token_exchange(
        conn,
        %{"grant_type" => "password", "name" => 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),
         {:ok, auth} <- Authorization.create_authorization(app, user),
         {: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: "read write follow"
      }

      json(conn, response)
    else
      _error ->
        put_status(conn, 400)
        |> json(%{error: "Invalid credentials"})
    end
  end

  defp fix_padding(token) do
    token
    |> Base.url_decode64!(padding: false)
    |> Base.url_encode64()
  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