Skip to content

Commit

Permalink
v0.9.0 (#70)
Browse files Browse the repository at this point in the history
* update dependencies

* bump version

* fixup preferred_cli_env

* bump version in README

* set logger to info since bypass doesn't seem to work right

* support request options

* update client docs

* remove param_key

* clean up response module

* support async response processing

* fix code after rebase

* various fixes

* fix up bang functions to raise the correct error

* convert response to an %Error{} when raising
  • Loading branch information
scrogson committed Feb 2, 2017
1 parent 05a0fa6 commit f82d5f1
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 85 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
language: elixir
elixir:
- 1.2.6
- 1.3.2
- 1.3.4
- 1.4.0
otp_release:
- 18.3
- 19.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ end

defp deps do
# Add the dependency
[{:oauth2, "~> 0.8"}]
[{:oauth2, "~> 0.9"}]
end
```

Expand Down
5 changes: 3 additions & 2 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use Mix.Config

config :bypass, enable_debug_log: false
config :logger, level: :info

config :oauth2,
client_id: "0bee1126b1a1381d9cab60bcd52349484451808a", # first commit sha of this library
client_secret: "f715d64092fe81c396ac383e97f8a7eca40e7c89", #second commit sha
redirect_uri: "http://example.com/auth/callback",
serializers: %{"application/json" => Poison}
serializers: %{"application/json" => Poison},
request_opts: []
94 changes: 53 additions & 41 deletions lib/oauth2/client.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule OAuth2.Client do
@moduledoc """
@moduledoc ~S"""
This module defines the `OAuth2.Client` struct and is responsible for building
and establishing a request for an access token.
Expand All @@ -13,23 +13,20 @@ defmodule OAuth2.Client do
### Examples
```
client = OAuth2.Client.new([{:token,"abc123"}])
client = OAuth2.Client.new(token: "abc123")
case OAuth2.Client.get(client, "/some/resource") do
{:ok, %OAuth2.Response{status_code: 401}} ->
"Not Good"
{:ok, %OAuth2.Response{status_code: status_code, body: body}} when status_code in [200..299] ->
"Yay!!"
{:error, %OAuth2.Error{reason: reason}} ->
reason
end
response = OAuth2.Client.get!(client, "/some/resource")
case OAuth2.Client.get(client, "/some/resource") do
{:ok, %OAuth2.Response{body: body}} ->
"Yay!!"
{:error, %OAuth2.Response{body: body}} ->
"Something bad happen: #{inspect body}"
{:error, %OAuth2.Error{reason: reason}} ->
reason
end
response = OAuth2.Client.post!(client, "/some/other/resources", %{foo: "bar"})
```
response = OAuth2.Client.get!(client, "/some/resource")
response = OAuth2.Client.post!(client, "/some/other/resources", %{foo: "bar"})
"""

alias OAuth2.{AccessToken, Client, Error, Request}
Expand All @@ -42,6 +39,8 @@ defmodule OAuth2.Client do
@type param :: binary | %{binary => param} | [param]
@type params :: %{binary => param} | Keyword.t
@type redirect_uri :: binary
@type ref :: reference
@type request_opts :: Keyword.t
@type site :: binary
@type strategy :: module
@type token :: AccessToken.t | nil
Expand All @@ -55,6 +54,8 @@ defmodule OAuth2.Client do
headers: headers,
params: params,
redirect_uri: redirect_uri,
ref: ref,
request_opts: request_opts,
site: site,
strategy: strategy,
token: token,
Expand All @@ -68,14 +69,16 @@ defmodule OAuth2.Client do
headers: [],
params: %{},
redirect_uri: "",
ref: nil,
request_opts: [],
site: "",
strategy: OAuth2.Strategy.AuthCode,
token: nil,
token_method: :post,
token_url: "/oauth/token"

@doc """
Builds a new OAuth2 client struct using the `opts` provided.
Builds a new `OAuth2.Client` struct using the `opts` provided.
## Client struct fields
Expand All @@ -87,6 +90,9 @@ defmodule OAuth2.Client do
* `params` - a map of request parameters
* `redirect_uri` - the URI the provider should redirect to after authorization
or token requests
* `request_opts` - a keyword list of request options that will be sent to the
`hackney` client. See the [hackney documentation] for a list of available
options.
* `site` - the OAuth2 provider site host
* `strategy` - a module that implements the appropriate OAuth2 strategy,
default `OAuth2.Strategy.AuthCode`
Expand All @@ -98,27 +104,34 @@ defmodule OAuth2.Client do
## Example
iex> OAuth2.Client.new([{:token, "123"}])
%OAuth2.Client{authorize_url: "/oauth/authorize", client_id: "",
client_secret: "", headers: [], params: %{}, redirect_uri: "", site: "",
strategy: OAuth2.Strategy.AuthCode,
token: %OAuth2.AccessToken{access_token: "123", expires_at: nil,
other_params: %{}, refresh_token: nil, token_type: "Bearer"},
token_method: :post, token_url: "/oauth/token"}
iex> token = OAuth2.AccessToken.new("123")
iex> OAuth2.Client.new([{:token, token}])
%OAuth2.Client{authorize_url: "/oauth/authorize", client_id: "",
client_secret: "", headers: [], params: %{}, redirect_uri: "", site: "",
strategy: OAuth2.Strategy.AuthCode,
token: %OAuth2.AccessToken{access_token: "123", expires_at: nil,
other_params: %{}, refresh_token: nil, token_type: "Bearer"},
token_method: :post, token_url: "/oauth/token"}
iex> OAuth2.Client.new(token: "123")
%OAuth2.Client{authorize_url: "/oauth/authorize", client_id: "",
client_secret: "", headers: [], params: %{}, redirect_uri: "", site: "",
strategy: OAuth2.Strategy.AuthCode,
token: %OAuth2.AccessToken{access_token: "123", expires_at: nil,
other_params: %{}, refresh_token: nil, token_type: "Bearer"},
token_method: :post, token_url: "/oauth/token"}
iex> token = OAuth2.AccessToken.new("123")
iex> OAuth2.Client.new(token: token)
%OAuth2.Client{authorize_url: "/oauth/authorize", client_id: "",
client_secret: "", headers: [], params: %{}, redirect_uri: "", site: "",
strategy: OAuth2.Strategy.AuthCode,
token: %OAuth2.AccessToken{access_token: "123", expires_at: nil,
other_params: %{}, refresh_token: nil, token_type: "Bearer"},
token_method: :post, token_url: "/oauth/token"}
[hackney documentation]: https://github.com/benoitc/hackney/blob/master/doc/hackney.md#request5
"""
@spec new(t, Keyword.t) :: t
def new(client \\ %Client{}, opts) do
{token, opts} = Keyword.pop(opts, :token)
opts = Keyword.put(opts, :token, process_token(token))
{req_opts, opts} = Keyword.pop(opts, :request_opts, [])

opts =
opts
|> Keyword.put(:token, process_token(token))
|> Keyword.put(:request_opts, Keyword.merge(client.request_opts, req_opts))

struct(client, opts)
end
Expand All @@ -135,7 +148,7 @@ defmodule OAuth2.Client do
"""
@spec put_param(t, String.t | atom, any) :: t
def put_param(%Client{params: params} = client, key, value) do
%{client | params: Map.put(params, param_key(key), value)}
%{client | params: Map.put(params, "#{key}", value)}
end

@doc """
Expand All @@ -144,7 +157,7 @@ defmodule OAuth2.Client do
@spec merge_params(t, params) :: t
def merge_params(client, params) do
params = Enum.reduce(params, %{}, fn {k,v}, acc ->
Map.put(acc, param_key(k), v)
Map.put(acc, "#{k}", v)
end)
%{client | params: Map.merge(client.params, params)}
end
Expand Down Expand Up @@ -199,15 +212,17 @@ defmodule OAuth2.Client do
* `client` - a `OAuth2.Client` struct with the strategy to use, defaults to
`OAuth2.Strategy.AuthCode`
* `params` - a keyword list of request parameters
* `params` - a keyword list of request parameters which will be encoded into
a query string or request body dependening on the selected strategy
* `headers` - a list of request headers
* `opts` - a `Keyword` list of options
* `opts` - a Keyword list of request options which will be merged with
`OAuth2.Client.request_opts`
## Options
* `:recv_timeout` - the timeout (in milliseconds) of the request
* `:proxy` - a proxy to be used for the request; it can be a regular url or a
`{Host, Proxy}` tuple
`{host, proxy}` tuple
"""
@spec get_token(t, params, headers, Keyword.t) :: {:ok, Client.t} | {:error, Error.t}
def get_token(%{token_method: method} = client, params \\ [], headers \\ [], opts \\ []) do
Expand Down Expand Up @@ -388,9 +403,6 @@ defmodule OAuth2.Client do
put_header(client, "content-type", "application/x-www-form-urlencoded")
defp token_post_header(%Client{} = client), do: client

defp param_key(binary) when is_binary(binary), do: binary
defp param_key(atom) when is_atom(atom), do: Atom.to_string(atom)

defp endpoint(client, <<"/"::utf8, _::binary>> = endpoint),
do: client.site <> endpoint
defp endpoint(_client, endpoint), do: endpoint
Expand Down
46 changes: 41 additions & 5 deletions lib/oauth2/request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ defmodule OAuth2.Request do
content_type = content_type(headers)
body = encode_request_body(body, content_type)
headers = process_request_headers(headers, content_type)
req_opts = Keyword.merge(client.request_opts, opts)

case :hackney.request(method, url, headers, body, opts ++ [with_body: true]) do
{:ok, status, headers, body} ->
{:ok, Response.new(status, headers, body)}
case :hackney.request(method, url, headers, body, req_opts) do
{:ok, ref} when is_reference(ref) ->
{:ok, ref}
{:ok, status, headers, ref} when is_reference(ref) ->
process_body(status, headers, ref)
{:ok, status, headers, body} when is_binary(body) ->
process_body(status, headers, body)
{:error, reason} ->
{:error, %Error{reason: reason}}
end
Expand All @@ -36,8 +41,21 @@ defmodule OAuth2.Request do
@spec request!(atom, Client.t, binary, body, Client.headers, Keyword.t) :: Response.t | Error.t
def request!(method, %Client{} = client, url, body, headers, opts) do
case request(method, client, url, body, headers, opts) do
{:ok, response} -> response
{:error, error} -> raise error
{:ok, resp} ->
resp
{:error, %{status_code: code, headers: headers, body: body}} ->
raise %Error{reason: """
Server responded with status: #{code}
Headers:
#{Enum.reduce(headers, "", fn {k, v}, acc -> acc <> "#{k}: #{v}\n" end)}
Body:
#{inspect body}
"""}
{:error, error} ->
raise error
end
end

Expand All @@ -49,6 +67,24 @@ defmodule OAuth2.Request do
end
end

defp process_body(status, headers, ref) when is_reference(ref) do
case :hackney.body(ref) do
{:ok, body} ->
process_body(status, headers, body)
{:error, reason} ->
{:error, %Error{reason: reason}}
end
end
defp process_body(status, headers, body) when is_binary(body) do
resp = Response.new(status, headers, body)
case status do
status when status in 200..399 ->
{:ok, resp}
status when status in 400..599 ->
{:error, resp}
end
end

defp process_params(url, nil),
do: url
defp process_params(url, params),
Expand Down
4 changes: 2 additions & 2 deletions lib/oauth2/response.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ defmodule OAuth2.Response do
defstruct status_code: nil, headers: [], body: nil

@doc false
def new(status_code, headers, body) do
def new(code, headers, body) do
headers = process_headers(headers)
body = decode_response_body(body, content_type(headers))
resp = %__MODULE__{status_code: status_code, headers: headers, body: body}
resp = %__MODULE__{status_code: code, headers: headers, body: body}

if Application.get_env(:oauth2, :debug) do
Logger.debug("OAuth2 Provider Response #{inspect resp}")
Expand Down
33 changes: 30 additions & 3 deletions lib/oauth2/serializer.ex
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
defmodule OAuth2.Serializer do
@moduledoc false

require Logger

defmodule NullSerializer do
@moduledoc false

@doc false
def decode!(content), do: content

@doc false
def encode!(content), do: content
end

def decode!(content, type), do: serializer(type).decode!(content)

def encode!(content, type), do: serializer(type).encode!(content)

defp serializer(type) do
configured_serializers()
|> Map.get(type, NullSerializer)
serializer = Map.get(configured_serializers(), type, NullSerializer)
warn_missing_serializer = Application.get_env(:oauth2, :warn_missing_serializer, true)

if serializer == NullSerializer && warn_missing_serializer do
Logger.warn """
A serializer was not configured for content-type '#{type}'.
To remove this warning for this content-type, add the following to your `config.exs` file:
config :oauth2,
serializers: %{
"#{type}" => MySerializer
}
To remove this warning entirely, add the following to you `config.exs` file:
config :oauth2,
warn_missing_serializer: false
"""
end

serializer
end

defp configured_serializers do
Application.get_env(:oauth2, :serializers) ||
raise("Missing serializers configuration! Make sure oauth2 app is added to mix application list")
end
end

6 changes: 2 additions & 4 deletions lib/oauth2/strategy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ defmodule OAuth2.Strategy do
user = OAuth2.AccessToken.get!(token, "/user")
"""

use Behaviour

alias OAuth2.Client

@doc """
Expand All @@ -80,7 +78,7 @@ defmodule OAuth2.Strategy do
|> merge_params(params)
end
"""
defcallback authorize_url(Client.t, OAuth2.params) :: Client.t
@callback authorize_url(Client.t, OAuth2.params) :: Client.t

@doc """
Builds the URL to token endpoint.
Expand All @@ -98,7 +96,7 @@ defmodule OAuth2.Strategy do
|> put_headers(headers)
end
"""
defcallback get_token(Client.t, OAuth2.params, OAuth2.headers) :: Client.t
@callback get_token(Client.t, OAuth2.params, OAuth2.headers) :: Client.t

defmacro __using__(_) do
quote do
Expand Down

0 comments on commit f82d5f1

Please sign in to comment.